十大经典排序算法详解(二)希尔排序,归并排序,快速排序(下)

简介: 十大经典排序算法详解(二)希尔排序,归并排序,快速排序

2-归并排序


算法思想:

归并排序的思想本质就是进行分冶.把 整个序列拆分成多个序列,先将每个序列排好序,这个就是分冶思想中分,同样也是归并排序中归 的思想.


之后再将各个序列整合到一起这就是分冶中的冶同样也是归并排序的并.思想说完了,但是呢说不能解决问题,我们还是通过下面的图来帮助大家理解:


20210121144946734.png


看到图之后,我们就会发现上面分和合并的过程都特别像递归对不对,都是按照一定的终止条件一直执行下去.所以呢这就暗示了我们的归并排序是可以通过递归的思想来编写的.


现在我们基本上已经基本了解归并排序的基本思想了,现在我们再来看看归并排序由那些特点吧


归并排序是稳定的,这个大家看过我上面的演示过程就能看出来了.

归并排序需要消耗大量的内存空间.这个内存空间是对比冒泡排序之类的排序算法而言的,因为他们的内存空间都是只存在于常量级别的,但是归并排序却是需要消耗线性级别的内存空间,所以才会使用大量这个形容词.消耗的内存空间就是等同于待排序序列的长度.即O(n)的复杂度.

算法图解:


2021011916192745.gif


示例代码:


重要代码我已经添加了注释能够更加方便读者们理解.


public static int[] sort(int []num) {
        //分裂之后的数组如果只有1个元素的话,
        //那么就说明可以开始合并的过程了,所以直接返回.
    if(num.length<2)
      return num;
    int middle=num.length/2;
    //截取左右两个序列
    int []left=Arrays.copyOfRange(num, 0, middle);
    int []right=Arrays.copyOfRange(num, middle, num.length);
    return merge(sort(left), sort(right));
  }
  public static int[] merge(int []left,int []right) {
    int []num=new int [left.length+right.length];
    int i=0,j=0,k=0;
    //注意终止条件是&&,只要有一个不满足,循环就结束
    while(i<left.length&&j<right.length) {
      if(left[i]<right[j])
        num[k++]=left[i++];
      else 
        num[k++]=right[j++];
    }
    //上面的循环跳出之后,就说明有且仅会有一个序列还有值了
    //所以需要再次检查各个序列,并且下面的两个循环是互斥的,只会执行其中的一个
    //或者都不执行
    while(i<left.length) {
      num[k++]=left[i++];
    }
    while(j<right.length) {
      num[k++]=right[j++];
    }
    for(int l=0;l<num.length;l++)
      System.out.print(num[l]+" ");
    System.out.println();
    return num;
  }
  public static void main(String[] args) {
    int []num ={7,4,9,3,2,1,8,6,5,10};
    long startTime=System.currentTimeMillis();  
    num=sort(num);
//    for(int i=0;i<num.length;i++) {
//      System.out.print(num[i]+" ");
//    }
//    System.out.println();
    long endTime=System.currentTimeMillis(); 
    System.out.println("程序运行时间: "+(endTime-startTime)+"ms"); 
  }

20210121150227411.png


这里呢就不演示全部的元素了,这里就简单和大家演示一下左半边区间元素排序的过程,右半边大家可以自行脑补,这个应该不难.


2021012209215545.png


这样大家应该就能理解了,主要就是如果序列长度不是2的次方的话,后续划分序列的时候就会出现上述与我们类似的情况.毕竟我们划分区间的 整个过程就类似于将区间中心划分成一个二叉树.


复杂度分析:


理解完归并排序的基本思想之后,我们就需要来分析一下他的时间复杂度,空间复杂度.


时间复杂度


其实我们从上面的算法中能明显看出来我们没有使用for循环,而是通过递归的方式来解决我们的循环问题的.所以更加的高效.

就像我们上面的演示过程里面写的一样,这个执行的层数为2 * log N,并且每层操作的元素是N个,所以我们的时间复杂度即为2 * N * log N,但是常数可以忽略不计,所以我们的时间复杂度就控制在了O(N*log N),对比我们之前算法的时间复杂度O(N*N),可以看到时间复杂度明显降低很多,并且这个时间复杂度不仅仅只是适用于平均情况,针对最坏的情况,同样也能适用.


空间复杂度


