[关闭]
@XQF 2016-12-18T22:11:47.000000Z 字数 6100 阅读 1292

排序大作战

LeetCode


选择排序(SelectSort)

  1. public class SelectSort {
  2. /**
  3. * 从代码中分析选择排序就是选择一个数作为基准数然后与后面的数进行最小值或最大值的比较。
  4. * 至于稳定性,一定是不稳定的。因为相等大小的数在比较时会蜜汁交换位置,
  5. * 时间O(n^2)。最好情况是合乎条件的有序数组,不用交换。最坏就是合乎条件的逆序数组。每次都要交换。
  6. */
  7. public void sort(int[] nums) {
  8. for (int i = 0; i < nums.length - 1; i++) {
  9. int min = nums[i];
  10. for (int j = i + 1; j < nums.length; j++) {
  11. if (min > nums[j]) {
  12. int temp = min;
  13. min = nums[j];
  14. nums[j] = temp;
  15. }
  16. }
  17. nums[i] = min;
  18. }
  19. }
  20. public static void main(String[] args) {
  21. int[] nums = {8, 2, 5, 6, 8, 2};
  22. SelectSort selectSort = new SelectSort();
  23. selectSort.sort(nums);
  24. System.out.println(Arrays.toString(nums));
  25. }
  26. }

测试结果:

[2, 2, 5, 6, 8, 8]

插入排序(InsertSort)

  1. public class InsertSort {
  2. /**
  3. * 插入排序就是结果集不断的满足条件增大。减少使用空间的话就使用原数组。但是也可以新开一个数组专门来放结果。虽然仍然要移动,但是好理解
  4. * 这里很有意思的利用了数组的特性,从尾部往前,有意思。用了个while循环进行移动,简练。
  5. * 把当前的数当作一个基准数,只要是前面有比它大的,统统后移,为基准数归位腾出位置。
  6. */
  7. public void sort(int[] nums) {
  8. for (int i = 1; i < nums.length; i++) {
  9. int temp = nums[i];//寄存nums[i]的值,这个时候就可以改变nums[i]的值了
  10. int j = i;//使用另一个指针方便描述
  11. if (nums[j - 1] > temp) {//如果a[j]的前一个数比a[j]大
  12. while (j >= 1 && nums[j - 1] > temp) {//a[j]前面的数一直比a[j]大,就一直向前寻找,同时移动数字。
  13. nums[j] = nums[j - 1];
  14. j--;
  15. }
  16. }
  17. nums[j] = temp;//终于找到一个。此时a[j]中的数已经移到a[j+1]中,于是归位
  18. }
  19. }
  20. public static void main(String[] args) {
  21. int[] nums = {8, 2, 5, 6, 8, 2};
  22. InsertSort insertSort = new InsertSort();
  23. insertSort.sort(nums);
  24. System.out.println(Arrays.toString(nums));
  25. }
  26. }

测试结果:

[2, 2, 5, 6, 8, 8]

冒泡排序(BubbleSort)

  1. public class BubbleSort {
  2. /**
  3. *原本是最熟悉的,结果没写出来,悲哀,。,。大三了连个冒泡都费时间。原理是理解了得,相邻两个数交换下沉。从尾部开始排好顺序的。
  4. *只是每次都是从数组起始开始,我一直以为是当前数组元素开始
  5. */
  6. public void sort(int[] nums) {
  7. for (int i = 0; i < nums.length - 1; i++) {
  8. for (int j = 0; j < nums.length - i - 1; j++) {
  9. if (nums[j] > nums[j + 1]) {
  10. int temp = nums[j];
  11. nums[j] = nums[j + 1];
  12. nums[j + 1] = temp;
  13. }
  14. }
  15. }
  16. }
  17. public static void main(String[] args) {
  18. int[] nums = {8, 2, 5, 6, 8, 2};
  19. BubbleSort bubbleSort = new BubbleSort();
  20. bubbleSort.sort(nums);
  21. System.out.println(Arrays.toString(nums));
  22. }
  23. }

测试结果:

[2, 2, 5, 6, 8, 8]

