• 深入浅出排序算法之归并排序


    目录

    1. 归并排序的原理

    1.1 二路归并排序执行流程

    2. 代码分析

    2.1 代码设计

    3. 性能分析

    4. 非递归版本


    1. 归并排序的原理

    “归并”一词的中文含义就是合并、并入的意思,而在数据结构中的定义是将两个或者两个以上的有序表组合成一个新的有序表。

    归并排序(Merging Sort)就是利用归并的思想实现的排序方法。它的原理是假设初始序列含有n个记录,则可以看成是n个有序的子序列,每个子序列的长度为1,然后两两归并,得到[n/2](\left \lfloor x \right \rfloor表示不小于x的最小整数)个长度为2或1的有序子序列;再两两归并,……,如此重复,直至得到一个长度为n的有序序列为止,这种排序方法成为2路归并排序。

    1.1 二路归并排序执行流程

    原始序列:49   38   65   97   76   13   27

    (1)将原始序列看成7个只含有一个关键字的子序列,显然这些子序列都是有序的。

    子序列1:49;子序列2:38;子序列3:65;子序列4:97;子序列5:76;子序列6:13;子序列7:27

    (2)两两归并,形成若干有序二元组,即49和38归并成{38   49},65和97归并成{65   97},76和13归并成{13   76},27没有归并对象,保持原样{27}。第一趟二路归并排序结束,结果如下:

    {38   49},{65   97},{13   76},{27}

    (3)再将这个序列看成若干二元组子序列

    子序列1:38   49;子序列2:65   97;子序列3:13   76;子序列4:27;

    最后一个子序列长度可能是1,也可能是2。

    (4)继续两两归并,形成若干有序四元组(同样,最后的子序列中不一定有4个关键字),即{38   49}和{65   97}归并形成{38   49   65   97},{13   76}和{27}归并形成{13   27   76}。第二趟二路归并排序结束,结果如下:

    {38   49   65   97},{13   27   76}

    (5)最后只有两个子序列了,再进行一次归并,就可完成整个二路归并排序,结果如下:

    13   27   38   49   65   76   97

      

    2. 代码分析

    大家先看看有没有思路!

     3,2,1 开始我的表演哈哈哈!

     请看图解:

    2.1 代码设计

    根据图解我们要怎么设计方法呢?

    我打算把分解功能写成一个方法,合并功能写成一个方法。具体实现如下: 

    1. public static void mergeSort1(int[] array){
    2. mergeSortDivide(array,0, array.length - 1);
    3. }
    4. //分解
    5. private static void mergeSortDivide(int[] array,int left,int right){
    6. //终止条件:left > right
    7. if(left >= right){
    8. return;
    9. }
    10. int mid = (left+right)/2;
    11. //递归左子序列
    12. mergeSortDivide(array,left,mid);
    13. //递归右子序列
    14. mergeSortDivide(array,mid+1,right);
    15. //合并
    16. merge(array,left,right,mid);
    17. }
    18. //合并
    19. private static void merge(int[] array,int start,int end,int mid){
    20. //左子序列从start开始
    21. int s1 = start;
    22. //右子序列从mid+1开始
    23. int s2 = mid + 1;
    24. //新建一个数组,作为复制数组
    25. int[] tmp = new int[end - start + 1];
    26. //k表示中间数组的元素下标
    27. int k = 0;
    28. //开始比较
    29. while(s1 <= mid && s2 <= end){
    30. if(array[s1] <= array[s2]){
    31. //将小的值赋值给tmp[k]
    32. //小伙伴们这可以思考一下:为什么不能先写array[s2] <= array[s1]?
    33. //等下看我下面的解析!
    34. tmp[k++] = array[s1++];
    35. } else{//array[s2] <= array[s1]的情况
    36. tmp[k++] = array[s2++];
    37. }
    38. }
    39. //有剩余的数组
    40. //左子序列
    41. while(s1 <= mid){
    42. tmp[k++] = array[s1++];
    43. }
    44. //右子序列
    45. while(s2 <= end){
    46. tmp[k++] = array[s2++];
    47. }
    48. //将tmp数组的值赋值给array数组
    49. for(int i = 0;i
    50. array[i+start] = tmp[i];
    51. }
    52. }

    回答问题:为什么不能先写array[s2] <= array[s1]?

    答:归并排序是稳定的。如果先写array[s2] <= array[s1],那么在s2开始的元素与s1开始的元素相等的话,例如:1<=1,那么本该在后面的1就会移到前面,导致这段代码实现的归并排序不稳定了!

    3. 性能分析

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

    4. 非递归版本

    上面的版本是递归版本,接下来是非递归版本。

    1. private static void merge(int[] array,int start,int end,int mid) {
    2. int s1 = start;
    3. //int e1 = mid;
    4. int s2 = mid+1;
    5. //int e2 = end;
    6. int[] tmp = new int[end-start+1];
    7. int k = 0;//tmp数组的下标
    8. while (s1 <= mid && s2 <= end) {
    9. if(array[s1] <= array[s2]) {
    10. tmp[k++] = array[s1++];
    11. }else {
    12. tmp[k++] = array[s2++];
    13. }
    14. }
    15. while (s1 <= mid) {
    16. tmp[k++] = array[s1++];
    17. }
    18. while (s2 <= end) {
    19. tmp[k++] = array[s2++];
    20. }
    21. for (int i = 0; i < tmp.length; i++) {
    22. array[i+start] = tmp[i];
    23. }
    24. }
    25. public static void mergeSort(int[] array) {
    26. int gap = 1;
    27. while (gap < array.length) {
    28. // i += gap * 2 当前gap组的时候,去排序下一组
    29. for (int i = 0; i < array.length; i += gap * 2) {
    30. int left = i;
    31. int mid = left+gap-1;//有可能会越界
    32. if(mid >= array.length) {
    33. mid = array.length-1;
    34. }
    35. int right = mid+gap;//有可能会越界
    36. if(right>= array.length) {
    37. right = array.length-1;
    38. }
    39. merge(array,left,right,mid);
    40. }
    41. //当前为2组有序 下次变成4组有序
    42. gap *= 2;
    43. }
    44. }

  • 相关阅读:
    SpringMVC异常处理器
    Java设计模式 _行为型模式_备忘录模式
    ARM汇编(gun-complier)
    使用vite + vue3 + ant-design-vue + vue-router + vuex 创建一个管理应用
    LeetCode50天刷题计划第二季(Day 32 — 阶乘后的零(13.20-14.00)
    网络代理技术:保障隐私与增强安全
    C/C++—Inline关键词
    这些女强人,颠覆了整个世界
    maven打完包运行,测试结果和本地不一致
    Digicert DV通配符
  • 原文地址:https://blog.csdn.net/ANNE_fly/article/details/133997637