七大排序算法详解,动图展示 +代码实现,老奶奶看了都直呼内行

2023-11-05

在这里插入图片描述

1、插入排序:(元素少时插排最快)

1、[ 有序区间(黄色区间),无序区间(蓝色区间) ]

每次操作:

  • 1、抓无序区间(右侧蓝区)的第一张牌(红色牌)
  • 2、依次和有序区间(左侧黄区)的牌比较(绿色为正在比较的牌)
  • 3、选择适合的位置插入

动图展示:

在这里插入图片描述

代码实现:

    public static void insertSort(long[] array) {
        for (int i = 0; i < array.length - 1; i++) {
            //1、抓无序区间第一张牌(红色牌)
            long key = array[i + 1];
            //2、将取出的牌依次和有序区间的牌比较
            int j;
            for (j = i; j >= 0; j--) {
                //如果取出 红色的牌的值 < 绿色牌的值
                if (key < array[j]) {
                    //将 绿牌 往后挪一个位置
                    array[j + 1] = array[j];
                } else {
                    break;
                }
            }
            //3、选择合适的位置插入
            array[j + 1] = key;
        }
    }

性能分析:

时间复杂度 空间复杂度
最好 平均 最坏
O(n) O(n^2) O(n^2) O(1)
数据有序 数据逆序

具备稳定性(相等的两个数相对位置不会变),插入排序,初始数据越接近有序,时间效率越高

2、希尔排序:

希尔是发明者(ShellSort)

插排的优化版(分组插排排序)

排序步骤:(分为几组,就意味着中间间隔是多少)(组数一般为 数组长度 / 2)(下次的组数为上次组数的一半)

  • 1、将数组按不同颜色依次分成了 5 组:然后进行组内比较大小,调换顺序
  • 2、将第一次调整好的数组分成 2 组:然后进行组内比较大小,调换顺序
  • 3、将第二次调整好的数组进行整体比较

在这里插入图片描述
动图展示:

在这里插入图片描述

代码实现:

    public static void shellSort(long[] array) {
        //gap为间隔,也就是分为了几组
        int gap = array.length / 2;
        while (true) {
            insertSortGap(array,gap);
            if (gap == 1) {
                break;
            }
            //下次的组数为上次组数的一半
            gap = gap / 2;
        }
    }
    public static void insertSortGap(long[] array,int gap) {
        for (int i = gap; i < array.length; i++) {
            // key 为同一组中的第二个数
            long key = array[i];
            int j = 0;
            //同一组中的第一个数
            for (j = i - gap; j >= 0; j = j -gap) {
                //第二个数 < 第一个数
                if (key < array[j]) {
                    //第二个数的值 = 第一个数的值
                    array[j + gap] = array[j];
                } else {
                    break;
                }
            }
            //第一个数 = 之前保存的第二个数的值(此时的 j 是一个负数,加上 gap 刚好为第一个数)
            array[j + gap] = key;
        }
    }

性能分析:

时间复杂度 空间复杂度
最好 平均 最坏
O(n) O(n^1.3) O(n^2) O(1)
数据有序 比较难构造

不稳定

3、选择排序:

[ 有序区间(黄色区间),无序区间(蓝色区间)]

每一次从无序区间选出最小(或最大)的一个元素,存放在无序区间的最前(或最后),直到全部待排序的数据元素排完

动图展示:

在这里插入图片描述

代码实现:

    public static void selectSort(long[] array) {
        for (int i = 0; i < array.length - 1; i++) {
            //有序区间:[0,i)
            //无序区间:[i,array.length]
            int minIndex = i;//最小数下标
            //先从无序区间中找到最小的数
            for (int j = i + 1; j < array.length; j++) {
                if (array[j] < array[minIndex]) {
                    minIndex = j;
                }
            }
            //交换这个 最小数 和 无序区间的第一个数
            long t = array[minIndex];
            array[minIndex] = array[i];
            array[i] = t;
        }
    }

性能分析:

