【数据结构】常见的排序方法的实现以及性能对比

来源:互联网 发布:淘宝网货到付款 编辑:程序博客网 时间:2024/06/07 14:58

前言:

  排序算法在笔试和面试的时候经常出现,有时候面试官可能不止问我们排序的写法,还会问相关算法的一些性能的比较。平时学习时,由于排序算法较多,如果不做一些归纳总结的话,很容易混为一谈,并且没有对每个排序有很明确的定位。就会导致在面试的时候哑口。


一、插入排序

  在我看来插入排序的思路简单,效率一般,最好的情况是O(N)即有序或接近有序的时候效率还是很高的,最坏的情况是O(N^2)即逆序时的情况,稳定。就效率而言算不上很优。下面看下插入排序的思路。

  1.思路分析

  插入排序指的是在数组中取一个数,拿这个数跟前面的数比较,如果比前面的数小,那么把当前的这个数往后挪一下,把后面的值挪到前面来,然后依次跟前面的依次比较。

//插入排序void InsertSort(int* a, int size){for (int i = 0; i<size - 1; i++){int end = i;int tmp = a[end + 1];while (end >= 0 && a[end] > a[end + 1]){a[end + 1] = a[end];a[end] = tmp;--end;}}}

二、shell排序

  shell排序的最好情况为O(N)即有序的情况,最坏情况为O(N^2)即逆序的情况,但是shell排序的时间复杂度平均值为O(N^1.3)。

  1.思路分析

  其实shell排序差不多可以理解为插入排序的优化,每次调整序列使之接近有序然后做和插入排序相类似的操作,然后实现shell排序。

//希尔排序void shellsort1(int a[], int n){int gap = n;while (gap > 1){gap = gap / 3 + 1;for (int i = 0; i<n-gap; i++)//小于n-gap的没一个数都比较一次{int end = i;int tmp = a[end + gap];while (end >= 0 && a[end]>a[end + gap]){a[end + gap] = a[end];a[end] = tmp;end = end - gap;}a[end + gap] = tmp;}}}

三、选择排序

  选择排序的最好情况为O(N^2),最坏情况也为O(N^2),所以对于选择排序是排序中比较差的那一种。

  1.思路分析

  实现思路就是每次选择一个最小的放到前面,或者选择一个最大的放到最后面(这里是升序排序的),但是有优化就是选同时选一个最小最大的,放到最前和最后,这样能够提高一一倍的效率。

//选择排序void ChoiceSort(int* a,int size){    assert(a);    int min, max;    for (int i = 0; i < size/2; i++)    {        min = i;        max = size - 1 - i;        for (int j = i+1; j < size; j++)//可能最小的一个值在最大值里面        {            if (a[min] > a[j])                min = j;            if (a[max] < a[size-j-1])                max = size-j-1;        }        if (min != i)        {            swap(a[i], a[min]);            if (max == i)//如果max在第一个位置就会被换走,所以需要重新换回来,重定向到交换后的位置            {                max = min;            }        }        if (max !=size-1-i)        swap(a[max], a[size - 1 - i]);    }}

四、堆排序

  堆排序的最好情况是O(n*lgN),最坏也如此,因为堆排序是根据他的高度来决定的。

  1.思路分析

  堆排序的实现思路就是先建好一个大堆或者小堆,然后每次拿堆头和最后一个叶子节点交换值,取出来以后放到最后,然后缩小区间调整堆。

