排序算法总结

来源:互联网 发布:软件规模度量单位 编辑:程序博客网 时间:2024/04/27 09:17
1. 插入排序
算法步骤叙述:对于一个给定的序列, 初始时假设第一个记录自成一个有序序列, 其余记录为无序序列。接着从第二个记录开始, 按照记录的大小依次将当前处理的记录插入到其之前的有序序列之中,直至最后一个记录插入到有序序列中为止。
插入排序图示:
初始数组为:5,2,4,6,1,3
 
java实现:
  1. /**
  2. * 插入排序
  3. * @param data
  4. */
  5. public static void insertSort(int[] data){
  6. for (int i = 1; i < data.length; ++i){
  7. int curData = data[i];
  8. int j = i - 1;
  9. while (j >= 0){
  10. if (curData < data[j]){
  11. data[j + 1] = data[j];
  12. j--;
  13. }else{
  14. break;
  15. }
  16. }
  17. data[j + 1] = curData;
  18. }
  19. }
  20. /**
  21. * 在一定范围内进行插入排序, 快速排序时使用
  22. * @param data
  23. * @param left
  24. * @param right
  25. */
  26. public static void insertSortWithBound(int[] data, int left, int right){
  27. for (int i = left + 1; i <= right; ++i){
  28. int curData = data[i];
  29. int j = i - 1;
  30. while (j >= 0){
  31. if (curData < data[j]){
  32. data[j + 1] = data[j];
  33. j--;
  34. }else{
  35. break;
  36. }
  37. }
  38. data[j + 1] = curData;
  39. }
  40. }
C实现:
  1. /*********************************************************************************************************************************/
  2. // 插入排序声明, 自己实现的, 不够简洁
  3. // 插入排序有一个特点: 那就是已经排好序的元素就是最终的元素, 利用插入排序也可以实现查找第k小的元素. 只是相对于运用优先队列来说耗时较长
  4. // 插入排序的平均和最坏时间复杂度都是O(N^2)
  5. void InsertSort(int data[], int length)
  6. {
  7. int temp, i = 0, j = 0;
  8. for (i = 1; i < length; ++i)
  9. {
  10. temp = data[i];
  11. j = i - 1;
  12. while(j >= 0)
  13. {
  14. if(temp < data[j]){
  15. data[j + 1] = data[j]; // 如果data[j+1]位置的元素比data[i]中的元素大的话, 就往后移动一个位置.
  16. --j;
  17. }
  18. else
  19. break; // 如果data[j+1]位置的元素比data[i]中的元素小的话, 说明后面的那个空位就是data[i]的正确位置
  20. }
  21. data[j + 1] = temp; // 将data[i]放到正确的位置到
  22. }
  23. }
  24. /*********************************************************************************************************************************/

2. 选择排序
算法步骤叙述:对于给定的一组记录,经过第一轮比较后得到最小的记录,然后将该记录与第一个记录的位置进行交换; 接着对不包括第一个记录以外的其他记录进行第二轮比较,  得到最小的记录和第二个位置的记录进行交换; 重复该过程, 直到进行到比较的记录只有一个时为止。
选择排序图示:

Java实现:
  1. /**
  2. * 选择排序
  3. * @param data
  4. */
  5. public static void selectSort(int[] data){
  6. for (int i = 0; i < data.length - 1; ++i){
  7. int temp = i;
  8. for (int j = i + 1; j < data.length; ++j){
  9. if (data[temp] > data[j])
  10. temp = j;
  11. }
  12. swap(data, i, temp);
  13. }
  14. }

3. 冒泡排序
算法步骤描述:对于给定的n个记录,从第一个记录开始依次对相邻的两个记录进行比较,当前面的记录大于后面的记录时,交换位置,进行一轮比较和交换后, n个记录中的最大值将位于第n位; 然后对前(n - 1)个记录进行第二轮比较; 重复该过程直到比较的记录只剩下一个为止。
冒泡排序图示:
一次冒泡的过程。
 