错误写法:

  1. public void sort(int[] nums) {
  2. for (int i = 0; i < nums.length - 1; i++) {
  3. for (int j = i; j < nums.length - i - 1; j++) {//这个错误真是不好找,一开始就理解错误也是没有办法
  4. if (nums[j] > nums[j + 1]) {
  5. int temp = nums[j];
  6. nums[j] = nums[j + 1];
  7. nums[j + 1] = temp;
  8. }
  9. }
  10. }
  11. }

关于冒泡排序稳定性的思考
假设有数组[8,3,10,5,8,4],一趟排序后10肯定是在最后[x,x,x,x,x,10],第二趟排序一定会出现[x,x,x,8,8,10]这个数组的状态,这个时刻的指针指向的是第一个8.倘若交换的条件为nums[j]>nums[j+1]则第一个8和第二个8不会发生交换,跳出内循环。倘若条件为nums[j]>=nums[j+1]则第一个和第二个8发生位置交换,因此这种情况下他们的相对位置是发生了改变的,但是,。,。我也解释不了为什么说是不稳定。难道大家都倾向于使用第二种交换条件?一定是我太low了。

桶排序(BucketSort)

  1. public class BucketSort {
  2. public void sort(int[] nums) {
  3. int[] array = new int[1000];//桶的数量
  4. for (int i = 0; i < nums.length; i++) {
  5. array[nums[i]]++;//相同数字
  6. }
  7. for (int i = 0; i < array.length; i++) {
  8. for(int j=0;j<array[i];j++){
  9. System.out.print(i+" ");
  10. }
  11. }
  12. }
  13. public static void main(String[] args) {
  14. int[] nums = {8, 2, 5, 6, 8, 2};
  15. BucketSort bucketSort = new BucketSort();
  16. bucketSort.sort(nums);
  17. }
  18. }

测试结果:

[2, 2, 5, 6, 8, 8]

快速排序(QiuckSort)

  1. public class QuickSort {
  2. public void sort(int[] nums, int left, int right) {
  3. int i = left;
  4. int j = right;
  5. //递归调用肯定有一个结束条件,而这个结束条件就是在内部判断开始前判断left与right.最后结束的时候子数组大小为1
  6. if (left >= right) {
  7. return;
  8. }
  9. int temp = nums[left];//设置基准数,与后面两句构成三变量交换,6666
  10. //退出这个循环的时候,哨兵相遇
  11. while (i != j) {
  12. //一定是右边的哨兵先走,。,。,寻找到比基准数小的数停下退出
  13. while (nums[j] >= temp && i < j) {
  14. j--;
  15. }
  16. //寻找比基准数大的数停下退出
  17. while (nums[i] <= temp && i < j) {
  18. i++;
  19. }
  20. //交换各自停下的当前数
  21. if (i < j) {
  22. int t = nums[i];
  23. nums[i] = nums[j];
  24. nums[j] = t;
  25. }
  26. }
  27. //哨兵相遇之后就将基准数归位
  28. nums[left] = nums[i];
  29. nums[i] = temp;//到这一步基准数就已经归位了
  30. //一轮过后就将大数组划分为两个小数组,一边所有的数比刚刚的基准数大,一边比刚刚的基准数小。
  31. //接下来就是分治对两个小数组执行同样的操作
  32. sort(nums, left, i - 1);
  33. sort(nums, i + 1, right);
  34. return;
  35. }
  36. public static void main(String[] args) {
  37. int[] nums = {8, 2, 5, 6, 8, 2};
  38. QuickSort quickSort = new QuickSort();
  39. quickSort.sort(nums, 0, nums.length - 1);
  40. System.out.println(Arrays.toString(nums));
  41. }
  42. }

测试结果:

[2, 2, 5, 6, 8, 8]

关于快排稳定性的思考
假如有一个数组[3,8,4,8,5,10],显然会有一个时刻第一个8会与5互换位置,此时的相对位置发生了改变

希尔排序(ShellSort)