这个我们也可以看到我们整个排序的过程中需要增加一个等同于排序序列的长度的空间来存储我们为二次排序之后的序列,所以我们需要的空间复杂度就是线性级别的O(n),这个复杂度对比我们之前的几种排序算法,可以看得出来的确是较为大了一点.但是也可以明显看出来整个的时间复杂度明显降低了很多,这就是一种跟明显通过牺牲空间换取时间的方法.对比我们第一篇讲的HashMap.


3-快速排序


算法思想:


快速排序的算法思想也比较好理解.但是呢用文字讲述出来可能会有点难以理解,这里我尽可能的讲的简单些.就算还是看不懂的话也没事,我们还是会通过图片演示的形式来加深大家的理解的.


快速排序的思想就是每次排序都选定一个基准值 ,当我们在选定完这个基准值之后,我们就另外在需要两个"“指针”",这个指针并不是我们C++中的指针,但是作用也差不多,主要就是帮助我们标记位置的.这两个指针分别指向待排序序列的队头元素和队尾元素.


我们首先先从队尾元素开始从右往左查找出第一个不大于该基准值的元素,找到之后首先将之前指向队尾元素的指针指向该元素位置,之后再将基准值与我们刚才查找到的元素交换位置.


在这个步骤结束之后,我们就需要从队头元素开始从左往右查找第一个不小于基准值的元素,查找到该元素之后还是按照我们上面的步骤.先将之前指向队头元素的指针指向该元素,之后再将基准值与该元素交换位置.


重复上面这个步骤,直到队头指针与队尾指针相遇,相遇之后就代表我们的第一次排序就已经完成了,并且在第一次排序完成之后我们就会发现序列是这样的状态,基准值左边的元素全部小于等于该基准值,基准值右边的元素全部大于等于该基准值.


之后的排序就只需要对基准值左右两边的序列在进行上述的操作即可.


好了关于算法的文字讲解已经完成了,当然了,这时候很多小伙伴肯定会想 “你这都说的啥啊” ,没关系老样子还是用图来说话:


20210122203205390.png

20210122203235664.png

20210122203258927.png


这里我只演示了第一次排序的过程,后续的相信大家可以自行脑补的.

理解完快速排序的基本算法思想之后,我们就需要来稍微聊聊快速排序的特点了.


快速排序本身是不稳定的,我们首先先来了解一下不稳定的快排是什么样的演示过程.


20210125101115652.png


通过上面的图大家就能知道快排为啥是不稳定的了.


快速排序也有一个极端情况,就是快排在对 已经有序的序列进行排序的时候,时间复杂度会直接飙到O(n*n),也就是最坏的时间复杂度,这个情况大家其实自己模拟以下就能知道了.

这里我们就不在过多讲解了.

算法图解:


20210119161935100.gif


示例代码:


重要代码我已经添加了注释能够更加方便读者们理解.

public static void sort(int []num,int left,int right) {
    if(left<right) {
      int key=num[left];
      int i=left;
      int j=right;
      //这部分便是算法的核心思想
      while(i<j) {
        //从右向左找到第一个不大于基准值的元素
        while(i<j&&num[j]>=key) {
          j--;
        }
        if(i<j) {
          num[i]=num[j];
        }
        //从左往右找到第一个不小于基准值的元素
        while (i<j&&num[i]<=key) {
          i++;
        }
        if(i<j) {
          num[j]=num[i];
        }
      }
      num[i]=key; 
      for(int k=0;k<num.length;k++) {
        System.out.print(num[k]+" ");
      }
      System.out.println();
      //递归继续对剩余的序列排序
      sort(num, left, i-1);
      sort(num, i+1, right);
    } 
  }
  public static void main(String[] args) {
    int []num ={7,4,9,3,2,1,8,6,5,10};
    long startTime=System.currentTimeMillis(); 
    sort(num, 0, num.length-1);
    long endTime=System.currentTimeMillis(); 
    System.out.println("程序运行时间: "+(endTime-startTime)+"ms"); 
  }

20210122203446215.png

因为上面的动态演示图已经演示的很清楚了,所以就不在画图演示了.


复杂度分析:


理解完快速排序的基本思想之后,我们就需要来分析一下他的时间复杂度,空间复杂度.


时间复杂度


其实我们从上面的算法中能明显看出来我们没有使用for循环,而是通过递归的方式来解决我们的循环问题的.所以更加的高效.


就像我们上面所演示的一样,平均情况下的时间复杂度即为O(N * log N),但是也想我们所说的那样,快速排序存在着一个极端情况就是 已经有序的序列进行快排 的话很刚好形成是类似于冒泡排序一样的时间复杂度即为O(N * N),这是我们需要注意的,但是在大多数情况下,快排还是我们效率最高的排序算法.