Java实现:
  1. /**
  2. * 冒泡排序
  3. */
  4. public static void bubbleSort(int[] data){
  5. for (int i = data.length - 1; i > 0; --i){
  6. int flag = 0;
  7. for (int j = 0; j < i; ++j){
  8. if (data[j] > data[j + 1]){
  9. swap(data, j, j + 1);
  10. ++flag;
  11. }
  12. }
  13. // 如果在一次冒泡中一次都没有进行交换, 那么这个数组已经是有序了,这个操作会提高冒泡排序的效率
  14. if (flag == 0)
  15. break;
  16. }
  17. }

4. 希尔排序
算法步骤描述:先将待排序列的数组元素分成多个子序列,使得每个子序列的元素个数相对较少, 然后对各个子序列分别进行直接插入排序, 待整个待排序列基本有序后, 最后再对所有元素进行一次插入排序。
希尔排序的详细步骤:
1. 选择一个步长序列t1, t2, ....., tk, 满足ti > tj, tk = 1
2. 按步长序列个数k, 对待排序列进行k趟排序
3. 每趟排序,根据对应的步长ti,将待排序列分割成ti个子序列, 分别对各个子序列进行插入排序
4. 最后一趟的步长为1, 也就是说直接对整个数组进行了一次直接插入排序,因为经过前面的步骤后,数组中的元素已经基本有序,此时再进行一次插入排序效率会很高。
注意:希尔排序的效率依赖于增量序列的选择, 所以对于选择使用不同的增量序列进行希尔排序,效率会相差很大;通过前人的实践得出了两个效率高的两个增量序列
 1. Hibbard增量序列: 1, 3, 7, ....., 2^k - 1; 采用此增量序列的希尔排序的最坏运行时间是N^(5/4).   
 2. sedgewick增量序列: 4^i - 3*2^i + 1; 采用此增量序列的希尔排序的最坏运行时间是O(N^(7/6))
希尔排序图示:

这个图感觉好形象的说。。。

上图是实现了序列为5, 2, 1的希尔排序的操作图示
Java实现:
  1. /**
  2. * 希尔排序
  3. * 希尔排序的时间复杂度依赖于增量序列的优劣
  4. *希尔排序最坏的时间复杂度为: O(N^2)
  5. * 采用Hibbard增量序列: 1, 3, 7, ....., 2^k - 1; 采用此增量序列的希尔排序的最坏运行时间是N^(5/4).
  6. * @param data
  7. */
  8. public static void hillSort(int[] data){
  9. int i = 0, j = 0;
  10. int H = (1 << (int) Math.log(data.length)) - 1; // 注意:"-"的优先级比"<<"高, 如果不加括号的话,会导致运算错误
  11. for (; H >= 1; H = (H + 1) / 2 - 1){
  12. // 内部是一个以H为间隔的子数组进行了插入排序
  13. for (i = H; i < data.length; ++i){
  14. j = i - H;
  15. int temp = data[i];
  16. while (j >= 0){
  17. if (data[j] > temp){
  18. data[j + H] = data[j];
  19. j -= H;
  20. }
  21. else
  22. break;
  23. }
  24. data[j + H] = temp;
  25. }
  26. }
  27. }
C实现:

