当前位置:   article > 正文

数据结构_排序

数据结构_排序

目录

一、排序

二、插入排序

2.1 直接插入排序

2.2 希尔排序

三、选择排序

3.1 直接选择排序

3.2 堆排序

四、交换排序

4.1 冒泡排序

4.2 快速排序

五、归并排序

六、排序算法分析

总结


一、排序

排序:就是使一串记录序列,按照其中某个或某些关键字的大小,进行递增或递减的排列操作。

稳定性:假定在待排序的记录序列中,存在多个具有相同关键字的记录,若经过排序后,这些记录的相对次序保持不变,则称这种排序算法是稳定的;否则称为不稳定。

例如,在原序列中,r[i] = r[j],且 r[i] 在 r[j] 之前,若排序后的序列中,r[i] 仍在 r[j] 之前,则排序算法稳定,反之即不稳定。


二、插入排序

2.1 直接插入排序

核心思想:第二个元素开始遍历,每次遍历将该元素与其前方元素对比,做出相应交换。

  1. public static void insertSort(int[] array) {
  2. //初始 i 下标指向第二个元素
  3. for (int i = 1; i < array.length; i++) {
  4. //将 i下标的值放入 temp 中
  5. int temp = array[i];
  6. //初始 j 下标指向 i 下标的前一位
  7. int j = i-1;
  8. for (; j >= 0; j--) {
  9. //若 j 下标的值比 temp 中的值大,则将其往后移一位
  10. if (array[j] > temp) {
  11. array[j+1] = array[j];
  12. } else {
  13. break;
  14. }
  15. }
  16. //循环结束将原本 i 的值放至 j 下标的后一位中
  17. array[j+1] = temp;
  18. }
  19. }

【总结】

时间复杂度:

        最坏情况下:O(n^2)

        最好情况下:O(n)

空间复杂度:O(1)

稳定性:稳定

适用性:待排序序列接近有序


2.2 希尔排序

希尔排序也叫缩小增量排序基本思想:选定一个整数 gap,将待排序序列分为 gap 组,所有距离为 gap 的元素为同一组,每组分别进行直接插入排序;然后缩小 gap,继续分组排序,直至 gap = 1 时,所有元素为一组,最后进行一次直接插入排序。

  1. public static void shellSort(int[] array) {
  2. int gap = array.length;
  3. //gap>1 都属于预排序
  4. while (gap > 1) {
  5. //取 gap 为全部元素的一半
  6. gap /= 2;
  7. //每组排序
  8. shell(array, gap);
  9. }
  10. }
  11. private static void shell(int[] array, int gap) {
  12. //初始 i 下标指向 gap,即每组的第二个元素
  13. for (int i = gap; i < array.length; i++) {
  14. //将 i下标的值放入 temp 中
  15. int temp = array[i];
  16. //初始 j 下标指向 i 下标的前 1gap 位
  17. //j 每次往前 1gap 位
  18. int j = i-gap;
  19. for (; j >= 0; j-=gap) {
  20. //若 j 下标的值比 temp 中的值大,则将其往后移 1gap 位
  21. if (array[j] > temp) {
  22. array[j+gap] = array[j];
  23. } else {
  24. break;
  25. }
  26. }
  27. //循环结束将原本 i 的值放至 j 下标的后 1gap 位中
  28. array[j+gap] = temp;
  29. }
  30. }

【总结】 

1、希尔排序是对直接插入排序的优化。

2、当 gap>1 时,都是预排序,是为了让序列趋于有序。

3、稳定性:不稳定


三、选择排序

3.1 直接选择排序

核心思想:遍历每个元素,每次遍历确定一个最小值令其有序。

  1. public static void selectSort(int[] array) {
  2. //遍历每个元素
  3. for (int i = 0; i < array.length; i++) {
  4. //初始化 minIndex 用于指向最小值
  5. int minIndex = i;
  6. //i 下标与后方每个元素对比
  7. for (int j = i+1; j < array.length; j++) {
  8. //确保 minIndex 指向 i 下标及其后方元素的最小值
  9. if (array[j] < array[minIndex]) {
  10. minIndex = j;
  11. }
  12. }
  13. //交换 i 下标与 minIndex 下标的值
  14. int temp = array[i];
  15. array[i] = array[minIndex];
  16. array[minIndex] = temp;
  17. }
  18. }

【总结】

时间复杂度:O(n^2)

空间复杂度:O(1)

稳定性:不稳定


3.2 堆排序

① 创建大根堆,初始化 end = array.length-1,即 end 指向最后一个结点;

② 将栈顶元素交换至 end 下标。由于大根堆中栈顶元素最大,故交换一次,end--,保证每次交换后的栈顶元素位置不变;

