七大经典排序算法总结【详解】

2023-11-17

排序算法的分类:

  1. 插入排序
  2. 选择排序
  3. 交换排序
  4. 归并排序
    具体分类如图所示:
    在这里插入图片描述
    这七种排序算法在我们生活中应用非常广泛,所用的场景各有不同,他的时间复杂度和空间复杂度也是不同的。

一、插入排序(初始数据越接近有序,时间效率越高):

1、直接插入排序:

直接插入排序(Insertion-Sort)的算法描述是一种简单直观的排序算法(这就跟我们打扑克牌一样,选择一张扑克牌直接插入到前面已经有序扑克牌后面)。

(1)思路分析:

① 从第一个元素开始,该元素可以认为已经被排序
② 取出下一个元素,在已经排序的元素序列中从后向前扫描
③如果该元素(已排序)大于新元素,将该元素移到下一位置
④ 重复步骤③,直到找到已排序的元素小于或者等于新元素的位置
⑤将新元素插入到该位置后
⑥ 重复步骤②~⑤

(2)动图演示:

在这里插入图片描述

具体代码实现:

public static void insertSort(int[] array) {
        int temp = 0;
        for (int i = 0; i < array.length; i++) {
            //选择一个数作为被比较的数;
            temp = array[i];
            int j = i - 1;
            for (j = i - 1; j >= 0; j--) {
                //取被比较数之前的数与被比较数进行比较;
                if (array[j] > temp) {
                    //如果这个数大于被比较的数,往后挪一个位置;
                    array[j + 1] = array[j];
                } else {
                    break;
                }

(3)性能分析:

①平均时间复杂度:O(N^2)
②最差时间复杂度:O(N^2)
③空间复杂度:O(1)
④稳定性:稳定

2、希尔排序:

希尔排序(ShellSort)是对插入排序最坏的情况的改进,主要是减少数据移动次数,增加算法的效率。

(1)思路分析:

将要排序的序列按照步长gap进行分组,先在这几组内进行插入排序,之后再进行整体的插入排序,gap步长的选择是希尔排序最重要的部分,要保证最后一次排序的步长为1,这样就会保证整个数组将会被排序,并且步长必须小于数组长度。

(2)图片演示:

在这里插入图片描述
具体代码实现:

public static void shellSort(int[] array) {
        //gap是为了分组;
        int gap = array.length;
        while (gap > 1) {
            //下一次的组数是上一次的一半;
            gap /= 2;
            shell(array, gap);
        }
    }

    public static void shell(int[] array, int gap) {
        int temp = 0;
        for (int i = gap; i < array.length; i++) {
            //同一组数的第二个数;
            temp = array[i];
            int j = i - gap;
            //同一组数的第一个数;
            for (j = i - gap; j >= 0; j -= gap) {
                if (array[j] > temp) {
                    array[j + gap] = array[j];
                } else {
                    break;
                }
            }
            array[j + gap] = temp;
        }
    }

性能分析:

①最好情况:时间复杂度为O(n)
②最坏情况下:时间复杂度为O(n^2)
③空间复杂度为:O(1)
④稳定性:不稳定

二、选择排序:

1、选择排序

(1)思路分析:

选择排序原理即是,遍历元素找到一个最小(或最大)的元素,把它放在第一个位置,然后再在剩余元素中找到最小(或最大)的元素,把它放在第二个位置,依次下去,完成排序。

(2)动图演示:

在这里插入图片描述

(3)具体代码实现:

public static void selectSort(int[] array) {
        int temp = 0;
        //记录此时最小的数;
        int minIndex;
        for (int i = 0; i < array.length; i++) {
            minIndex = i;
            int j = i + 1;
            //找到最小的数;
            for (j = i + 1; j < array.length; j++) {
                if (array[minIndex] > array[j]) {
                    minIndex = j;
                }
            }
            //将最小的数放到前面;
            temp = array[i];
            array[i] = array[minIndex];
            array[minIndex] = temp;
        }
    }

(4)性能分析:

(1)时间复杂度:O(n^2;
(2)空间复杂度:O(1;
(3)稳定性:不稳定;

2、堆排序

堆是一个按照完全二叉树存储的数组,它是一个近似的完全二叉树但是同时它又满足堆的性质。

(1)思路分析:

  1. .从小到大排序建大堆,从大到小排序建小堆

  2. 把堆顶的元素和当前堆的最后一个元素交换

  3. 堆的元素个数减一

  4. 从根节点向下调整

(2)图片演示:

在这里插入图片描述

(3)具体代码演示:

public static void heapSort(int[] array) {
        creatBigHeap(array);
        int end = array.length - 1;
        while (end > 0) {
            swap(array, 0, end);
            shiftDown(array, 0, end);
            end--;
        }
    }
    //建立大根堆;
    private static void creatBigHeap(int[] array) {
        for (int parent = (array.length - 1 - 1) / 2; parent >= 0; parent--) {
            shiftDown(array, parent, array.length);
        }
    }
    //向下调整;
    private static void shiftDown(int[] array, int parent, int len) {
        int child = 2 * parent + 1;
        while (child < len) {
            //找出最大孩子节点;
            if (child + 1 < len && array[child] < array[child + 1]) {
                child++;
            }
            //调整成大根堆;
            if (array[child] > array[parent]) {
                swap(array, child, parent);
                parent = child;
                child = 2 * parent + 1;
            } else {
                break;
            }
        }
    }
    //交换两个元素;
    private static void swap(int[] array, int i, int j) {
        int temp = array[i];
        array[i] = array[j];
        array[j] = temp;
    }

(4)性能分析:

(1)最好情况下时间复杂度为:O(nlogn)
(2)最坏情况下时间复杂度为:O(n
logn)
(3)空间复杂度为:O(1)
(4)稳定性:不稳定

三、交换排序

1、冒泡排序:

冒泡排序和选择排序差不多,都是与后面挨个比较,将最小的放到前面。

(1)思路分析:

冒泡排序是一种简单的排序算法,它不断地重复遍历数组,每次与其相邻的数进行比较,如果他们的顺序错误就交换,直到数组只剩下一个元素的时候,说明该数组已经排好序,之所以成为冒泡排序,是因为越小的元素会经由交换慢慢“浮”到数列的前面。

(2)动图分析:

在这里插入图片描述

(3)具体代码分析:

public static void bubbleSort(int[] array) {
        for (int i = 0; i < array.length; i++) {
            boolean flg=false;
            for (int j = 0; j < array.length - 1 - i; j++) {
                if (array[j] > array[j+1]) {
                    swap(array,j,j+1);
                    flg=true;
                }
            }
            if(flg==false){
                return;
            }
        }
    }

(4)性能分析:

(1)最坏情况时间复杂度为:O(n^2)。
(2)平均情况时间复杂度为:O(n^2)。
(3)需要额外空间:O(1)。
(4)稳定性:稳定。

2、快速排序(重点)

快速排序是目前应用最广,最有意思的一个排序算法,速度快,空间利用率高,名副其实是理想中最快的算法了。

基本思想

这里是引用选择一个基准元素,通常选择第一个元素或者最后一个元素,通过一趟扫描,将待排序列分成两部分,一部分比基准元素小,一部分大于等于基准元素,此时基准元素在其排好序后的正确位置,然后再用同样的方法递归地排序划分的两部分。

(1)挖坑法:

(1.1)基本思路:

①设置基准值,也就是数组的第一个元素。
②先将temp所在的位置设置为坑,然后从left开始找比temp大的数,找到后填上刚才temp处所设的坑,并且将找到的最大数的位置设为坑。
③end开始找比key小的数放到刚才最大数位置所设的坑处。
④这样就将数组分为两个子区间,如此循环。

(1.2)动图分析:

在这里插入图片描述

(1.3)具体代码实现:

 public static void quickSort(int[] array){
        quick(array,0,array.length-1);
    }
    public static void quick(int[] array,int start,int end){
        if(start>=end){
            return;
        }
        //找基准;
        int pivot=partition(array,start,end);
        //划分基准左边的数;
        quick(array,start,pivot-1);
        //划分基准右边的数;
        quick(array,pivot+1,end);
    }
    public static int partition(int[] array,int left,int right){
        int tmp=array[left];
        //从右边找比temp小的值;
        while (left<right){
            while (left<right&&array[right]>=tmp){
                right--;
            }
            array[left]=array[right];
            //从左边找比temp大的值;
            while (left<right&&array[left]<=tmp){
                left++;
            }
            array[right]=array[left];
        }
        array[left]=tmp;
        return left;
    }

(2)Hoare法:

(2.1)基本思路:

①将左边设为temp;
②先从右边开始找比temp小的,再从左边开始找比temp大的,然后将两个交换。重复此步骤;
③left和right相遇,将left和temp交换;

(2.2)动图分析:

(2.3)基本代码实现:

public static void quickSort(int[] array){
        quick(array,0,array.length-1);
    }
    public static void quick(int[] array,int start,int end){
        if(start>=end){
            return;
        }
        //找基准;
        int pivot=partition2(array,start,end);
        //划分基准左边的数;
        quick(array,start,pivot-1);
        //划分基准右边的数;
        quick(array,pivot+1,end);
    }
    public static int partition2(int[] array,int left,int right){
        int temp=array[left];
        int i=left;
        while (left<right){
            //从右边找比temp小的值;
            while (left<right&&array[right]>=temp){
                right--;
            }
            //从左边找比temp大的值;
            while (left<right&&array[left]<=temp){
                left++;
            }
            //交换left和right的值;
            swap(array,left,right);
        }
        swap(array,left,i);
        return left;
    }
    //交换两个元素;
    private static void swap(int[] array, int i, int j) {
        int temp = array[i];
        array[i] = array[j];
        array[j] = temp;
    }

(3)前后指针法:

(3.1)基本思路:

①定义两个指针前后开始遍历;
②当后面的指针的值小于temp,并且它不等于前一个指针的下一个,交换两个值;
③当一个指针到达最右边,最后交换前一个指针和temp的值;

(3.2)动图分析:

在这里插入图片描述

(3.3)具体代码分析:

public static void quickSort(int[] array) {
        quick(array, 0, array.length - 1);
    }

    public static void quick(int[] array, int start, int end) {
        if (start >= end) {
            return;
        }
        //找基准;
        int pivot = partition2(array, start, end);
        //划分基准左边的数;
        quick(array, start, pivot - 1);
        //划分基准右边的数;
        quick(array, pivot + 1, end);
    }
    public static int partition3(int[] array, int left, int right) {
        int prev = left;
        int cur = left + 1;
        while (cur <= right) {
            if (array[cur] < array[left] && array[++prev] != array[cur]) {
                swap(array, cur, prev);
            }
            cur++;
        }
        swap(array, prev, left);
        return prev;
}
 //交换两个元素;
    private static void swap(int[] array, int i, int j) {
        int temp = array[i];
        array[i] = array[j];
        array[j] = temp;
    }

(4)性能分析:

(1)时间复杂度:最好情况:O(n*logn);
最坏情况: O(n^2);
(2)空间复杂度:最好情况:O(logn);
最坏情况:O(n);
(3)稳定性:不稳定;

四、归并排序

归并排序(MERGE-SORT)是建立在归并操作上的一种有效的排序算法,该算法是采用分治法(Divide andConquer)的一个非常典型的应用。将已有序的子序列合并,得到完全有序的序列;即先使每个子序列有序,再使子序列段间有序。若将两个有序表合并成一个有序表,称为二路归并。

1、思路分析:

(1)分解(Divide):将n个元素分成个含n/2个元素的子序列。
(2)解决(Conquer):用合并排序法对两个子序列递归的排序。
(3)合并(Combine):合并两个已排序的子序列已得到排序结果。

2、图解分析

在这里插入图片描述

3、具体代码实现:

 public static void mergeSort(int[] array){
        mergeSortFunc(array,0,array.length-1);
    }
    //将左右分解;
    public static void mergeSortFunc(int[] array,int left,int right){
        if(left>=right){
            return;
        }
        //取中间的数;
        int mid=(left+right)/2;
        //递归mid左边的数;
        mergeSortFunc(array,left,mid);
        //递归mid右边的数;
        mergeSortFunc(array,mid+1,right);
        merge(array,left,right,mid);
    }
    //将左右排序后进行合并;
    public static void merge(int[] array,int start,int end,int mid){
        int s1=start;
        int s2=mid+1;
        int[] temp=new int[end-start+1];
        int k=0;
        while (s1<=mid&&s2<=end){
            if(array[s1]<=array[s2]){
                temp[k++]=array[s1++];
            }else {
                temp[k++]=array[s2++];
            }
        }
        while (s1<=mid){
            temp[k++]=array[s1++];
        }
        while (s2<=end){
            temp[k++]=array[s2++];
        }
        for (int i = 0; i <temp.length ; i++) {
            //因为array不一定是从0开始所以要加start;
            array[i+start]=temp[i];
        }
    }

4、性能分析:

(1)时间复杂度:O(n*logn);
(2)空间复杂度:O(n);
(3)稳定性:稳定;

五、排序算法总结:

1、应用场景:

数据量规模较小,考虑插入或选择。当元素分布有序时插入将大大减少比较和移动记录的次数,如果不要求稳定性,可以使用选择,效率略高于插入;
数据量规模中等,使用希尔排序;
数据量规模较大,考虑堆排序(元素分布接近正序或逆序)、快速排序(元素分布随机)和归并排序(稳定性);
一般来说不使用冒泡。

2、性能总结:

在这里插入图片描述

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

七大经典排序算法总结【详解】 的相关文章

随机推荐

  • eclipse 项目没错却有红叉(解决办法)

    1 进入 eclipse 选择报错的项目 然后在工具栏选择Window 选择Show View 选择Problems 如下图 2 找到 General 下的 problems 双击 problems 就会在下面提示你项目报错的原因 根据错误
  • Go_异常处理

    Error 异常就是程序出现了不正常的情况 会导致程序非正常停止 而异常处理就是针对非正常停止的情况 给出异常时的处理方式 语法错误不算异常体系中 error是一个接口 作用是返回程序异常的信息 errors实现了error type er
  • QT踩坑第十一天(QT多线程)

    前言 QT在什么时候会用到多线程 所有的IO操作都要放到线程里面 1 IO操作QIODevice文件IO网络IO 套接字eg CAN Linux下也是套接字 串口等外设 因为他们不确定什么时候可以读完 2 耗时的算法eg 文件压缩 信号处理
  • Moviepy音视频开发:视频转gif动画或jpg图片exe图形化工具开发案例

    前往老猿Python博文目录 一 引言 老猿之所以学习和研究Moviepy的使用 是因为需要一个将视频转成动画的工具 当时在网上到处搜索查找免费使用工具 结果找了很多自称免费的工具 但转完GIF后都会在动画中打上对应工具的显著广告或Logo
  • 一周小结 - 拒绝拖延 现在做起

    很早就一直有用文字记录生活的想法 终于在这周开始了 不知道能写多久 拭目以待 一周的生活回顾下来 可能下面的一些让自己有些许感悟吧 感悟之一 更多的体验发现不一样的美好 感悟之二 有些事并没那么可怕 可怕的 可能是被头脑放大了N倍 感悟之三
  • 华为od机试 Python【扩散矩阵】

    描述 你手上有一个数字版的迷宫 里面只有两种格子 0 和 1 这里的1有个特性 它每秒会感染它上 下 左 右的0格子 一旦0被感染 它就变成1 给定一个迷宫大小以及两个起始感染点 你能算出这个迷宫被完全感染需要多少秒吗 输入 迷宫的行列数
  • mybatis-plus返回查询总记录数

    mybatis plus返回查询总记录数 mp框架提供了selectCount方法 来查询总记录数 需求 查找薪水大于3500 名字里有 小 的 员工的个数 sql实现 select count from t employee where
  • C++的基础学习

    C 主要学习 C与C 的不同 C 的特性及专业术语 C 程序的编译 一 C到C 的转换 C与C 的区别 C 是C的增强 区别 C 具有严格的数据类型检查 C 新增了命名空间 异常处理 面向对象编程 变量的权限和引用及函数的重载及运算符的重载
  • HTTP1.0和HTTP1.1和HTTP2.0的区别

    HTTP1 0和HTTP1 1和HTTP2 0的区别 1 HTTP1 0和HTTP1 1的区别 1 1 长连接 Persistent Connection HTTP1 1支持长连接和请求的流水线处理 在一个TCP连接上可以传送多个HTTP请
  • Mysql索引详解及优化(key和index区别)

    Mysql索引详解及优化 key和index区别 文章记录
  • Hyperledger Fabric配置文件解析

    目录 1 相关配置文件介绍 2 crypto config yaml 3 configtx yaml 3 1 Organizations组织配置部分 3 2 Capabilities通道能力配置部分 3 3 Application 应用通道
  • 浏览器内核css前缀大全

    1 css前缀为 moz 的浏览器 火狐浏览器 2 css前缀为 webkit 的浏览器 谷歌浏览器 苹果浏览器 Comodo Drangon 科摩多龙 搜狗高速浏览器 快快浏览器 枫树浏览器 云游浏览器 360极速浏览器 世界之窗极速版
  • RedmiBook 蓝屏 关机后出现 No Bootable Devices 问题的解决方法

    问题 关机后 重新开机出现 显示 没有可启用设备 解决方法一 该方法解决的不够彻底 暂时可以解决问题 正常开机 关机后 按f2 开机键 出现以下页面 选择 启动菜单 gt 启动模式 gt UEFI 启动 gt Enter 选择 退出菜单 g
  • 嵌入式毕业设计 树莓派寝室宿舍门禁刷卡系统 - 物联网 单片机 嵌入式

    文章目录 0 前言 1 前言 2 主要器件 3 实物效果 4 树莓派读取 RC522 RFID 标签 5 mg90s 控制原理 6 最后 0 前言 这两年开始毕业设计和毕业答辩的要求和难度不断提升 传统的毕设题目缺少创新和亮点 往往达不到毕
  • 为什么使用非线性激活函数?常见的非线性激活函数及优缺点对比

    为何使用非线性激活函数 如上图的神经网络 在正向传播过程中 若使用线性激活函数 恒等激励函数 即令 则隐藏层的输出为 即 可以看到使用线性激活函数神经网络只是把输入线性组合再输出 所以当有很多隐藏层时 在隐藏层使用线性激活函数的训练效果和不
  • js实现滑动拼图验证码

    js实现滑动拼图验证码 我这个样式是仿那些大网站做了 学习用的 只用到前端 无后端内容 想改成后端的 思路大概就是 后端切割图片 然后把图片传给前端 前端展示 前端完成拖拽后将坐标传回给后端 后端去判断与自己切割的地方是否一致 下面看图示
  • C++57个入门知识点_47 虚函数的多态性(成员函数中的虚函数具有多态性;构造和析构函数中,虚函数没有多态性;在构造析构函数中调用普通成员函数,该普通成员函数中有虚函数的间接调用,没有多态)

    本篇主要讨论两个问题 1 成员函数中 虚函数是否有多态性 答案为 有 2 构造和析构函数中 虚函数是否有多态性 答案为 无 1 成员函数中 虚函数是否有多态性 成员函数中的虚函数具有多态性 以下代码中 void test foo 普通成员函
  • #手写代码# 用Bert+CNN解决文本分类问题

    文章目录 1 配置文件 2 定义模型 2 1 init self config 函数 2 1 conv and pool 函数 2 3 forward self x 函数 1 配置文件 首先定义一个配置文件类 类里边存放Bert和CNN的一
  • Web函数请求多并发上线,Web服务部署更快更省

    Web函数 Web Function 是云函数的一种函数类型 区别于事件函数 Event Function Web函数通过支持原生的HTTP WebSocket协议 兼容任意一种原生Web框架编写的Web服务 无需改造即可将传统项目部署到函
  • 七大经典排序算法总结【详解】

    排序算法的分类 插入排序 选择排序 交换排序 归并排序 具体分类如图所示 这七种排序算法在我们生活中应用非常广泛 所用的场景各有不同 他的时间复杂度和空间复杂度也是不同的 一 插入排序 初始数据越接近有序 时间效率越高 1 直接插入排序 直