5. 归并排序
算法步骤描述:归并排序利用了递归和分治的技术。 对于一个给定的序列, 先将该序列利用递归的方式, 将序列每次递归都将序列1分为2, 即 1->2->4->8直到分为一个元素自成一个序列为止, 然后开始将两个相邻的有序序列两两合并, 合成一个较大的有序子序列, 8->4->2->1. 最后合并成为一个有序序列, 算法结束。下面的图很生动形象的展示了这个过程。
二路归并排序排序的过程需要logn趟, 每一趟都要涉及到每一个元素, 所以每一趟的时间复杂度为o(n), 因此, 二路归并排序的时间复杂度为O(nlogn).
归并排序图示:

 Java实现:
  1. /**
  2. * 归并排序
  3. * 二路归并排序的时间复杂度为o(nlogn)
  4. */
  5. public static void mergeSort(int[] data){
  6. Msort(data, 0, data.length - 1);
  7. }
  8. /**
  9. * 核心函数,通过递归实现将一个大数组分解为小数组,然后进行归并
  10. * @param data
  11. * @param leftStart
  12. * @param rightEnd
  13. */
  14. public static void Msort(int[] data, int leftStart, int rightEnd){
  15. if (leftStart < rightEnd){
  16. int middle = (leftStart + rightEnd) / 2; // 获取中间序号
  17. Msort(data, leftStart, middle); // 先对左半部分进行归并排序
  18. Msort(data, middle + 1, rightEnd); // 在对右半部份进行归并排序
  19. merge(data, leftStart, middle + 1, rightEnd); // 将左半部分和右半部份进行合并
  20. }
  21. }
  22. /**
  23. * 合并两个数组中的数据,合并的前提是这两个数组都是有序的
  24. * @param data
  25. * @param leftStart
  26. * @param rightStart
  27. * @param rightEnd
  28. */
  29. private static void merge(int[] data, int leftStart, int rightStart, int rightEnd){
  30. int leftEnd = rightStart - 1;
  31. int[] tempArray = new int[data.length];
  32. int ls = leftStart, rs = rightStart, ts = leftStart;
  33. // 关键步骤,通过每次比较两个数组的当前头节点来合并数组(前提是这两个数组都是有序的)
  34. while (ls <= leftEnd && rs <= rightEnd){
  35. if (data[ls] < data[rs])
  36. tempArray[ts++] = data[ls++];
  37. else
  38. tempArray[ts++] = data[rs++];
  39. }
  40. // 左边剩下,拷贝剩余的部分
  41. while (ls <= leftEnd)
  42. tempArray[ts++] = data[ls++];
  43. // 右边剩下, 拷贝剩余的部分
  44. while (rs <= rightEnd)
  45. tempArray[ts++] = data[rs++];
  46. // 将tempArray中合并好的数组拷贝到data数组中
  47. for (int i = rightEnd; i >= leftStart; --i)
  48. data[i] = tempArray[i];
  49. }
C实现:
  1. /*********************************************************************************************************************************/
  2. // 归并排序
  3. // 归并排序的最坏时间复杂度为:O(NlogN)
  4. // 归并排序需要额外的空间. 大小等于原数组的大小N.
  5. void MergeSort(int data[], int length)
  6. {
  7. void MSort(int data[], int TempArray[], int leftStart, int rightEnd);
  8. int * TempArray = NULL;
  9. TempArray = (int *)malloc(length * sizeof(int)); // 为temp数组分配空间.
  10. if (TempArray == NULL)
  11. {
  12. cout << "可用空间不足, 无法进行排序" << endl;
  13. return;
  14. }
  15. else
  16. {
  17. MSort(data, TempArray, 0, length - 1);
  18. }
  19. // 释放占用的空间
  20. if (TempArray != NULL)
  21. free(TempArray);
  22. }
  23. // 归并排序的主要操作, 递归进行归并
  24. void MSort(int data[], int TempArray[], int leftStart, int rightEnd)
  25. {
  26. void Merge(int data[], int TempArray[], int leftStart, int RightStart, int RightEnd);
  27. if (leftStart < rightEnd)
  28. {
  29. int Middle = (leftStart + rightEnd) / 2;
  30. MSort(data, TempArray, leftStart, Middle); // 递归使用归并操作对左半数组排序
  31. MSort(data, TempArray, Middle + 1, rightEnd); // 递归归并操作对右半数组排序
  32. Merge(data, TempArray, leftStart, Middle + 1, rightEnd); // 左右半数组进行合并操作
  33. }
  34. }
  35. // 归并排序用到的低级操作
  36. // 合并操作: 将两个有序数组合并为一个
  37. // 思想等同于以前写的两个有序链表的合并算法.
  38. void Merge(int data[], int TempArray[], int leftStart, int RightStart, int RightEnd)
  39. {
  40. int leftEnd = RightStart - 1;
  41. int dataNumber = RightEnd - leftStart + 1;
  42. int lS = leftStart, rS = RightStart, tS = leftStart;
  43. // 主和并循环
  44. while (lS <= leftEnd && rS <= RightEnd)
  45. {
  46. if (data[lS] <= data[rS])
  47. TempArray[tS++] = data[lS++];
  48. else
  49. TempArray[tS++] = data[rS++];
  50. }
  51. // 如果left数组剩下了, 则将剩下的部分直接复制到TempArray的后面
  52. while (lS <= leftEnd)
  53. TempArray[tS++] = data[lS++];
  54. // 如果right数组剩下了, 则将剩下的部分直接复制到TempArray的后面
  55. while (rS <= RightEnd)
  56. TempArray[tS++] = data[rS++];
  57. for (int i = RightEnd; i >= leftStart; --i)
  58. data[i] = TempArray[i];
  59. }
  60. /*********************************************************************************************************************************/