希尔排序的原理:先将待排序的数组元素分成多个子序列,使得每个子序列的元素个数相对较少,然后对各个子序列分别进行直接插入排序,待整个待排序列“基本有序后”,最后再对所有的元素进行一次插入排序。

  1. public class ShellSort {
  2. public void sort(int[] nums) {
  3. int length = nums.length;
  4. //确定步长
  5. for (int gap = length / 2; gap > 0; gap /= 2) {
  6. //确定了步长也就确定了序列,接下来对序列进行插入排序
  7. for (int i = gap; i < length; i++) {
  8. //根据gap抓住突破口,把这个数与序列中其他数进行比较,同样是采用从尾部入手的办法
  9. int temp = nums[i];
  10. int j;
  11. //每隔gap步长个数才是序列中的元素,这里用一个while循环更加简练
  12. for (j = i - gap; j >= 0; j -= gap) {
  13. //如果从序列尾部开始的数比temp大,则当前数字后移,为temp中的数腾出位置
  14. if (temp < nums[j]) {
  15. //后移gap位才是序列中的数的位置
  16. nums[j + gap] = nums[j];
  17. } else {
  18. break;
  19. }
  20. }
  21. //已经找到了要插入的位置,j的值表示,j位置上的数比temp中的数小,因此temp中的数应该放在j的后gap位上
  22. nums[gap + j] = temp;
  23. }
  24. }
  25. }
  26. public static void main(String[] args) {
  27. int[] nums = {8, 2, 5, 6, 8, 2};
  28. ShellSort shellSort = new ShellSort();
  29. shellSort.sort(nums);
  30. System.out.println(Arrays.toString(nums));
  31. }
  32. }

归并排序(MergeSort)

  1. /**
  2. * Created by XQF on 2016/11/16.
  3. */
  4. public class MergeSort {
  5. private static int[] a;
  6. // 原地归并核心代码,使用辅助数组进行暂存,当然有不使用辅助就可以实现的,在书上有讲,。,没时间对吧。呵呵
  7. public static void merge(int[] nums, int left, int medium, int right) {
  8. int i = left;
  9. int j = medium + 1;
  10. for (int k = left; k <= right; k++) {// k不一定为0,这有可能是原数组中的一个子数组。因此使用直接复制数组的方法在这里不合适
  11. a[k] = nums[k];
  12. }
  13. for (int k = left; k <= right; k++) {
  14. if (i > medium) {// i本身是小于medium,但是现在已经比medium大了,说明右半部分已经消耗完了,插入左半部分
  15. nums[k] = a[j++];
  16. } else if (j > right) {// 同理,j本身应该是小于right的,但是现在已经比right大了,说明左半部分已经消耗完了,插入右半部分
  17. nums[k] = a[i++];
  18. } else if (a[i] > a[j]) {// 如果右半部分的当前值比左半部分的当前值大,则插入左半部分当前值
  19. nums[k] = a[j++];
  20. } else {
  21. nums[k] = a[i++];// 如果右半部分比左半部分小或者等,则插入右半部分
  22. }
  23. }
  24. // 一个循环结束,归并就结束了,归并就是把两个排好序的子数组合并成一个大数组。
  25. // 一种情况是俩个数组的大小分明
  26. // 一种情况是你出一张我出一张,比分交替上升
  27. }
  28. // 递归回溯的三大步
  29. // 结束条件
  30. // 选择
  31. // 限制(本例没有限制,只有一部分操作)
  32. //--------------自顶向下,理解是从整个数组入手,然后层层分割,由底往上层层归并。
  33. public static void sort(int[] nums, int left, int right) {
  34. if (right <= left) {// 结束条件,必须要右大于左,否则就说明已经排序结束了
  35. return;
  36. }
  37. int medium = (left + right) / 2;
  38. sort(nums, left, medium);// 选择1,对左半边进行排序
  39. sort(nums, medium + 1, right);// 选择2,对右半边进行排序
  40. merge(nums, left, medium, right);// 操作,归并
  41. //为什么这里就要使用medium这个参数,不使用的话,只知道数组的两头,理论上可以实现归并,应该是很困难,我很懒不用解释了
  42. }
  43. public static void main(String[] args) {
  44. int[] nums = new int[] { 1, 5, 3, 2, 8, 0 };
  45. a = new int[nums.length];
  46. sort(nums, 0, nums.length - 1);
  47. for (int i : nums) {
  48. System.out.print(" " + i);
  49. }
  50. }
  51. }

堆排序(HeapSort)

原理:先来建树,堆是完全二叉树,因此堆又称作优先队列。然后从最后一个非叶结点逐个扫描所有结点。根据需要进行向下调整。

添加新批注
在作者公开此批注前,只有你和作者可见。
回复批注