③ 重新向下调整为大根堆;

④ 重复 ②、③ 操作,直至排序完成。

  1. //创建大根堆
  2. private static void createHeap(int[] array) {
  3. //从最后一棵子树开始调整,依次往前,直至根结点
  4. //父亲结点 = (孩子结点-1) / 2
  5. //usedSize-1 是树中最后一个结点
  6. for (int parent = (array.length-1-1) / 2; parent >= 0; parent--) {
  7. //向下调整
  8. siftDown(array, parent,array.length);
  9. }
  10. }
  11. //堆排序
  12. public static void heapSort(int[] array) {
  13. //创建大根堆
  14. createHeap(array);
  15. int end = array.length-1;
  16. while (end > 0) {
  17. //将大根堆中栈顶元素交换至 end
  18. swap(array, 0, end);
  19. //向下调整为大根堆
  20. siftDown(array, 0, end);
  21. //保证每次调整的栈顶元素位置不变
  22. end--;
  23. }
  24. }
  25. //向下调整
  26. private static void siftDown(int[] array, int parent, int length) {
  27. //左孩子结点 = 父亲结点*2 + 1
  28. int child = parent*2 + 1;
  29. //首先保证该结点有左孩子结点
  30. while (child < length) {
  31. //再次确定该结点有右孩子结点,再进行比较
  32. //保证 child 引用指向孩子结点中最大值
  33. if ((child+1) < length && array[child] < array[child+1]) {
  34. //若右孩子的值大于左孩子,则 child 引用指向右孩子
  35. child = child + 1;
  36. }
  37. if (array[child] > array[parent]) {
  38. //若 child 比 parent 大,则交换元素
  39. swap(array, child, parent);
  40. //parent 指向 child 位置,向下调整至下方无子树
  41. parent = child;
  42. child = parent*2 + 1;
  43. } else {
  44. //子结点都比父结点小
  45. break;
  46. }
  47. }
  48. }
  49. //交换元素
  50. private static void swap(int[] array, int i, int j) {
  51. int temp = array[i];
  52. array[i] = array[j];
  53. array[j] = temp;
  54. }

【总结】 

时间复杂度:O(n*log n)

空间复杂度:O(1)

稳定性:不稳定


四、交换排序

4.1 冒泡排序

核心思想:进行 array.length-1 趟比较,每次确定一个最大值元素。

  1. public static void bubbleSort(int[] array) {
  2. //i 表示趟数
  3. for (int i = 0; i < array.length-1; i++) {
  4. //设置一个标签,检测该趟是否发生交换
  5. boolean flag = false;
  6. //j 表示该趟比较次数
  7. for (int j = 0; j < array.length-1-i; j++) {
  8. //每趟确定一个最大值
  9. if (array[j] > array[j+1]) {
  10. int temp = array[j];
  11. array[j] = array[j+1];
  12. array[j+1] = temp;
  13. //若交换,则改变标签状态
  14. flag = true;
  15. }
  16. }
  17. //若标签状态未改变,则序列已经有序
  18. if (!flag) {
  19. break;
  20. }
  21. }
  22. }

【总结】

时间复杂度:O(n^2),若加上 flag 优化,则最好可以达到 O(n)

空间复杂度:O(1)

稳定性:稳定


4.2 快速排序

1、Hoare版

核心思想: 0 下标元素为基准,保证每次排序后,基准左侧元素小于基准右侧大于基准,分别在左序列与右序列进行递归排序。

  1. public static void quickSort(int[] array) {
  2. quick(array, 0, array.length-1);
  3. }
  4. private static void quick(int[] array, int start, int end) {
  5. // start < end 时才需要排序
  6. if (start >= end) {
  7. return;
  8. }
  9. //找到基准的下标
  10. int pivot = partitionHoare(array, start, end);
  11. //左序列
  12. quick(array, start, pivot-1);
  13. //右序列
  14. quick(array, pivot+1, end);
  15. }
  16. private static int partitionHoare(int[] array, int left, int right) {
  17. //保存基准的值
  18. int temp = array[left];
  19. //保存基准的下标
  20. int index = left;
  21. while (left < right) {
  22. //在右侧找到比基准小的数
  23. while (left < right && array[right] >= temp) {
  24. right--;
  25. }
  26. //在左侧找到比基准小的数
  27. while (left < right && array[left] <= temp) {
  28. left++;
  29. }
  30. swap(array, left, right);
  31. }
  32. //保证基准左侧都比基准小,右侧都比基准大
  33. swap(array, index, left);
  34. //确定基准的位置时,left = right
  35. return left;
  36. }

2、挖坑版