6. 快速排序
算法步骤描述:对于给定的记录,通过一趟排序后, 将原序列分为两个部分,其中前一个部分的所有记录均比后一部分的所有记录小, 然后再依次对前后两个部分的记录进行快速排序, 递归该过程, 直到该序列中的所有记录均有序为止。
具体步骤:
1. 分解: 选择一个枢纽值,将输入的序列array[m...n]划分成两个非空子序列array[m...k-1]和array[k+1...n],使得array[m...k-1]中的任一元素的值都不大于枢纽值, array[k+1...n]中的任一元素的值不小于枢纽值。
2. 递归:通过递归的方式对array[m...k-1]和array[k+1...n]进行快速排序。
3. 递归操作完成后整个数组就成为了一个有序数组。
两点注意:
1. 枢纽值的选取:枢纽值的选择越能将数组平均分开越好,反之将降低快速排序的运行效率. 如果每次都以数组第一个元素作为枢纽值时, 当数组元素有序时将得到最坏的运行效率o(n^2),所以这种方法是不允许的。 这里提供两种比较好的选择枢纽值得方式:第一种就是通过随机方式选取枢纽值, 但是随机选取需要用到随机数生成,这个代价还是比较昂贵的,一般采用第二个方法:那就是采用三数中值法。将首, 中, 尾位置上的记录进行比较,选择三者的中值作为枢纽值。这样的选取方式可以很好的工作。但是这种方式要求数组必须要有三个以上的元素。
2. 有如下的事实:对于小数组(N<20), 快速排序不如插入排序。所以在递归中, 如果当前的数组的元素个数小于5, 我们就采取使用插入排序的方式进行排序,这样做有两个好处,既提高了效率, 同时也满足了三数中值法的要求。