时间复杂度 空间复杂度
O(n^2) O(1)
数据不敏感 数据不敏感

不稳定:(相等的两个数相对位置会变)

int[] a = { 9, 2, 5a, 7, 4, 3, 6, 5b };
// 交换中该情况无法识别,保证 5a 还在 5b 前边

4、堆排序:

随便给一组数据:[ 20,17,16,5,4,3 ]

(无序区间,我们用下划线的方式表示)(有序区间:用小蓝圈来表示)

排序流程:

  • 1、建大堆:[ 20,17,4,16,5,3 ]
  • 2、从大堆中找出最大值(堆顶元素),和无序区间的最后一个数字交换
  • 3、此时数组中左边是无序区间,右边是有序区间,用来存放最大值
  • 4、交换完数字后,需要对交换的堆顶元素进行向下调整为大顶堆(不包括有序区间)
  • 5、循环遍历该过程

不能通过小堆来实现,因为有序区间放在开头的话,堆的逻辑结构就构不成二叉树了

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

   public static void heapSort(long[] array) {
        //1、建大堆
        createHeap(array,array.length);
        //2、进行选择的过程,一共需要 array.length - 1组
        for (int i = 0; i < array.length - 1; i++) {
            //无序区间:[ 0,array.length - 1]
            //交换0号下标(大堆中0号下标为堆中最大值)和无序数组中最后一个元素
            long t = array[0];
            array[0] = array[array.length - 1 - i];
            array[array.length - 1 - i] = t;
            // 无序区间变为:[ 0,array - i - 1]
            //向下调整
            adjustDown(array,array.length-1-i,0);
        }
    }
    //建大堆
    private static void createHeap(long[] array, int size) {
        for (int i = (size - 2) / 2; i >= 0; i--) {
            adjustDown(array,size,i);
        }
    }
    //向下调整
    private static void adjustDown(long[] array, int size, int index) {
        while (true) {
            //1、判断该结点是不是叶子结点
            int leftIndex = 2 * index + 1;
            if (leftIndex >= size) {
                //左孩子不存在,该节点是叶子结点
                return;
            }
            //2、找出最大的孩子
            int maxIndex = leftIndex;
            int rightIndex = 2 * index + 2;
            if (rightIndex < size && array[rightIndex] > array[maxIndex]) {
                maxIndex = rightIndex;
            }
            //3、比较最大孩子和该节点的大小
            if (array[index] >= array[maxIndex]) {
                //当前结点的值大于两个孩子的值,则无须交换
                return;
            }
            //4、交换
            long t = array[index];
            array[index] = array[maxIndex];
            array[maxIndex] = t;
            //5、将最大孩子视为 index,循环回去
            index = maxIndex;
        }

性能分析:

时间复杂度 空间复杂度
O(n*log(n)) O(1)
数据不敏感 数据不敏感

不稳定

5、冒泡排序:

[ 无序区间(蓝色区间),有序区间(黄色区间)]

绿色为两个正在比较的牌

在无序区间(蓝色区间),通过相邻两个数的比较,将最大的数冒泡到无序区间的最后,持续这个过程

动图展示:

在这里插入图片描述

代码实现:

    public static void bubbleSort(long[] array) {
        for (int i = 0; i < array.length - 1; i++) {
            //无序区间:[0,array.length - 1]
            //有序区间:[array.length - i,array.length]

            //每次进行冒泡之前,假设数组已经有序
            boolean isSorted = true;
            //进行冒泡过程
            for (int j = 0; j < array.length - i - 1; j++) {
                if (array[j] > array[j + 1]) {
                    long t = array[j];
                    array[j] = array[j + 1];
                    array[j + 1] = t;
                    isSorted = false;
                }
            }
            if (isSorted) {
                break;
            }
        }
    }

性能分析:

时间复杂度 空间复杂度
最好 平均 最坏
O(n) O(n^2) O(n^2) O(1)
数据有序 数据逆序

稳定(当两个数字相同时,不交换两个数字的位置)

6、快速排序:(重点)

步骤:

  • 1、从数组中选择一个数(一般是最左边的那个数),作为基准
  • 2、实现 partition 方法(小于 基准的放 左边,大于 基准的放 右边)
  • 3、分别对左右两个区间按同样的方法处理
  • 4、直到(小区间有序: size = 0 / 1)

例如:给定数组:[ 5,2,1,7,3,6,9 ]
(我们用红框来表示基准)

解法类似一颗二叉树
在这里插入图片描述
最终结果: [ 1,2,3,5,6,7,9 ]

动图展示:
在这里插入图片描述
代码实现

    public static void quickSort(long[] array) {
        quickSortInternal(array,0,array.length - 1);
    }
    //给定位置之间采用快排
    private static void quickSortInternal(long[] array, int lowIndex, int highIndex) {
        //4、区间内的个数 = 0 / 1时,结束
        int size = highIndex - lowIndex + 1;
        if (size <= 1) {
            return;
        }
        //1、选出一个数(选择最左边的)- array[lowIndex]
        //2、执行 partition,小的放左,大的放右
        //keyIndex 是经过 partition之后,选出来的数最终所在的下标(一次 partition之后,红框的位置)
        int keyIndex = partition(array,lowIndex,highIndex);
        //3、分别对左右区间进行相同处理 - 递归
        quickSortInternal(array,lowIndex,keyIndex - 1);
        quickSortInternal(array,keyIndex + 1,highIndex);
    }

    //以选出的 lowIndex 位置下的元素为基准,遍历数组,把比基准元素小的放到他左边,比他大的放右边
    private static int partition(long[] array, int lowIndex, int highIndex) {
        //选择合适的方法:
        //方法1: 
        //return hover(array,lowIndex,highIndex);
        //方法2:
        //return digAHole(array,lowIndex,highIndex);
        //方法3:
        return doublePointer(array,lowIndex,highIndex);
    }

实现 partition 方法:

  • 方法1:Hover 法

在这里插入图片描述

    private static int hover(long[] array, int lowIndex, int highIndex) {
        int leftIndex = lowIndex;
        int rightIndex = highIndex;
        // 选择的数是最左边的一个
        long key = array[lowIndex];
        //选择了最左边,从右边先开始
        //因为从右边写起来比较简单,从左边开始写需要处理特殊情况,较复杂
        while (leftIndex < rightIndex) {
            while (leftIndex < rightIndex && array[rightIndex] >= key) {
                rightIndex--;
            }
            while (leftIndex < rightIndex && array[leftIndex] <= key) {
                leftIndex++;
            }
            //rightIndex下标遇到比基数小的数,leftIndex下标遇到比基数大的数,则,交换两个数字
            swap(array,leftIndex,rightIndex);
        }
        swap(array,lowIndex,leftIndex);
        return leftIndex;
    }
    private static void swap(long[] array,int index1,int index2) {
        long t = array[index1];
        array[index1] = array[index2];
        array[index2] = t;
    }
  • 方法2:挖坑法(效率最高)

在这里插入图片描述

    private static int digAHole(int[] array, int lowIndex, int highIndex) {
        //key 代表基数,将其挖出,则该位置为空
        int key = array[lowIndex];
        int leftIndex = lowIndex + 1;
        int rightIndex = highIndex;
        //只有两个元素的情况
        if (leftIndex == rightIndex) {
            if (array[leftIndex] >= key) {
                return lowIndex;
            } else {
                int tmp = key;
                key = array[leftIndex];
                array[leftIndex] = key;
                return leftIndex;
            }
        }
        while (leftIndex < rightIndex) {
            while (leftIndex < rightIndex && array[rightIndex] >= key) {
                rightIndex--;
            }
            //把右边小于基数的那个数填到之前左边挖出的坑中
            array[lowIndex] = array[rightIndex];
            while (leftIndex < rightIndex && array[leftIndex] < key) {
                leftIndex++;
            }
            //把左边大于基数的那个数填到上边空出的那个位置
            array[rightIndex] = array[leftIndex];
            lowIndex = leftIndex;
        }
        //将 key 放回剩余的坑中
        array[rightIndex] = key;
        return rightIndex;
    }
  • 方法3:前后遍历法:

设置两个指针,依次往后遍历,当遇到 > 基准值的时候,让 separateIndex下标不动,i 继续遍历,遍历到 < 基准值的时候,交换两下标的值,最后交换 separateIndex 的前一个值和基准的值
在这里插入图片描述

    private static int doublePointer(long[] array, int lowIndex, int highIndex) {
        int separateIndex = lowIndex + 1;
        for (int i = lowIndex + 1; i <= highIndex; i++) {
            if (array[i] < array[lowIndex]) {
                swap(array,i,separateIndex);
                separateIndex++;
            }
        }
        swap(array,lowIndex,separateIndex - 1);
        return separateIndex - 1;
    }
    private static void swap(long[] array,int index1,int index2) {
        long t = array[index1];
        array[index1] = array[index2];
        array[index2] = t;
    }

性能分析:
每一次 partition 时间复杂度是 O(n),一共多少层,看二叉树的高度(二叉树一般是 log(n),最坏是 n)

时间复杂度 空间复杂度
最好 平均 最坏 最好 平均 最坏
O(n*log(n)) O(n*log(n)) O(n^2) O(log(n)) O(log(n)) O(n)
数据有序

不稳定

快排优化

  • 1、partition 挖坑(小细节优化)
  • 2、数量比较少的时候,不是最快(当区间的个数低于某个阈值时(16),使用插排)
  • 3、优化选择特殊的数的方式
    (1)随机数
    (2)挑几个数,选大小为中间值的(三数取中)
  • 4、把相等的值特殊处理

7、归并排序:(重点)

分治法 思想:

  • 1、把数组平均分成两份。分别对左右两个区间,进行相同方式处理(归并排序),直到区间内个数(size = 0 / 1)
  • 2、合并左右两个有序数组

在这里插入图片描述

在这里插入图片描述

代码实现:

    public static void mergeSort(long[] array) {
        mergeSortInternal(array,0,array.length);
    }
    //区间范围时左闭右开的
    // array[lowIndex,highIndex ]
    private static void mergeSortInternal(long[] array, int lowIndex, int highIndex) {
        int size = highIndex - lowIndex;
        if (size <= 1) {
            return;
        }
        int midIndex = (highIndex + lowIndex) / 2;
        // 左区间:[lowIndex,midIndex)
        // 右区间:[midIndex,highIndex)
        mergeSortInternal(array,lowIndex,midIndex);
        mergeSortInternal(array,midIndex,highIndex);
        // 左右两个区间有序,进行合并
        mergeOrderedIntervals(array,lowIndex,midIndex,highIndex);
    }
    // 新建一个额外数组,将需要合并的两个数组中依次取出元素进行比较,在放入额外数组中
    private static void mergeOrderedIntervals(long[] array, int lowIndex, int midIndex, int highIndex) {
        int size = highIndex - lowIndex;//新数组长度
        long[] extra = new long[size];
        int leftIndex = lowIndex;//左边数组的下标
        int rightIndex = midIndex;//右边数组的下标
        int i = 0;//新数组下标
        // 两个数组中都有元素
        while (leftIndex < midIndex && rightIndex < highIndex) {
            // <= 保证稳定性
            if (array[leftIndex] <= array[rightIndex]) {
                extra[i] = array[leftIndex];
                leftIndex++;
            } else {
                extra[i] = array[rightIndex];
                rightIndex++;
            }
            i++;
        }
        // 已经有数组空了,则将另外一个数组全部放入新数组中
        if (leftIndex < midIndex) {
            //右边数组为空
            while (leftIndex < midIndex) {
                extra[i++] = array[leftIndex++];
            }
        } else {
            //左边数组为空
            while (rightIndex < highIndex) {
                extra[i++] = array[rightIndex++];
            }
        }
        //在将原数组中的值换为新数组的值
        for (int j = 0; j < size; j++) {
            array[lowIndex + j] = extra[j];
        }
    }

性能分析:

时间复杂度 空间复杂度
O(n*log(n)) O(n)
数据不敏感 数据不敏感

稳定

扩展:

排序算法都是在内存中进行的,当出现 海量数据 时,内存存不下,必须借助硬盘,采用归并排序(多路归并)

步骤:

  • 1、先将数据平均分为 n 份(每份的大小较小)
  • 2、分别对每份数据进行排序
  • 3、至此,得到 n 个分别有序的数据文件
  • 4、借助内存,进行 n 个有序数据文件的合并
    (1)将每份文件中最小的数放入内存中
    (2)将最小的数选出来,尾插到最后的有序文件中

n 多个数据以文件的形式放到磁盘上,把每个数据的第一个数选出来作为代表放到内存中,比较,谁小就放到最终的结果文件中,尾插进去

在这里插入图片描述

排序总结:

在这里插入图片描述

排序方法 最好 平均 最坏 空间复杂度 稳定性
插入排序 O(n) O(n^2) O(n^2) O(1) 稳定
希尔排序 O(n) O(n^1.3) O(n^2) O(1) 不稳定
选择排序 O(n^2) O(n^2) O(n^2) O(1) 不稳定
堆排序 O(n*log(n)) O(n*log(n)) O(n*log(n)) O(1) 不稳定
冒泡排序 O(n) O(n^2) O(n^2) 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) 稳定
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