核心思想: 0 下标元素为基准,0 下标处为第一个坑位,从右侧开始遍历,找到比基准小的元素放至 left 下标 (初始即 0 下标) 处;然后从左侧遍历到比基准大的放至 right 下标处,循环至 left 与 right 相遇,将基准放入最后一个坑位。

  1. public static void quickSort(int[] array) {
  2. quick(array, 0, array.length-1);
  3. }
  4. private static void quick(int[] array, int start, int end) {
  5. // start < end 时才需要排序
  6. if (start >= end) {
  7. return;
  8. }
  9. //找到基准的下标
  10. int pivot = partition(array, start, end);
  11. //左序列
  12. quick(array, start, pivot-1);
  13. //右序列
  14. quick(array, pivot+1, end);
  15. }
  16. private static int partition(int[] array, int left, int right) {
  17. //保存基准的值
  18. int temp = array[left];
  19. //保存基准的下标
  20. int index = left;
  21. while (left < right) {
  22. //在右侧找到比基准小的数
  23. while (left < right && array[right] >= temp) {
  24. right--;
  25. }
  26. //找到放置左边的坑位
  27. array[left] = array[right];
  28. //在左侧找到比基准小的数
  29. while (left < right && array[left] <= temp) {
  30. left++;
  31. }
  32. //找到放置右边的坑位
  33. array[right] = array[left];
  34. }
  35. //将基准值放入最后一个坑中
  36. array[left] = temp;
  37. //确定基准的位置时,left = right
  38. return left;
  39. }

【总结】 

时间复杂度:O(n*log n)

空间复杂度:O(log n)

稳定性:不稳定


五、归并排序

将两个有序序列合并成一个有序序列的操作,称为二路归并

核心步骤:先分解再合并

  1. public static void mergeSort(int[] array) {
  2. merge(array, 0, array.length-1);
  3. }
  4. private static void merge(int[] array, int start, int end) {
  5. // start < end 时才需要排序
  6. if (start >= end) {
  7. return;
  8. }
  9. //标记序列中间下标
  10. int mid = (start+end)/2;
  11. //左序列
  12. merge(array, start, mid);
  13. //右序列
  14. merge(array, mid+1, end);
  15. //排序, 合并
  16. mergeMethod(array, start, mid, end);
  17. }
  18. private static void mergeMethod(int[] array, int left,int mid, int right) {
  19. //指向左右序列最小值
  20. int min1 = left;
  21. int min2 = mid+1;
  22. //定义一个新的空间用于排序
  23. int[] sort = new int[right-left+1];
  24. //新空间的下标
  25. int k = 0;
  26. //保证左右序列都有元素
  27. while (min1 <= mid && min2 <= right) {
  28. //左右序列从最左侧(最小值)开始比较
  29. if (array[min1] <= array[min2]) {
  30. sort[k++] = array[min1++];
  31. } else {
  32. sort[k++] = array[min2++];
  33. }
  34. }
  35. //表示右序列中无元素,即左序列剩下元素比右序列都要大
  36. while (min1 <= mid) {
  37. sort[k++] = array[min1++];
  38. }
  39. //表示左序列中无元素,即右序列剩下元素比左序列都要大
  40. while (min2 <= right) {
  41. sort[k++] = array[min2++];
  42. }
  43. //将排序好的数组,拷贝回原数组
  44. for (int i = 0; i < sort.length; i++) {
  45. //利用 left 下标(每个序列起始下标) 保证右序列可以顺利拷贝
  46. array[i+left] = sort[i];
  47. }
  48. }

【总结】 

时间复杂度:O(n*log n)

空间复杂度:O(n)

稳定性:稳定

适用性:更多是在解决磁盘中的外排序问题


六、排序算法分析

排序方法最好平均最坏空间复杂度稳定性
冒泡排序O(n)O(n^2)O(n^2)O(1)稳定
插入排序O(n)O(n^2)O(n^2)O(1)稳定
选择排序O(n^2)O(n^2)O(n^2)O(1)不稳定
希尔排序O(n)O(n^1.3)O(n^2)O(1)不稳定
堆排序O(n*log n)O(n*log n)O(n*log n)O(1)不稳定
快速排序O(n*log n)O(n*log n)O(n^2)O(log n) ~ O(n)不稳定
归并排序O(n*log n)O(n*log n)O(n*log n)O(n)稳定

总结

1、希尔排序是对直接插入排序的优化。

2、直接选择排序效率不高,很少使用;堆排序效率高。

3、快速排序综合性能和使用场景都是比较好的。

4、归并排序更多是在解决磁盘中的外排序问题。

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/木道寻08/article/detail/856647
推荐阅读
相关标签
  

闽ICP备14008679号