Java实现
  1. /**
  2. * 快速排序
  3. * 平均时间复杂度为:o(nlogn)
  4. * 最坏时间复杂度为:o(n^2)
  5. * @param data
  6. */
  7. public static void quickSort(int[] data){
  8. Qsort(data, 0, data.length - 1);
  9. }
  10. /**
  11. * 快速排序的核心函数
  12. * 下面的算法采用了三数中值分割法, 尽可能好的选择了枢纽.
  13. * 快速排序需要通过递归来实现,其最大的深度为logn + 1(向上取整), 所以空间复杂度应为logn
  14. * @param data
  15. * @param left
  16. * @param right
  17. */
  18. public static void Qsort(int[] data, int left, int right){
  19. if (right - left > 5){ // 判断当前数组的大小, 如果当前数组过小, 则使用插入排序, 因为此时插入排序比快速排序要快
  20. int pivot = middle3(data, left, right);
  21. int i = left, j = pivot; // 因为最右边的那个数已经符合要求啦(因为在middle3中会使得最右边的那个数大于枢纽值)
  22. for (;;){
  23. while (data[++i] < data[pivot]); // 从左向右遍历元素, 遇到比枢纽值大的元素就在此停下来等待互换
  24. while (data[--j] > data[pivot]); // 从右向左遍历元素, 遇到比枢纽值小的元素就在此停下来等待互换
  25. if (i < j){ // 检查i 是否已经越过 j, 没有越过则需要交换两个位置上的值
  26. swap(data, i, j);
  27. }else{
  28. break;
  29. }
  30. }
  31. swap(data, i, pivot); // 将枢纽还原到原来的正确位置, 因为i在结束时肯定指向的是一个大于枢纽值的元素, 所以可以直接互换
  32. Qsort(data, left, i - 1); // 左半边递归快排
  33. Qsort(data, i + 1, right); // 右半边递归快排
  34. }else{
  35. // 当数组的个数小于3时,使用插入排序比使用快速排序效率更高
  36. insertSortWithBound(data, left, right);
  37. }
  38. }
  39. /**
  40. * 三位中值分割法, 用于选取合适的枢纽, 是快速排序效率更高(相比于直接使用0号元素作为枢纽来进行排序, 三位中值分割法能更好的选取合适的枢纽值)
  41. * 从头, 中, 尾三个元素中选出中间元素作为枢纽, 并将他们排序。返回值是枢纽元素所在的位置
  42. * @param data
  43. * @param left
  44. * @param right
  45. * @return
  46. */
  47. private static int middle3(int[] data, int left, int right){
  48. // 算出中间序号
  49. int middle = (left + right) / 2;
  50. // 将最前, 中间, 最后三个位置上的数据进行排序
  51. if (data[left] > data[middle])
  52. swap(data, left, middle);
  53. if (data[left] > data[right])
  54. swap(data, left, right);
  55. if (data[middle] > data[right])
  56. swap(data, middle, right);
  57. // 将中间位置上的数据(枢纽值)和right-1位置的数据交换位置
  58. swap(data, middle, right - 1);
  59. return right - 1; // 交换位置后,right - 1 位置上的值才是枢纽值。
  60. }
C实现
  1. /*********************************************************************************************************************************/
  2. // 快速排序
  3. // 有以下的事实: 对于很小的数组(N <= 20), 快速排序不如插入排序好.
  4. // 下面的算法采用了三数中值分割法, 尽可能好的选择了枢纽.
  5. // 快速排序的平均情况的时间复杂度为: O(NlogN).
  6. // 快速排序的最好情况的时间复杂度为: O(NlogN).
  7. // 快速排序的最坏情况的时间复杂度为:O(N^2).
  8. #define TRESHOLD 3
  9. void QuickSort(int data[],int left, int right)
  10. {
  11. int Middle3(int data[], int left, int right);
  12. void Swap(int* x, int* y);
  13. //*1*
  14. if (left + TRESHOLD <= right) // 判断当前数组的大小, 如果当前数组过小, 则使用插入排序, 因为此时插入排序比快速排序要快
  15. {
  16. int pivot = Middle3(data, left, right); // 只有在进行快排的时候才需要进行此操作, 在进行插入排序的时候则不需要此排序(如果在执行插入排序时进行了此操作会出错, 所以这个操作一定要放到if语句内部, 而不能放在外部, 即*1*处)
  17. int i = left, j = pivot;
  18. for (;;)
  19. {
  20. while (data[++i] < data[pivot]) ; // 从左向右遍历元素, 遇到比枢纽值大的元素就在此停下来等待互换
  21. while (data[--j] >data[pivot]) ; // 从右向左遍历元素, 遇到比枢纽值小的元素就在此停下来等待互换
  22. if (i > j) // 检查i 是否已经越过 j, 如果越过证明已经遍历完成
  23. {
  24. Swap(&data[i], &data[pivot]); // 将枢纽还原到原来的正确位置, 因为i在结束时肯定指向的是一个大于枢纽值的元素, 所以可以直接互换
  25. break;
  26. }
  27. Swap(&data[i], &data[j]); // 如果i仍然小于j, 那么就需要将i停在的位置的元素和j停在的位置的元素进行互换. 让他们都在合适的位置
  28. }
  29. QuickSort(data, left, i - 1); // 左半边递归快排
  30. QuickSort(data, i + 1, right); // 有半边递归快排
  31. }
  32. else
  33. {
  34. InsertSort(data + left, right - left + 1); // 当数组的个数不大于3时,使用插入排序, 因为小数组时时插入比快排更快
  35. }
  36. }
  37. // 三位中值分割法, 用于选取合适的枢纽, 是快速排序效率更高(相比于直接使用0号元素作为枢纽来进行排序, 三位中值分割法能更好的选取合适的枢纽值)
  38. // 从头, 中, 尾三个元素中选出中间元素作为枢纽, 并将他们排序
  39. // 返回值是枢纽元素所在的位置
  40. int Middle3(int data[], int left, int right)
  41. {
  42. void Swap(int* x, int* y);
  43. int middle = (left + right)/2;
  44. // 将这三个位置上的元素按照从小到大的顺序排序
  45. if (data[left] > data[middle])
  46. Swap(&data[left], &data[middle]);
  47. if (data[left] > data[right])
  48. Swap(&data[left], &data[right]);
  49. if (data[middle] > data[right])
  50. Swap(&data[middle], &data[right]);
  51. // 将middle位置的枢纽和right - 1位置的元素互换
  52. // 互换之后, right - 1的位置上的元素就是枢纽
  53. int temp = data[middle];
  54. data[middle] = data[right - 1];
  55. data[right - 1] = temp;
  56. return right - 1; // 返回middle位置
  57. }
  58. // 交换两个位置上的元素函数
  59. void Swap(int* x, int* y)
  60. {
  61. int temp = *x;
  62. *x = *y;
  63. *y = temp;
  64. }
  65. /*********************************************************************************************************************************/