空间复杂度


这个我们也可以看到我们整个排序的过程中每次排序的一个循环里面就需要添加一个空间来存储我们的key,并且快排其实和我们上面的归并排序也是有异曲同工之妙的,也有点类似于使用了二叉树的概念,所以他的空间复杂度就是O(log N)


相关文章
|
11月前
|
搜索推荐 C语言
【排序算法】快速排序升级版--三路快排详解 + 实现(c语言)
本文介绍了快速排序的升级版——三路快排。传统快速排序在处理大量相同元素时效率较低,而三路快排通过将数组分为三部分(小于、等于、大于基准值)来优化这一问题。文章详细讲解了三路快排的实现步骤,并提供了完整的代码示例。
354 4
|
6月前
|
算法 搜索推荐
快速排序-数据结构与算法
快速排序(Quick Sort)是一种基于分治法的高效排序算法。其核心思想是通过选择基准(pivot),将数组划分为左右两部分,使得左侧元素均小于基准,右侧元素均大于基准,然后递归地对左右两部分进行排序。时间复杂度平均为 O(n log n),最坏情况下为 O(n²)(如数组已有序)。空间复杂度为 O(1),属于原地排序,但稳定性不佳。 实现步骤包括编写 `partition` 核心逻辑、递归调用的 `quickSort` 和辅助函数 `swap`。优化方法有随机化基准和三数取中法,以减少最坏情况的发生。
435 13
|
11月前
|
存储 搜索推荐 Python
用 Python 实现快速排序算法。
快速排序的平均时间复杂度为$O(nlogn)$,空间复杂度为$O(logn)$。它在大多数情况下表现良好,但在某些特殊情况下可能会退化为最坏情况,时间复杂度为$O(n^2)$。你可以根据实际需求对代码进行调整和修改,或者尝试使用其他优化策略来提高快速排序的性能
263 61
|
算法 搜索推荐 Shell
数据结构与算法学习十二:希尔排序、快速排序(递归、好理解)、归并排序(递归、难理解)
这篇文章介绍了希尔排序、快速排序和归并排序三种排序算法的基本概念、实现思路、代码实现及其测试结果。
351 1
|
搜索推荐
冒泡排序(Bubble Sort)以及选择排序(Selection Sort)和快速排序(Quick Sort)详细解析
冒泡排序(Bubble Sort)以及选择排序(Selection Sort)和快速排序(Quick Sort)详细解析
199 1
|
存储 搜索推荐 算法
【排序算法(二)】——冒泡排序、快速排序和归并排序—>深层解析
【排序算法(二)】——冒泡排序、快速排序和归并排序—>深层解析
|
28天前
|
传感器 机器学习/深度学习 编解码
MATLAB|主动噪声和振动控制算法——对较大的次级路径变化具有鲁棒性
MATLAB|主动噪声和振动控制算法——对较大的次级路径变化具有鲁棒性
146 3
|
1月前
|
存储 编解码 算法
【多光谱滤波器阵列设计的最优球体填充】使用MSFA设计方法进行各种重建算法时,图像质量可以提高至多2 dB,并在光谱相似性方面实现了显著提升(Matlab代码实现)
【多光谱滤波器阵列设计的最优球体填充】使用MSFA设计方法进行各种重建算法时,图像质量可以提高至多2 dB,并在光谱相似性方面实现了显著提升(Matlab代码实现)
|
22天前
|
机器学习/深度学习 算法 数据可视化
基于MVO多元宇宙优化的DBSCAN聚类算法matlab仿真
本程序基于MATLAB实现MVO优化的DBSCAN聚类算法,通过多元宇宙优化自动搜索最优参数Eps与MinPts,提升聚类精度。对比传统DBSCAN,MVO-DBSCAN有效克服参数依赖问题,适应复杂数据分布,增强鲁棒性,适用于非均匀密度数据集的高效聚类分析。
|
22天前
|
开发框架 算法 .NET
基于ADMM无穷范数检测算法的MIMO通信系统信号检测MATLAB仿真,对比ML,MMSE,ZF以及LAMA
简介:本文介绍基于ADMM的MIMO信号检测算法,结合无穷范数优化与交替方向乘子法,降低计算复杂度并提升检测性能。涵盖MATLAB 2024b实现效果图、核心代码及详细注释,并对比ML、MMSE、ZF、OCD_MMSE与LAMA等算法。重点分析LAMA基于消息传递的低复杂度优势,适用于大规模MIMO系统,为通信系统检测提供理论支持与实践方案。(238字)

热门文章

最新文章