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

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

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)


相关文章
|
21天前
|
搜索推荐 C语言
【排序算法】快速排序升级版--三路快排详解 + 实现(c语言)
本文介绍了快速排序的升级版——三路快排。传统快速排序在处理大量相同元素时效率较低,而三路快排通过将数组分为三部分(小于、等于、大于基准值)来优化这一问题。文章详细讲解了三路快排的实现步骤,并提供了完整的代码示例。
47 4
|
2月前
|
算法 搜索推荐 Shell
数据结构与算法学习十二:希尔排序、快速排序(递归、好理解)、归并排序(递归、难理解)
这篇文章介绍了希尔排序、快速排序和归并排序三种排序算法的基本概念、实现思路、代码实现及其测试结果。
24 1
|
2月前
|
存储 搜索推荐 算法
【排序算法(二)】——冒泡排序、快速排序和归并排序—>深层解析
【排序算法(二)】——冒泡排序、快速排序和归并排序—>深层解析
|
2月前
|
存储 算法 搜索推荐
算法进阶之路:Python 归并排序深度剖析,让数据排序变得艺术起来!
算法进阶之路:Python 归并排序深度剖析,让数据排序变得艺术起来!
76 0
|
2月前
|
算法 Python
Python算法编程:冒泡排序、选择排序、快速排序
Python算法编程:冒泡排序、选择排序、快速排序
27 0
|
2月前
|
搜索推荐 Java Go
深入了解归并排序算法
深入了解归并排序算法
22 0
|
2月前
|
算法 安全 数据安全/隐私保护
基于game-based算法的动态频谱访问matlab仿真
本算法展示了在认知无线电网络中,通过游戏理论优化动态频谱访问,提高频谱利用率和物理层安全性。程序运行效果包括负载因子、传输功率、信噪比对用户效用和保密率的影响分析。软件版本:Matlab 2022a。完整代码包含详细中文注释和操作视频。
|
1天前
|
算法
基于GA遗传算法的PID控制器参数优化matlab建模与仿真
本项目基于遗传算法(GA)优化PID控制器参数,通过空间状态方程构建控制对象,自定义GA的选择、交叉、变异过程,以提高PID控制性能。与使用通用GA工具箱相比,此方法更灵活、针对性强。MATLAB2022A环境下测试,展示了GA优化前后PID控制效果的显著差异。核心代码实现了遗传算法的迭代优化过程,最终通过适应度函数评估并选择了最优PID参数,显著提升了系统响应速度和稳定性。
|
2天前
|
算法
基于大爆炸优化算法的PID控制器参数寻优matlab仿真
本研究基于大爆炸优化算法对PID控制器参数进行寻优,并通过Matlab仿真对比优化前后PID控制效果。使用MATLAB2022a实现核心程序,展示了算法迭代过程及最优PID参数的求解。大爆炸优化算法通过模拟宇宙大爆炸和大收缩过程,在搜索空间中迭代寻找全局最优解,特别适用于PID参数优化,提升控制系统性能。
|
14天前
|
算法 数据安全/隐私保护 索引
OFDM系统PAPR算法的MATLAB仿真,对比SLM,PTS以及CAF,对比不同傅里叶变换长度
本项目展示了在MATLAB 2022a环境下,通过选择映射(SLM)与相位截断星座图(PTS)技术有效降低OFDM系统中PAPR的算法实现。包括无水印的算法运行效果预览、核心程序及详尽的中文注释,附带操作步骤视频,适合研究与教学使用。