7. 堆排序
算法步骤描述:对于给定的n个记录,初始时把这些记录看作一颗完全二叉树, 然后将其调整为一个大顶堆, 然后将堆中的最后一个元素与堆顶元素(二叉树的根节点)进行交换后, 堆的最后一个元素即为最大记录; 接着将前n-1个元素重新调整为一个大顶堆,再将堆顶元素与当前堆的最后一个元素进行交换后得到次大的记录, 重复该过程直到堆中只剩下一个元素时为止。此时得到了一个有序序列
堆排序对记录较多的数组是很有效的,但是对记录较少的数组一般。堆排序的最大的优点就是即使在最坏的情况下,其时间复杂度也是nlogn。
堆排序图示:
初始堆为: 3,2,6,4,1,0,6,7,5
 
Java实现
  1. /**
  2. * 堆排序
  3. * 堆排序的原理是先构建一个Max堆(大顶堆), 然后通过删除大顶堆的最大的节点(将根节点依次和最后一个节点进行互换位置进行删除操作).
  4. * 最后排序完成(最后是升序排列的数据).
  5. * 最坏时间复杂度: NlogN
  6. * 平均时间复杂度: NlogN
  7. * @param data
  8. */
  9. public static void heapSort(int[] data){
  10. // 首先将数组调整成为大顶堆
  11. int length = data.length;
  12. for (int i = length/2 - 1; i >= 0; --i){ // length/2 - 1的位置是最后一个有孩子的节点的位置
  13. percolateDown(data, i, length - 1);
  14. }
  15. // 将大顶堆的顶点值和最后一个位置的值互换,然后再次进行下滤操作从剩余的数组中选出最大值, 重复该操作
  16. for (int i = length - 1; i >= 0; --i){
  17. swap(data, 0, i);
  18. percolateDown(data, 0, i - 1);
  19. }
  20. }
  21. /**
  22. * 下滤操作, 堆排序所需的基本操作, 这个需要有完全二叉树的基本知识, 建议看一下树那一章。
  23. * @param data
  24. * @param i
  25. * @param N
  26. */
  27. public static void percolateDown(int[] data, int i, int N){
  28. int temp, child;
  29. for (temp = data[i]; leftChild(i) <= N; i = child){
  30. child = leftChild(i);
  31. if (child != N && data[child] < data[child + 1]){ // 如果有右孩子并且右孩子的值大于左孩子的值, 那么指向右孩子, 否则什么也不做
  32. child++;
  33. }
  34. if (data[child] > temp){ // 最大的孩子和temp(爹)中的值进行比较,如果孩子的值比temp大,则爹和孩子互换位置, 如果孩子的值比temp小, 则停止循环
  35. data[i] = data[child];
  36. }else{
  37. break;
  38. }
  39. }
  40. data[i] = temp; // 将temp中的值赋给当前的位置
  41. }
  42. /**
  43. * 计算左孩子的位置,因为数组的下标是从0开始的,所以计算左孩子下表的计算方法会和以1开始的数组的计算方式不太一样
  44. * @param i
  45. * @return
  46. */
  47. private static int leftChild(int i) {
  48. return 2*i+ 1;
  49. }
