排序是将无需的记录序列调整为有序记录序列的一种操作。
包括:冒泡排序,选择排序,堆排序,插入排序,希儿排序,快速排序,归并排序等。
每次进行相邻两个元素的比较,如果为逆序时即进行交换,直到没有反序的数据元素为止。
排序过程:
设想被排序的数组R[1..N]垂直竖立,将每个数据元素看作有重量的气泡,根据轻气泡不能在重气泡之下的原则,从下往上扫描数组R,凡扫描到违反本原则的轻气泡,就使其向上"漂浮",如此反复进行,直至最后任何两个气泡都是轻者在上,重者在下为止。
示例:对36 23 87 16 11 进行冒泡排序的过程:
初始时:36 23 87 16 11
第一趟:23 36 16 11 87
第二趟:23 16 11 36 87
第三趟:16 11 23 36 87
第四趟:11 16 23 36 87
代码如下所示
1. /** 2. * 冒泡排序算法 3. * 4. * @author stecai 5. */ 6. public class BubbleSort { 7. /** 8. * 排序算法的实现,对数组中指定的元素进行排序 9. * 10. * @param arr 待排序的数组 11. * @param from 待排序的数组 12. * @param end 排到哪里 13. */ 14. public static void sort(Integer[] arr) { 15. if (arr == null || arr.length < 2) { 16. return; 17. } 18. 19. // 需arr.length - 1轮比较 20. for (int i = 0, iend = arr.length - 1; i < iend; i++) { 21. // 每轮循环中从第一个元素开始向后比较起泡 22. for (int j = 0, jEnd = iend - i; j < jEnd; j++) { 23. // 按照一种规则(后面元素不能小于前面元素)排序 24. if ((arr[j].compareTo(arr[j + 1])) > 0) { 25. // 交换数组中的两个元素的位置 26. swap(arr, j, j + 1); 27. } 28. } 29. } 30. } 31. 32. /** 33. * 交换数组中的两个元素的位置 34. * 35. * @param arr 待交换的数组 36. * @param i 第一个元素 37. * @param j 第二个元素 38. */ 39. private static void swap(Integer[] arr, int i, int j) { 40. Integer tmp = arr[i]; 41. arr[i] = arr[j]; 42. arr[j] = tmp; 43. } 44. } |
基本思想:
每一趟在n-i+1(i=1,2,3,...,n-1)个记录中选取关键字最小的记录作为有序序列中的第i个记录。
排序过程:
将整个记录序列划分为有序区域和无序区域,有序区域位于最左端,无序区域位于右端,初始状态有序区域为空,无序区域含有待排序的所有n个记录。
设置一个整型变量index,用于记录在一趟的比较过程中,当前关键字值最小的记录位置。开始将它设定为当前无序区域的第一个位置,即假设这个位置的关键字最小,然后用它与无序区域中其他记录进行比较,若发现有比它的关键字还小的记录,就将index改为这个新的最小记录位置,随后再用a[index].key 与后面的记录进行比较,并根据比较结果,随时修改index的值,一趟结束后index中保留的就是本趟选择的关键字最小的记录位置。
将index位置的记录交换到无序区域的第一个位置,使得有序区域扩展了一个记录,而无序区域减少了一个记录。
不断重复 (2)、(3),直到无序区域剩下一个记录为止。此时所有的记录已经按关键字从小到大的顺序排列就位。
示例:对36 23 87 16 11 进行选择排序的过程:
初始时:36 23 87 16 11
第一趟:11 [36 23 87 16]
第二趟:11 16 [36 23 87]
第三趟:11 16 23 [36 87]
第四趟:11 16 23 36 [87]
结 果 :11 16 23 36 87
代码如下所示
1. /** 2. * 选择排序算法 3. * 4. * @author stecai 5. */ 6. public class SelectSort { 7. /** 8. * 对数组中指定的元素进行排序 9. * 10. * @param arr 待排序的数组 11. */ 12. public static void sort(Integer[] arr) { 13. // 最小索引 14. int minIndex; 15. 16. // 循环整个数组 17. for (int i = 0; i < arr.length; i++) { 18. // 假设每轮第一个元素为最小元素 19. minIndex = i; 20. 21. // 从假设的最小元素的下一元素开始循环 22. for (int j = i + 1; j < arr.length; j++) { 23. // 如果发现有比当前更小元素,则记下该元素的索引于minIndex中 24. if ((arr[j].compareTo(arr[i])) < 0) { 25. minIndex = j; 26. } 27. } 28. 29. // 与每轮的第一个元素交换 30. if (i != minIndex) { 31. swap(arr, i, minIndex); 32. } 33. } 34. } 35. 36. /** 37. * 交换数组中的两个元素的位置 38. * 39. * @param arr 待交换的数组 40. * @param i 第一个元素 41. * @param j 第二个元素 42. */ 43. private static void swap(Integer[] arr, int x, int y) { 44. int temp = arr[x]; 45. arr[x] = arr[y]; 46. arr[y] = temp; 47. } 48. } |
基本思想:
将堆看成是一棵以k1为根的完全二叉树,则这棵完全二叉树中的每个非终端结点的值均不大于(或不小于)其左、右孩子结点的值。由此可以看出,若一棵完全二叉树是堆,则根结点一定是这n个结点中的最小者或最大者。
堆的定义
N个元素的序列K1,K2,K3,...,Kn.称为堆,当且仅当该序列满足特性:
Ki≤K2i Ki ≤K2i+1(1≤ I≤ [N/2])
排序过程:
首先将待排序的记录序列构造一个堆,此时,选出了堆中所有记录的最小者或最大者,然后将它从堆中移走,并将剩余的记录再调整成堆,这样又找出了次小(或次大)的记录,以此类推,直到堆中只有一个记录为止,每个记录出堆的顺序就是一个有序序列。
代码如下所示
1. /** 2. * 堆排序算法 3. * 4. * @author stecai 5. */ 6. public class HeapSort { 7. /** 8. * 对数组中指定的元素进行排序 9. * 10. * @param arr 待排序的数组 11. * @param from 待排序的数组 12. * @param end 排到哪里 13. */ 14. public static void sort(Integer[] arr, int from, int end) { 15. // 创建初始堆 16. initialHeap(arr, from, end); 17. 18. /* 19. * 对初始堆进行循环,每轮循环后丢弃最后一个叶子节点,再看作一个新的堆 20. */ 21. for (int i = end - from + 1; i >= 2; i--) { 22. // 根节点与最后一个叶子节点交换位置,即数组中的第一个元素与最后一个元素互换 23. swap(arr, from, i - 1); 24. 25. // 交换后需要重新调整堆 26. adjustNote(arr, 1, i - 1); 27. } 28. } 29. 30. /** 31. * 初始化堆 32. * 33. * @param arr 排序数组 34. * @param from 从哪里开始排序 35. * @param end 排到哪里 36. */ 37. private static void initialHeap(Integer[] arr, int from, int end) { 38. // 对所有的非叶子节点(半数)进行循环,且从最一个非叶子节点开始 39. for (int i = (end - from + 1) / 2; i >= 1; i--) { 40. adjustNote(arr, i, end - from + 1); 41. } 42. } 43. 44. /** 45. * 从父、左右子节点三个节点中选择一个最大节点与父节点转换 46. * 47. * @param arr 待排序数组 48. * @param parentNodeIndex 要调整的节点,与它的子节点一起进行调整 49. * @param len 树的节点数 50. */ 51. private static void adjustNote(Integer[] arr, int parentNodeIndex, int len) { 52. int minNodeIndex = parentNodeIndex; 53. 54. // 如果有左子树,i * 2为左子节点索引 55. if (parentNodeIndex * 2 <= len) { 56. // 如果父节点小于左子树时 57. if ((arr[parentNodeIndex - 1].compareTo(arr[parentNodeIndex * 2 - 1])) < 0) { 58. // 记录最大索引为左子节点索引 59. minNodeIndex = parentNodeIndex * 2; 60. } 61. 62. // 再进一步断判是否有右子树 63. if (parentNodeIndex * 2 + 1 <= len) { 64. // 如果右子树比最大节点更大 65. if ((arr[minNodeIndex - 1].compareTo(arr[parentNodeIndex * 2])) < 0) { 66. // 记录最大索引为右子节点索引 67. minNodeIndex = parentNodeIndex * 2 + 1; 68. } 69. } 70. } 71. 72. /* 73. * 如果在父节点、左、右子节点三都中,最大节点不是父节点时需交换,把最大的与父节点交换,继续创建大顶堆 74. */ 75. if (minNodeIndex != parentNodeIndex) { 76. swap(arr, parentNodeIndex - 1, minNodeIndex - 1); 77. 78. // 交换后可能需要重建堆,原父节点可能需要继续下沉.是否有子节点,只需判断是否有左子树即可知道 79. if (minNodeIndex * 2 <= len) { 80. adjustNote(arr, minNodeIndex, len); 81. } 82. } 83. } 84. 85. /** 86. * 交换数组中的两个元素的位置 87. * 88. * @param arr 待交换的数组 89. * @param i 第一个元素 90. * @param j 第二个元素 91. */ 92. public static void swap(Integer[] arr, int x, int y) { 93. int temp = arr[x]; 94. arr[x] = arr[y]; 95. arr[y] = temp; 96. } 97. } |
基本思想:
每次将一个待排序的数据元素,插入到前面已经排好序的数列中的适当位置,使数列依然有序;直到待排序数据元素全部插入完为止。
排序过程:
将待排序表看做是左、右两部分,其中左边为有序区,右边为无序区,整个排序过程就是将右边无序区中的记录依次按关键字大小逐个插入到左边有序区中,以构成新的有序区,直到全部记录都排好序。
示例:对36 23 87 16 11 进行插入排序的过程:
初始:36 23 87 16 11
一趟:23 36 87 16 11
二趟:23 36 87 16 11
三趟:16 23 36 87 11
四趟:11 16 23 36 87
代码如下所示
1. /** 2. * 插入排序算法 3. * 4. * @author stecai 5. */ 6. public class InsertSort { 7. /** 8. * 对数组中指定的元素进行排序 9. * 10. * @param arr 待排序的数组 11. * @param from 待排序的数组 12. * @param end 排到哪里 13. */ 14. public static void sort(Integer[] arr, int from, int end) { 15. // 从待排序数组断的第二个元素开始循环,到最后一个元素(包括)止 16. for (int i = from + 1; i <= end; i++) { 17. // 对有序数组进行循环,且从有序数组最第一个元素开始向后循环 找到第一个大于待插入的元素 18. for (int j = 0; j < i; j++) { 19. // 待插入到有序数组的元素 20. Integer insertedElem = arr[i]; 21. 22. // 从有序数组中最一个元素开始查找第一个大于待插入的元素 23. if ((arr[j].compareTo(insertedElem)) > 0) { 24. // 找到插入点后,从插入点开始向后所有元素后移一位 25. 26. move(arr, j, i - 1); 27. // 将待排序元素插入到有序数组中 28. arr[j] = insertedElem; 29. break; 30. } 31. } 32. } 33. } 34. 35. /** 36. * 数组元素后移 37. * 38. * @param arr 待移动的数组 39. * @param startIndex 从哪个开始 40. * @param endIndex 到哪个元素止 41. */ 42. private static void move(Integer[] arr, int startIndex, int endIndex) { 43. for (int i = endIndex; i >= startIndex; i--) { 44. arr[i + 1] = arr[i]; 45. } 46. } 47. } |
基本思想:
希儿排序又称为缩小增量排序,是将待排序的记录划分成几组,从而减少参与直接插入排序的数据量,当经过几次分组排序后,记录的排列已经基本有序,这个时候再对所有的记录实施直接插入排序。
排序过程:
假设带排序的记录为n个,先取整数step < n作为步长,通常选取step = n / 2 (n / 2表示不大于n / 2的最大整数),将所有距离为step的记录构成一组,从而将整个待排序记录序列分割称为step个子序列。反复比较相距step的两数,当两个数不等时,将小的交换到前面,直到所有相距step的两数符合从小到大的顺序,倍减步长,重复上面分组,直到步长为1,即将所有记录放在一组进行一次直接插入排序,最终将所有记录重新排列成按关键字有序的序列。
示例:对4 1 10 2 16 9 3 14进行希儿排序的过程:
因为关键字有8个,第一次步长step = 8 / 2 = 4
初始:4 1 10 2 16 9 3 14
一趟:4 1 3 2 16 9 10 14 (只有10,3这一组不符合,按升序排列)
第二次步长step = 4 / 2 = 2
初始:4 1 3 2 16 9 10 14
二趟:3 1 4 2 10 9 16 14 (2组都按升序排列)
第三次步长step = 2 / 2 = 1
初始:3 1 4 2 10 9 16 14
三趟:1 2 3 4 9 10 14 16 (按升序排列)
代码如下所示
1. /** 2. * 希尔排序算法 3. * 4. * @author stecai 5. */ 6. public class ShellSort { 7. /** 8. * 对数组中指定的元素进行排序 9. * 10. * @param arr 待排序的数组 11. */ 12. public static void sort(Integer[] arr) { 13. // 给size指定数组的长度 14. int size = arr.length; 15. 16. // step是给定的增量值当增量为一时,所有数据放在同一组中 17. for (int step = size / 2; step >= 1; step /= 2) { 18. // 从step开始,循环扫描至末尾 19. for (int i = step; i < size; i++) { 20. int k; 21. int temp = arr[i]; 22. 23. // 循环判断 24. for (k = i - step; k >= 0 && arr[k] > temp; k -= step) { 25. arr[k + step] = arr[k]; 26. } 27. 28. arr[k + step] = temp; 29. } 30. } 31. } 32. } |
基本思想:
通过一趟排序将要排序的数据分割成独立的两部分,其中一部分的所有数据都比另外一部分的所有数据都要小,然后再按此方法对这两部分数据分别进行快速排序,整个排序过程可以递归进行,以此达到整个数据变成有序序列。
θ(n)。
排序过程:
设要排序的数组是A[0]……A[N-1]。
以第一个数组元素作为关键数据,赋值给key,即key=A[0];
从j开始向前搜索,即由后开始向前搜索(j--),找到第一个小于key的值A[j],将A[j]和A[i]互换;
从i开始向后搜索,即由前开始向后搜索(i++),找到第一个大于key的A[i],将A[i]和A[j]互换;
重复第3、4步,直到i=j; (3,4步中,没找到符合条件的值,即3中A[j]不小于key,4中A[i]不大于key的时候改变j、i的值,使得j=j-1,i=i+1,直至找到为止。找到符合条件的值,进行交换的时候i, j指针位置不变。另外,i==j这一过程一定正好是i+或j-完成的时候,此时令循环结束)。
示例:对36 23 87 16 11 进行快速排序的过程:
初始:36 23 87 16 11 (i = 0, j = 4, key = 36)
一趟:11 23 87 16 11 (i = 0, j = 4, j位置覆盖i位置)
二趟:11 23 87 16 87 (i = 2, j = 4, i++, i位置是比key大,i覆盖j位置)
三趟:11 23 1616 87 (i = 2, j = 3, j--, j位置比key小,j覆盖i位置)
四趟:11 23 16 3687 (i = 3, j = 3, i++, i == j, key覆盖i位置)
递归:左边 11 23 16
右边 87
代码如下所示
1. /** 2. * 快速排序算法 3. * 4. * @author stecai 5. */ 6. public class QuickSort { 7. /** 8. * 待排序数组 9. * 10. * @param arr 待排序的数组 11. * @param low 低指针 12. * @param high 高指针 13. */ 14. public static void sort(Integer[] arr, int low, int high) { 15. int i; 16. 17. // 如果low小于high则循环 18. if (low < high) { 19. // 给定中间值 20. i = partition(arr, low, high); 21. 22. // 递归左半部分 23. sort(arr, low, i - 1); 24. 25. // 递归右半部分 26. sort(arr, i + 1, high); 27. } 28. } 29. 30. /** 31. * 快速排序,给定中间值 32. * 33. * @param arr 待排序的数组 34. * @param low 低指针 35. * @param high 高指针 36. * @return 调整后中枢位置 37. */ 38. private static int partition(Integer[] arr, int low, int high) { 39. // 取最小的值作为比较开始值 40. int pivot = arr[low]; 41. 42. while (low < high) { 43. while (low < high && arr[high] >= pivot) { 44. // 如果高位大于pivot则high-- 45. high--; 46. } 47. 48. // 跳出循环后直接将a[high]的值赋给a[low],并且low自加 49. if (low < high) { 50. arr[low] = arr[high]; 51. low++; 52. } 53. 54. while (low < high && arr[low] < pivot) { 55. // 往反方向开始扫描,如果a[low]小于 pivot则low++ 56. low++; 57. } 58. 59. // 跳出循环后直接将a[low]的值赋给a[high],并且high自减 60. if (low < high) { 61. arr[high] = arr[low]; 62. high--; 63. } 64. } 65. 66. arr[high] = pivot; 67. 68. // 返回中间值 69. return low; 70. } 71. } |
基本思想:
归并排序是建立在归并操作上的一种有效的排序算法,该算法是采用分治法(Divide and Conquer)的一个非常典型的应用。将已有序的子序列合并,得到完全有序的序列;即先使每个子序列有序,再使子序列段间有序。若将两个有序表合并成一个有序表,称为二路归并。
排序过程:
比较a[i]和a[j]的大小,若a[i]≤a[j],则将第一个有序表中的元素a[i]复制到r[k]中,并令i和k分别加上1;否则将第二个有序表中的元素a[j]复制到r[k]中,并令j和k分别加上1,如此循环下去,直到其中一个有序表取完,然后再将另一个有序表中剩余的元素复制到r中从下标k到下标t的单元。归并排序的算法我们通常用递归实现,先把待排序区间[s,t]以中点二分,接着把左边子区间排序,再把右边子区间排序,最后把左区间和右区间用一次归并操作合并成有序的区间[s,t]。
示例:对4 1 10 2 16 9 3 14 进行插入归并的过程:
初始:4 1 10 2 16 9 3 14
一趟:[1, 4], [2, 10], [9, 16], [3, 14]
二趟:[1, 2, 4, 10], [3, 9, 14, 16]
三趟:[1, 2, 3, 4, 9, 10, 14, 16]
代码如下所示
1. /** 2. * 归并排序算法 3. * 4. * @author stecai 5. */ 6. public class MergeSort { 7. /** 8. * 将两个(或两个以上)有序表合并成一个新的有序表 9. * 10. * @param arr 待排序数组 11. * @param low 低指针 12. * @param high 高指针 13. * @return 输出有序数组 14. */ 15. public static int[] sort(int[] arr, int low, int high) { 16. int mid = (low + high) / 2; 17. 18. if (low < high) { 19. // 左边 20. sort(arr, low, mid); 21. // 右边 22. sort(arr, mid + 1, high); 23. // 左右归并 24. merge(arr, low, mid, high); 25. } 26. 27. return arr; 28. } 29. 30. /** 31. * 数组合并,合并过程中对两部分数组进行排序 32. * 33. * @param arr 待排序数组 34. * @param low 低指针 35. * @param mid 中间值 36. * @param high 高指针 37. */ 38. private static void merge(int[] arr, int low, int mid, int high) { 39. int[] temp = new int[high - low + 1]; 40. // 左指针 41. int i = low; 42. // 右指针 43. int j = mid + 1; 44. int k = 0; 45. 46. // 把较小的数先移到新数组中 47. while (i <= mid && j <= high) { 48. if (arr[i] < arr[j]) { 49. temp[k++] = arr[i++]; 50. } else { 51. temp[k++] = arr[j++]; 52. } 53. } 54. 55. // 把左边剩余的数移入数组 56. while (i <= mid) { 57. temp[k++] = arr[i++]; 58. } 59. 60. // 把右边边剩余的数移入数组 61. while (j <= high) { 62. temp[k++] = arr[j++]; 63. } 64. 65. // 把新数组中的数覆盖nums数组 66. for (int k2 = 0; k2 < temp.length; k2++) { 67. arr[k2 + low] = temp[k2]; 68. } 69. } 70. } |
排序法 |
平均时间 |
最差情形 |
稳定度 |
额外空间 |
备注 |
冒泡 |
O(n2) |
O(n2) |
稳定 |
O(1) |
n小时较好 |
选择 |
O(n2) |
O(n2) |
不稳定 |
O(1) |
n小时较好 |
堆 |
O(nlogn) |
O(nlogn) |
不稳定 |
O(1) |
n大时较好 |
插入 |
O(n2) |
O(n2) |
稳定 |
O(1) |
大部分已排序时较好 |
希尔 |
O(nlogn) |
O(ns) 1<s<2 |
不稳定 |
O(1) |
s是所选分组 |
快速 |
O(nlogn) |
O(n2) |
不稳定 |
O(nlogn) |
n大时较好 |
归并 |
O(nlogn) |
O(nlogn) |
稳定 |
O(1) |
n大时较好 |
参考:
原文地址:http://blog.csdn.net/chndata/article/details/44855901