七大排序算法详解,动图展示 +代码实现,老奶奶看了都直呼内行 的相关文章

随机推荐

  • Zookeeper实践(四)zookeeper的WEB客户端zkui使用

    前面几篇实践说明了zookeeper如何配置和部署 如何开发 因为大多是后台操作 对于维护和产品项目管理人员来说太抽象 下面介绍一下zookeeper的web客户端使用 一 环境准备 1 既然是客户端 必然得先有一个zookeeper服务
  • Class 09 - Data Frame和查看数据

    Class 09 Data Frame和查看数据 DataFrame tibbles head str colnames mutate 创建 Dataframe DataFrame 在我们开始做数据清洗或者检查数据是否存在偏差之前 我们需要
  • error: method does not override or implement a method from a supertype java:方法不会覆盖或实现超类型的方法

    错误 编译报错 error method does not override or implement a method from a supertype 即java 方法不会覆盖或实现超类型的方法 详细错误 解决方案 对超类进行继承即可
  • 交换两个变量的值的4种方法,你了解了吗?

    目录 一 引入第三变量 二 不引入第三变量 1 a a b b a b a a b 2 利用异或 3 巧妙运用优先级 总结 在我们的开发中 或者在我们平时的练习中 常常会遇到交换两个变量的值 那么如何交换两个变量的值呢 可能很多初学者都只知
  • 区块链四级知识考试

    区块链知识四级考试 考试时间30分钟 总分100分 请认真作答 出题人及监考老师 高志豪 请转载者注明 谢谢支持 一 单选题 每题5分 共40分 1 加密数字货币如果设置过短的确认时间会更容易导致什么出现 A 高效率 B 低效率 C 孤块
  • C/C++:C/C++在大数据时代的应用,以及C/C++程序员未来的发展路线

    目录 1 C C 在大数据时代的应用 1 1 C C 数据处理 1 2 C C 数据库 1 3 C C 图像处理和计算机视觉 1 3 1 导读 2 C C 程序员未来的发展路线 2 1 图导 1 C C 在大数据时代的应用 C C 在大数据
  • 三. go 常见数据结构实现原理之 silce

    目录 一 基础 几个小问题 1 题目一 2 题目二 3 题目三 4 数组和切片陷阱 二 Slice实现原理 切片的创建与底层结构 append 与切片的扩容 切片的值传递引用传递 切片再切片 特殊切片 切片的 Copy 总结 一 基础 为什
  • Path的Data生成归总

    Path的Data数据有三种生成方式 1 最简单的是用Expression Design 可以粘贴来自其它软件的矢量图形 导出时选择 文件 gt 导出 gt 导出属性 gt 格式 gt XAML Silverlight 画布 即可得到XAM
  • ES6中var let const三种变量(一文弄清楚)

    ES6中三种变量声明 ECMAScript 变量是松散类型的 变量可以用于保存任何类型的数据 有3个关键字可以声明变量 var const 和 let 其中 var在ECMAScript 的所有版本中都可以使用 而 const 和 let
  • 判断密码是否包含键盘连续字母

    新增内容为增加键盘列排序检测 原理 用两个与传入密码长度相等的一维数组 Row行数组 Column列数组 按密码顺序在二维键盘数组中查找每个字符 找到了则用 一维行列数组分别存放密码中每个字符的行号和列号 然后循环分析行号和列号是否满足二维
  • Alist V3 “全新版本“ 使用 安装/启动 教程!

    Introduction AList文档 nn ci 如果安装了Docker 请确保将其添加到系统的PATH变量中 您可以通过在终端中运行命令echo PATH来检查这一点 输出应该包括码头工人二进制文件的路径 通常是 usr bin 码头
  • RuntimeError: Pin memory thread exited unexpectedly 或 OSError: [Errno 9] Bad file descriptor 的解决方法

    遇到这几个错 大多是因为内存不足 我的解决方法 方案一 pin memory 不变 pin memory的值仍然为True 不需要改为False 而是把batch size 和 num worker的值分别调成 batch size 2 n
  • 10-20-010-简介-目录-Kylin目录详解

    文章目录 1 视界 1 Kylin二进制源码目录解析 2 HDFS 目录结构 2 1 cardinality 2 2 coprocessor 2 3 kylin job id 2 4 resources 2 5 jdbc resources
  • 基于vue构建lib + 类型声明文件

    构建lib 通过创建一个vue项目 实现自己的组件或工具类 然后创建一个index ts作为导出的入口文件 接下来就可以通过Vue CLIkais构建自己的lib了 index ts export Fns from utils fn 创建了
  • 基于STM32的阿里云物联网项目实战

    引言 之前自学了一些关于阿里云物联网项目的开发 收获颇丰 但是总感觉网上的东西太散了 需要自己去不停的收集整理 于是在项目结束后决心自己写一篇比较具有实用性的指导文档 需要声明的是本文档只适合像我一样的新人小白 路过的大佬不喜勿喷 如有疏漏
  • java开发实习生-面试

    redis常用命令 登录 redis cli p a 检查key是否存在 exists key 设置和获取一个键的值 set get 删除键对 del key 同时获取多个 mget linux常用命令 list 查看文件 cd 切换目录
  • 持续交付与敏捷开发的历史

    软件中的 持续交付 不仅仅是一个热门话题 软件的持续交付是软件开发实践和客户保留的圣杯 这也是DevOps今天是如此热门的概念的原因 要了解持续交付的重要性 你需要了解敏捷的历史以及敏捷方法的来源 敏捷软件开发的历史并不是从敏捷宣言开始的
  • Spark:failed to launch: nice -n 0 /opt/spark/bin/spark-class org.apache.spark.deploy.worker.

    查看真实的报错结果 bash 4 4 cat spark org apache spark deploy master Master 1 spark manager 77d4d75859 kbr5v out nohup can t exec
  • 2023-9-11 台阶-Nim游戏

    题目链接 台阶 Nim游戏 include
  • 七大排序算法详解,动图展示 +代码实现,老奶奶看了都直呼内行

    七种 基于比较 的排序 1 插入排序 元素少时插排最快 2 希尔排序 3 选择排序 4 堆排序 5 冒泡排序 6 快速排序 重点 7 归并排序 重点 排序总结 1 插入排序 元素少时插排最快 1 有序区间 黄色区间 无序区间 蓝色区间 每次