C实现:
  1. /*********************************************************************************************************************************/
  2. // 堆排序
  3. // 堆排序的原理是先构建一个Max堆(大顶堆), 然后通过删除大顶堆的最大的节点(将根节点依次和最后一个节点进行互换位置进行删除操作.
  4. // 最后排序完成(最后是升序排列的数据).
  5. // 最坏时间复杂度: NlogN
  6. // 平均时间复杂度: NlogN
  7. void HeapSort(int data[], int length)
  8. {
  9. void PercolateDown(int data[], int i, int length);
  10. // 第一步, 通过下滤操作构建Max堆
  11. for(int i = length/2; i >= 0; --i)
  12. PercolateDown(data, i, length);
  13. cout << "完成Max堆的构建" << endl;
  14. for (int j = length - 1; j > 0; --j) // 每次都将堆顶元素和当前堆的最后一个元素交换, 然后再次调整堆使之成为大顶堆
  15. {
  16. int temp = data[j];
  17. data[j] = data[0];
  18. data[0] = temp;
  19. PercolateDown(data, 0, j);
  20. }
  21. }
  22. // 堆排序所需要的下滤操作
  23. #define LeftChild(i) ((2*i)+1) // 因为数组是以0开头的, 所以左孩子是2 * i + 1, 而右孩子是2 * i+ 2
  24. void PercolateDown(int data[], int i, int length)
  25. {
  26. int temp, child;
  27. for (temp = data[i]; (child = LeftChild(i)) < length; i = child)
  28. {
  29. if (child != length - 1 && data[child] < data[child + 1]) // 因为for中已经判断了child是小于length的, 所以在此只通过判断child是否等于length - 1就能判断出这个节点有没有右孩子
  30. ++child;
  31. if (temp < data[child]) // 如果孩子中有比爹大的, 则孩子当爹.
  32. data[i] = data[child];
  33. else // 俩孩子都没有爹大
  34. break;
  35. }
  36. data[i] = temp; // 下滤操作完成, 将temp填入合适的位置
  37. }
  38. /*********************************************************************************************************************************/

