经典的各类排序总结

来源:互联网 发布:淘宝上心悦会员怎么刷 编辑:程序博客网 时间:2024/05/21 09:22

1. 插入排序

实现源码:

void InsertSort(int* arr, unsigned int len){//前置条件判断if (!arr || len <= 1){return;}unsigned int i;unsigned int j;for (i = 1; i < len; i++){j = i - 1;int x = arr[i];while (j >= 0 && arr[j] > x){arr[j + 1] = arr[j];j--;}arr[j + 1] = x;}}

性能分析:假设需要从小到大进行排序,则插入排序的最坏情形是原来的数组是按照从大到小的顺序排列的,这样需要的时间复杂度是:

                         

插入排序的最好情形是原来的数组已经从小到大排好序了,则时间复杂度是:

                         

接下来是插入排序的平均情况,其时间复杂度为:

                        

最后,插入排序是稳定的。                         

2. 折半插入排序

在前面插入排序中,由于每次在插入的过程中,都需要与前面已排好序的元素进行比较,但是需要都进行比较吗?考虑到已经排好序这个特点,所以在比较的时候可以采用折半的方式,这里给出折半插入排序的实现源码:

void InsertHalfSort(int* arr, unsigned int len){//前置条件判断if (!arr || len <= 1){return;}unsigned int i,j,low,mid,high,upper;for (i = 1; i < len; i++){int x = arr[i];//初始化指针low、mid、highlow = 0;high = i - 1;mid = (low + high) / 2;//通过折半查找查询应该插入的位置while (low < high){if (x == arr[mid]){break;}else if (x < arr[mid]){high = mid - 1;}else{low = mid + 1;}mid = (low + high) / 2;}if (arr[mid] <= x){upper = mid + 1;}else{upper = mid;}for (j = i; j >= upper + 1; j--){arr[j] = arr[j - 1];}arr[j] = x;}}

该排序算法的平均时间复杂度为O(nlgn),最好情形依旧为O(n),且是稳定的。

3. 希尔排序

希尔排序是对插入排序的又一次成功的改良,适用于大量数据的排序,且代码简洁:

void ShellSort(int* arr, int len){int i, j, k;k = len / 2;while (k > 0){for (i = k + 1; i < len; i++){int x = arr[i];j = i - k;while (j >= 0 && arr[j] > x){arr[j + k] = arr[j];j = j - k;}arr[j + k] = x;}k = k / 2;}}

4. 归并排序

归并排序的关键是将两个已经排好序的数组合并成一个排序数组,以下是合并函数的实现源码:

//将两个有序的数组合并到一起void Merge(int* arr, unsigned int begin, unsigned int mid, unsigned int high){unsigned int len1 = mid - begin + 1;unsigned int len2 = high - mid;//构造临时数组存储合并的结果int* tempArr = (int* )malloc((len1 + len2) * sizeof(int));//申请内存空间失败,则返回if (!tempArr){return;}unsigned int i = 0;unsigned int j = 0;unsigned int k =  0;while (i < len1 && j < len2){if (arr[i + begin] > arr[j + mid + 1]){tempArr[k] = arr[j + mid + 1];j++;}else{tempArr[k] = arr[i + begin];i++;}k++;}if (i == len1){while (j < len2){tempArr[k] = arr[j + mid + 1];k++;j++;}}if (j == len2){while (i < len1){tempArr[k] = arr[i + begin];k++;i++;}}//将临时数组中的元素赋给原数组for (i = 0; i < len1 + len2; i++){arr[i + begin] = tempArr[i];}//释放空间free(tempArr);}

接下来只要进行递归归并排序即可:

//归并排序void MergeSort(int* arr, unsigned int low, unsigned int high){if (low < high){unsigned int mid = (low + high) / 2;MergeSort(arr, low, mid);MergeSort(arr, mid + 1, high);Merge(arr, low, mid, high);}}

归并排序的时间复杂度为O(nlgn),且是稳定的。

5. 快速排序

快速排序和归并排序一样,采用了分治的思想。快速排序是每次选择一个主元,然后将小于或等于主元的元素放到主元的左边,其余的放到主元的右边。以下是实现源码:

//交换两个整数void Swap(int* x, int* y){int temp = *x;*x = *y;*y = temp;}int Split(int* arr, int low, int high){int i = low;//指针i始终指向轴点int j = low + 1;//指针j用于遍历数组while (j <= high){if (arr[j] <= arr[i])//如果小于或等于主元,则交换{i++;//注意,这里是先增加指针i,然后交换Swap(&arr[i], &arr[j]);}j++;}Swap(&arr[i], &arr[low]);return i;}//快速排序void QuickSort(int* arr, int low, int high){if (low >= high){return;}int w = Split(arr, low, high);QuickSort(arr, low, w - 1);QuickSort(arr, w + 1, high);}

快速排序的最坏情况是出现单边树的情况,此时快速排序的时间复杂度为O(n^2),其余情况下时间复杂度都是O(nlgn)。

6. 计数排序

如果已知序列中比元素x小的个数,则可以确定元素x在排序后的位置。这就是计数排序的基本思想,计数排序需要两个辅助数组B和C,B表示排好序的输出数组,C用于存储每个元素在原序列中比该元素小的个数,则有以下实现源码:

void CountSort(int* A, int* B, int* C, int len, int k){int i = 0;//将计数数组初始化为0for (i = 0; i <= k; i++){C[i] = 0;}//计数数组取值为A[i]的元素个数for (i = 0; i < len; i++){C[A[i]] = C[A[i]] + 1;}//计数数组取值为小于或等于i的元素个数,放在C[i]里面for (i = 1; i < len; i++){C[i] = C[i] + C[i - 1];}for (i = len - 1; i >= 0; i--){B[C[A[i]]] = A[i];C[A[i]] = C[A[i]] - 1;}}

计数排序的有点是线性时间和稳定性,但是由于计数排序的运行时间与输入参数的取值规模有关,所以在有些情况下会达到平方级时间,所以具有一定的局限性。

7. 基数排序

8. 桶排序

9. 堆排序

原创粉丝点击