void AdjustDown(int* a, int size, int parent){int child = parent * 2 + 1;while (child < size){//找子节点中的最大值if (child + 1 < size&&a[child + 1] > a[child]){++child;}//如果孩子节点大于父节点交换if (a[parent] < a[child]){swap(a[parent], a[child]);parent = child;child = 2 * parent + 1;}else{break;}}}//堆排序void HeapSort(int* a, int size){//建堆for (int i = (size-2)/2; i >= 0; i--){AdjustDown(a, size, i);}//堆排序for (int i = 0; i < size; i++){swap(a[0], a[size - 1 - i]);AdjustDown(a, size - 1 - i, 0);}}

五、冒泡排序

  冒泡排序的最好情况是O(N),最坏是O(N^2),效率不是特别高

  1.思路分析

  冒泡排序的实现就是前后比较选出一个最大或最小的数放到最后,两重循环控制就能实现了。但是有优化,只需要设置个标志位判断是否已经完成排序。

//优化的冒泡排序void Bubble(int* a, int n){for (int i = 0; i < n - 1; i++){bool judge = false;for (int j = 0; j < n - 1 - i; j++){if (a[j] > a[j + 1]){swap(a[j], a[j + 1]);judge = true;}}if (!judge){return;}}}

六、快速排序

  快速排序就是个比较优的排序了,虽然他的最坏情况是O(N^2),但是他的总体情况是O(N*lgN)的,总体而言效率还是很高,再加上一些优化,性能还是很不错的

  1.思路分析

  挖坑法:选定最右边的值作为坑,从左边开始找比选定的值大的,然后把值写入这个位置,然后从右边选一个小的,填入到刚刚转移出去的位置,一直找直到左右指针相等,然后把选定的值填入中间位置就完成了。

  prev和cur法:prev一开始赋值为-1,cur赋值为0。cur遇到比选定的右边的值大的话就往后走,遇到小的就停下来,然后++prev再交换prev和cur的值,使小的上前大的往后走,prev始终指向的是大的值的前一个。

  交换法:同样选定右边的值作为中间值,从左边找一个大的停下来,从右边找一个小的停下来,然后交换左右两边的值,这样就实现了大的往后小的往前的目的。

//快速排序--》挖坑法void QuickSort(int* a, int left,int right){if (left < right){int low = left;int high = right;int key = a[low];while (low < high){//从右边找到第一个小于基数的位置while (low< high && a[high]> key)high--;a[low] = a[high];//从左边找到第一个大于技术的位置while (low < high && a[low] < key)low++;a[high] = a[low];}a[low] = key;QuickSort(a, left, low - 1);QuickSort(a, low + 1, right);}}
int PartSort(int* a, int left, int right)//prev和cur法{assert(a);if (left < right){int cur = left;int prev = left - 1;int key = a[right];while (cur < right){if (a[cur] < key){prev++;if (cur != prev)swap(a[cur], a[prev]);}cur++;}swap(a[++prev], a[right]);return prev;}return left;}

void QuickSort_NonR(int* a, int left, int right)//非递归实现{assert(a);stack<int> s;if (left < right){//先压入左,再压入右,取的时候先取右,再取左,每次去左右区间排序下,然后一直循环下去,借助栈,跟栈的原理差不多int mid = PartSort(a, left, right);if (left < (mid - 1)){s.push(left);s.push(mid - 1);}if (mid + 1 < right){s.push(mid + 1);s.push(right);}while (!s.empty()){int high = s.top();s.pop();int low = s.top();s.pop();if (high - low < 13)InsertSort(a, high -low+1);mid = PartSort(a, low, high);if (low < mid - 1){s.push(low);s.push(mid - 1);}if (mid + 1 < high){s.push(mid + 1);s.push(high);}}}}
七、归并排序

  归并排序的最好最坏情况都为O(N*lgN),稳定

  1.思路分析

  归并排序的思路就是先开辟一块空间,把原来的序列每次分一半,一直分到只有一个值为止,然后一个一个合并,先把排序好的值拷到开辟的空间中,然后再拷会原数组,区间每次增加一半,直到增回原来的值。

void MergrPartSort(int* a, int first, int mid, int end, int* tmp){assert(a);int begin1 = first;int begin2 = mid + 1;int last1 = mid;int last2 = end;int i = first;while (begin1 <= last1 && begin2 <= last2){if (a[begin1] <= a[begin2])tmp[i++] = a[begin1++];elsetmp[i++] = a[begin2++];}while (begin1 <= last1)tmp[i++] = a[begin1++];while (begin2 <= last2)tmp[i++] = a[begin2++];for (int j = first; j <= end; j++){a[j] = tmp[j];}}void MergrSort(int *a, int first, int end, int* tmp){assert(a);if (first < end){int mid = first + (end - first) / 2;//一直递归到只有一个MergrSort(a, first, mid, tmp);MergrSort(a, mid + 1, end, tmp);//递归到最后一种情况的时候,做合并处理(相当于链表的合并)MergrPartSort(a, first, mid, end, tmp);}}void MergrSort_R(int* a,int n){assert(a);//开辟一块空间,用于归并排序时,将空间先在数组中排序好,然后在放回原数组int* tmp = new int[n];//如果开辟不成功,返回if (tmp == NULL)return ;MergrSort(a, 0,n-1,tmp);delete[] tmp;}

//非递归写法void sort(int *a, int *tmp, int begin, int mid, int end){int begin1 = begin, begin2 = mid + 1, k=begin;while (begin1<=mid && begin2<=end){if (a[begin1] <= a[begin2]){tmp[k++] = a[begin1++];}elsetmp[k++] = a[begin2++];}while (begin1 <= mid){tmp[k++] = a[begin1++];}while (begin2 <= end){tmp[k++] = a[begin2++];}/*for (int i = begin; i <= end; i++){a[i] = tmp[i];}*/}void Merge(int* a, int* tmp, int k, int n){int i = 0, j;while (i + 2*k <= n)//按照间隔为k,每一次分类2*k个区间,直到所有的都分类完{sort(a, tmp, i, i + k - 1, i + 2 * k - 1 );//为什么中间值是i+k-1;因为这是做一个默认处理,将前面区间的最后一个值作为中间值,因为都是双倍的i += 2 * k;}if (i+ k + 1 < n)//如果剩余的个数比一个k长度还多,那么就在进行一次合并sort(a,  tmp, i, i + k - 1, n - 1);else//其他情况说明这一块是已经排序好了的,直接拷贝过去就行了,为什么会排序好,因为前面排序小区间时排序好了{for (j = i; j < n; j++){tmp[j] = a[j];}}for (i = 0; i < n; i++)//将值拷回原数组{a[i] = tmp[i];}}//归并排序的非递归写法void MergeSort_NonR(int* a, int n){int * tmp = new int[n];int i = 1;while (i < n){Merge(a, tmp, i, n);i *= 2;//合并时每次翻一倍,1 2 4 8 16}delete[] tmp;}void testMerge_NonR(){int a[] = { 5, 5, 3, 4, 7, 5, 2, 5, 1, 5, 0, 5, 5 };MergeSort_NonR(a, sizeof(a) / sizeof(a[0]));Print(a, sizeof(a) / sizeof(a[0]));}



0 0