8. 桶式排序
算法步骤描述:对于一个由小于M的正整数组成的数组data,使用一个大小为M的数组count, 将其全部初始化为0, 然后遍历数组data,将data中每个元素数值Ai作为下标并将count数组对应下标的元素加1, count[Ai] += 1; 最后遍历count数组, 将元素不为0的下标值依次放入data数组中。以此完成排序。
桶式排序针对一些元素都是小整数的数组特别有效。
C实现:
  1. /*********************************************************************************************************************************/
  2. // 桶式排序
  3. // 桶式排序的要求比较苛刻, 它要求输入的数据必须只由小于max的正整数组成
  4. // 桶式排序的原理很简单, 而且时间复杂度是线性的, 但是它对空间量需求比其它的排序算法要高的多.
  5. // 桶式排序的平均时间复杂度是O(N)
  6. void BucketSort(int data[], int length, int max)
  7. {
  8. int* temp = (int* )malloc(sizeof(int) * max);
  9. if (temp == NULL)
  10. {
  11. cout << "内存空间不足" << endl;
  12. return ;
  13. }
  14. // 初始化temp数组
  15. for (int i = 0; i < max; ++i)
  16. temp[i] = 0;
  17. // 统计data数组中的元素
  18. for (int i = 0; i < length; ++i)
  19. temp[data[i]] += 1; // +=是考虑到可能会有重复的元素
  20. for (int i = 0, j = 0; i < max; ++i)
  21. while (temp[i]-- != 0)
  22. {
  23. data[j++] = i;
  24. }
  25. // 释放占用的空间
  26. if (temp != NULL)
  27. free(temp);
  28. }
  29. /*********************************************************************************************************************************/

各个排序算法的性能表
排序算法最好时间平均时间最坏时间辅助存储稳定性备注简单选择排序o(n2)o(n2)o(n2)o(1)不稳定n小时较好直接插入排序o(n)o(n2)o(n2)o(1)稳定基本有序时较好冒泡排序o(n)(优化后)o(n2)o(n2)o(1)稳定n小时较好, 基本有序时较好希尔排序o(n)o(nlogn)o(ns)(1<s<2)o(1)不稳定希尔排序在使用不同的增量序列进行排序时, 其时间复杂度差别较大
s对于选择的不同的增量序列其最坏时间不尽相同快速排序o(nlogn)o(nlogn)o(n2)o(logn)(因为递归的原因)不稳定使用三数中值分割法和小数组使用插入排序对快速排序进行优化, 
使得快速排序得到很大的优化
待排数组越无序越好堆排序o(nlogn)o(nlogn)o(nlogn)o(1)不稳定 归并排序o(nlogn)o(nlogn)o(nlogn)o(n) (需要一个辅助数组)稳定 

注: 博客中所用图片均为网络获取, 侵通删!
参考文献:Java程序员面试笔试宝典


// 下面的这些可以忽略
各种排序的时间复杂度总结(内部排序)
排序算法平均情况下时间复杂度最坏情况下时间复杂度备注 插入排序O(N2)O(N2)  希尔排序Hibbard增量序列O(N5/4)O(N3/2)希尔排序在使用不同的增量序列进行排序时, 其时间复杂度差别较大 Sedgewick增量序列O(N7/6)O(N4/3) 堆排序O(NlogN)O(NlogN)使用二叉堆, 先构建Max二叉堆, 然后使用DeleteMin方法每次将最大的数从队列中删除然后放到数组的尾部, 当执行N次后排序完成.  归并排序O(NlogN)O(NlogN)递归算法的优秀运用 快速排序O(NlogN)O(N2)
使用三数中值分割法和小数组使用插入排序对快速排序进行优化, 
使得快速排序得到很大的优化 桶式排序O(N)-该算法使用限制较多, 但是在限制之下, 此算法可以以线性时间进行排序工作 
外部排序的常用方法
  • 二路合并, K路合并
       通过使用归并排序的Merge操作进行排序, 先将N个元素的外部序列分为K = N/M个子序列, 然后依次对子序列进行排序, 然后两两合并, 然后再次两两合并直到剩下一个序列. 就完成了最终的排序. 在该算法需要使用较多的外部空间(2K个外部空间). 总共需要进行┌log(N/M)┐.
       K路合并中在合并时会有K个顺串的K个头元素进行比较的问题, 可以使用优先队列来提高比较的速度.
  • 替换选择
       具体过程详见书上第192页的描述. 
       替换选择方法进行外部排序主要是减少了顺串的数量, 减少了趟数.

找出第k小的元素问题的解决方法
  • 插入排序可以找出第k小的元素
  • 使用二叉堆实现的优先队列执行k此DeleteMin操作.
  • 基于快速排序的快速选择算法
3 0
原创粉丝点击