【数据结构】七大排序算法详解Java

news2025/1/19 11:02:30

目录

1.排序算法分类

 1.直接选择排序

代码展示:

2.直接插入排序

核心思路:

代码展示:

​编辑

3.希尔排序

 思路分析:

代码展示:

 4.归并排序

代码展示:

5.快速排序(挖坑法)

思路分析:

代码展示:

5.快速排序(<算法四>的分区方法)

思路分析:如图所示

 代码展示:

代码展示:

1.排序算法分类

 1.直接选择排序

核心思路:每次在无序区间中选择最小值与第一个元素交换,直到整个数组有序(不稳定)

代码展示:

    public static void insertSort(int [] arr){
        for(int i = 0; i < arr.length; i++){
            int min = i;
            int j = 0;
            for(j = i; j < arr.length; j++){
                if(arr[j] < arr[min]){
                    min = j;
                }
            }
            swap(arr,i,min);
        }
    }

    private static void swap(int[] arr, int i, int j){
        int temp = arr[i];
        arr[i] = arr[j];
        arr[j] = temp;
    }

2.直接插入排序

在近乎有序的数组上性能非常好

核心思路:

每次从无序区间的第一个位置选取元素,插入到有序区间的合适位置,直至数组有序

代码展示:

    public static void selectionSort(int[] arr){
        for(int i = 1; i < arr.length; i++){
            for(int j = i; j >= 1 && arr[j] < arr[j-1]; j--){
                swap(arr,j,j-1);
            }
        }
    }

我们生成随机数组,并运用排序算法对他们分别进行排序,计算并比较插入,选择,冒泡,原地堆排序的排序速度,结果如下:

 public static void main(String[] args) {
        int n = 10_000;
        int[] arr = ArrayUtil.generateRandomArray(n,0,Integer.MAX_VALUE);
        int[] arr1 = ArrayUtil.arrCopy(arr);
        int[] arr2 = ArrayUtil.arrCopy(arr);
        int[] arr3 = ArrayUtil.arrCopy(arr);
        int[] arr4 = ArrayUtil.arrCopy(arr);
        //
        long start = System.nanoTime();
        selectionSort(arr);
        long end = System.nanoTime();
        if(isSorted(arr)){
            System.out.println("选择排序共耗时:"+(end - start)/1000000.0+"ms");
        }

        start = System.nanoTime();
        insertSort(arr1);
        end = System.nanoTime();
        if(isSorted(arr1)){
            System.out.println("插入排序共耗时:"+(end - start)/1000000.0+"ms");
        }

        start = System.nanoTime();
        bubbleSort(arr2);
        end = System.nanoTime();
        if(isSorted(arr2)){
            System.out.println("冒泡排序共耗时:"+(end - start)/1000000.0+"ms");
        }

        start = System.nanoTime();
        heapSort(arr3);
        end = System.nanoTime();
        if(isSorted(arr3)){
            System.out.println("原地堆排序共耗时:"+(end - start)/1000000.0+"ms");
        }

    }

可以看到在这四种排序算法中,原地堆排序最快,冒泡排序最慢

3.希尔排序

 思路分析:

有人发现插入排序在数组内元素近乎有序的时候性能极好,所以发明了希尔排序

将原数组分成若干个子数组,先将子数组内部调整为有序,当最终分组长度为1时,整个数组接近有序,最后再来一次插入排序(此时性能最好)即可~

代码展示:

    public static void shellSort(int[] arr){
        //先分组
        int gap = arr.length >> 1;
        if(gap > 1){
            insertSort(arr,gap);
            gap >>= 1;
        }
        //此时整体进行一次插入排序
        insertSort(arr,1);
    }

    private static void insertSort(int[] arr,int gap){
        //按照gap进行插入排序
        for(int i = gap; i < arr.length; i++){
            for(int j = i; j - gap >= 0 && arr[j] < arr[j - gap]; j -= gap){
                swap(arr,j,j-gap);
            }
        }
    }

结果可以看到,希尔排序比插入排序的速度要快,在这里选择比希尔排序要快是java编译器的原因,具体我也不清楚,等我在学一段时间来解答吧~

 4.归并排序

1.不断地将原数组一分为二,直到拆分后的子数组只剩下一个元素

2.不断地将两个连续的有序子数组合并为一个大的有序数组,直到合并后的数组长度等于元数组的长度,即排序完成~

代码展示:

优化一:当子数组内元素小于64个元素时,使用直接插入排序会更高效

优化二:当两个子数组连接已经有序的时候,可以不用在进行排序

    //归并排序
    public static void mergeSort(int[] arr){
        mergeSortInternal(arr,0,arr.length - 1);
    }


    private static void mergeSortInternal(int[] arr, int l, int r) {
        //优化一
        if(r - l <= 64){
            insertionSort(arr,l,r);
            return;
        }
        int mid = l + ((r - l) >> 1);
        mergeSortInternal(arr,l,mid);
        mergeSortInternal(arr,mid + 1,r);
        //优化二
        if(arr[mid] > arr[mid + 1]){
            merge(arr,l,mid,r);
        }
    }

    private static void insertionSort(int[] arr, int l, int r) {
        for(int i = l + 1; i <= r; i++) {
            for (int j = i; j > l && arr[j] < arr[j-1]; j--) {
                swap(arr,j,j - 1);
            }
        }
    }

    private static void merge(int[] arr, int l, int mid, int r) {
        //创建一个和原数组长度一样的新数组
        int [] aux = new int[r - l + 1];
        System.arraycopy(arr,l,aux,0,r - l + 1);
        int i = l;
        int j = mid + 1;
        for (int k = l; k <= r; k++) {
            if(i > mid){
                arr[k] = aux[j - l];
                j++;
            }else if(j > r){
                arr[k] = aux[i - l];
                i++;
            }else if(aux[i - l] <= aux[j - l]){
                arr[k] = aux[i - l];
                i++;
            }else {
                arr[k] = aux[j - l];
                j++;
            }
        }

    }

 结果可以看到,当数据量在10000时,归并排序比选择,插入,希尔排序都要快,

5.快速排序(挖坑法)

思路分析:

每次从无序数组中选取一个元素作为分区点(pivot),将原集合中所有小于pivo的元素放在该元素的左侧,将所有大于pivot的元素放在该元素的右侧,继续在左侧和右侧区间重复此过程,直到整个数组有序

代码展示:

优化一:当子数组内元素小于64个元素时,使用直接插入排序会更高效

    public static void quickSortHole(int[] arr) {
        quickSortHoleInternal(arr,0,arr.length - 1);
    }

    private static void quickSortHoleInternal(int[] arr, int l, int r) {
        if(r - l <= 64){
            insertionSort(arr,l,r);
            return;
        }
        int p = partitionByHole(arr,l,r);
        quickSortHoleInternal(arr,l,p - 1);
        quickSortHoleInternal(arr,p + 1,r);
    }

    private static int partitionByHole(int[] arr, int l, int r) {
        int pivot = arr[l];
        int i = l;
        int j = r;
        while (i < j){
            while (i < j && arr[j] >= pivot) {
                j--;
            }
            arr[i] = arr[j];
            while (i < j && arr[i] <= pivot) {
                i++;
            }
            arr[j] = arr[i];
        }
        arr[i] = pivot;
        return i;
    }

结果可以看到,在100 0000数量级下,快排比堆排快了接近一倍的速度

 但是呢,挖坑这种方法下的堆排有一个弊端,就是在近乎有序的数组上,时间复杂度会下降为O(n),出现这种情况是我主要原因是我们每次选择pivot的时候选择的都是最左边,所以我们只需要每次在无序数组的下标中随机选择一个与最左边进行交换,这样就可以避免在近乎有序的数组上快排性能下降的问题

代码展示:

    public static void quickSortHole1(int[] arr) {
        quickSortHoleInternal(arr,0,arr.length - 1);
    }

    private static void quickSortHoleInternal1(int[] arr, int l, int r) {
        if(r - l <= 64){
            insertionSort(arr,l,r);
            return;
        }
        int p = partitionByHole(arr,l,r);
        // 继续在两个子区间上进行快速排序
        quickSortHoleInternal(arr,l,p - 1);
        quickSortHoleInternal(arr,p + 1,r);
    }

    private static int partitionByHole1(int[] arr, int l, int r) {
        Random random = new Random();
        int indexRandom = random.nextInt();
        swap(arr,indexRandom,l);
        int pivot = arr[l];
        int i = l;
        int j = r;
        while (i < j){
            while (i < j && arr[j] >= pivot) {
                j--;
            }
            arr[i] = arr[j];
            while (i < j && arr[i] <= pivot) {
                i++;
            }
            arr[j] = arr[i];
        }
        arr[i] = pivot;
        return i;
    }

 ​​​​​​​​​​​​​​​​​​​​​​​​​​​​yH5BAAAAAAALAAAAAAOAA4AAAIMhI+py+0Po5y02qsKADs=wAAACH5BAEKAAAALAAAAAABAAEAAAICRAEAOw==

可以看到,在10000数据量的数组中,优化后的快排性能提升了不少

5.快速排序(<算法四>的分区方法)

思路分析:如图所示

 代码展示:

    public static void quickSortFour(int[] arr){
        quickSortFourInternal(arr,0, arr.length - 1);
    }

    private static void quickSortFourInternal(int [] arr, int l, int r){
        if(r - l <= 64) {
            insertionSort(arr,l,r);
            return;
        }
        int p = partitionByFour(arr,l,r);
        quickSortFourInternal(arr,l,p - 1);
        quickSortFourInternal(arr,p + 1,r);
    }

    private static int partitionByFour(int[] arr, int l, int r) {
        int indexRandom = ThreadLocalRandom.current().nextInt(l,r);
        swap(arr,l,indexRandom);
        int pivot = arr[l];
        int j = l;
        for (int i = l + 1; i <= r; i++) {
            if(arr[i] < pivot) {
                swap(arr,i,j+1);
                j++;
            }
        }
        swap(arr,l,j);
        return  j;
    }

上面的快排虽说经过优化对近乎有序的数组进行排序会性能下降的问题解决了,可在当数组中·有大量重复元素时,快排性能依然会下降

可以看到在100000数据量的情况下,原地堆排比快排序块了将近三四倍

所以我们提出了新的方法(三路快排)

 

代码展示:

    public static void quicSortByThree(int[] arr) {
        quicSortByThreeInternal(arr,0,arr.length - 1);
    }

    private static void quicSortByThreeInternal(int[] arr, int l, int r) {
        if(r - l <= 64) {
            insertionSort(arr,l,r);
            return;
        }
        int [] res = partitionByThree(arr,l,r);
        quicSortByThreeInternal(arr,l,res[0] - 1);
        quicSortByThreeInternal(arr,res[1] + 1,r);
    }

    private static int[] partitionByThree(int[] arr, int l, int r) {
        int indexRandom = ThreadLocalRandom.current().nextInt(l,r);
        swap(arr,indexRandom,l);
        int pivot = arr[l];
        int lt = l;
        int gt = r + 1;
        for(int i = l + 1; i < gt; i++) {
            if(arr[i] < pivot) {
                swap(arr,lt + 1, i);
                lt++;
            }else if(arr[i] > pivot) {
                swap(arr,gt - 1, i);
                gt--;
                i--;
            }
        }
        swap(arr,l,lt);
        return new int[]{lt, gt - 1};
    }

数组中有大量重复元素时,可以看见三路快排的速度是最快的

 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/414839.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

OJ系统刷题 第十篇

13444 - 求出e的值 时间限制 : 1 秒 内存限制 : 128 MB 利用公式e11/!1​1/2!​1/3!​...1/n!​&#xff0c;求e的值&#xff0c;要求保留小数点后10位。 输入 输入只有一行&#xff0c;该行包含一个整数n&#xff08;2≤n≤15&#xff09;&#xff0c;表示计算e时累加到1/…

计算机操作系统第四版第六章输入输出系统—课后题答案

1.试说明I/O系统的基本功能。 隐藏物理设备的细节、与设备的无关性、提高处理机和I/O设备的利用率、对I/O设备进行控制、确保对设备的正确共享、错误处理 2.简要说明I/O软件的4个层次的基本功能。 用户层I/O软件&#xff1a;实现与用户交互的接口&#xff0c;用户可直接调用该层…

4.8、socket介绍

4.8、socket1. socket介绍1. socket介绍 所谓 socket&#xff08;套接字&#xff09;&#xff0c;就是对网络中不同主机上的应用进程之间进行双向通信的端点的抽象。一个套接字就是网络上进程通信的一端&#xff0c;提供了应用层进程利用网络协议交换数据的机制。从所处的地位…

阿里云centos7搭建ChatGPT网站

需要的环境 有一台外网的服务器 拥有一个OpenAI API Key Centos7&#xff08;其他服务器也行&#xff09; nodejs 前端github上 大神写的 https://github.com/ddiu8081/chatgpt-demo/ 一.安装node.js centos7 安装node.js 二.安装pnpm npm i -g pnpm三.下载web前端项目从…

【2023最新】超详细图文保姆级教程:App开发新手入门(3)

上文回顾&#xff0c;我们已经完成了一个应用项目创建、导入、代码更新、代码同步和代码提交&#xff0c;本章继续我们的新手开发之旅&#xff0c;讲述一下如何将开发完成的应用进行编译&#xff0c;生成可供他人安装、可上架的应用安装包。 6 应用打包 应用打包&#xff0c;…

27个必备的Python技巧,你一定要知道!

目录 01. 为什么使用缩进来分组语句&#xff1f; Guido van Rossum 认为使用缩进进行分组非常优雅&#xff0c;并且大大提高了普通 Python 程序的清晰度。大多数人在一段时间后就学会并喜欢上这个功能。 由于没有开始/结束括号&#xff0c;因此解析器感知的分组与人类读者之间…

免费英文在线翻译-英文自动翻译

免费的自动翻译器 作为一款免费的自动翻译器&#xff0c;我们的产品可以为全球用户提供高质量、高效率的翻译服务&#xff0c;帮助他们更好地沟通和交流。 现在&#xff0c;随着数字化的进一步发展&#xff0c;人们之间的跨文化交流越来越频繁。然而&#xff0c;语言偏差和文…

22-JavaScript

目录 1.什么是JavaScript&#xff1f; 1.1.JS、HTML、CSS关系 1.2.JS是如何运行的&#xff1f; 2.JS前置知识 2.1.第一个JS程序 PS&#xff1a;JS书写位置 2.2.JS书写格式 2.2.1.行内格式 ​2.2.2.内嵌格式 ​2.2.3.外部格式 2.3.注释&#xff08;script标签中&…

【软件测试】测试用例

目录 &#x1f337;1. 测试用例的基本要素 &#x1f337;2. 测试用例的设计方法 &#x1f333;2.1 基于需求进行测试用例的设计 ⭐️&#xff08;1&#xff09;功能需求测试分析 ⭐️&#xff08;2&#xff09;非功能需求测试分析 &#x1f333;2.2 具体的设计方法 &#…

【Python搞笑游戏】因蔡徐坤打篮球动作超火,被某程序员写成了一款游戏,画面美到不敢看,成功学到了精髓~(附源码免费)

导语 之前网络最火的梗&#xff0c;非“C徐坤打篮球”莫属。个人感觉&#xff0c;只有多年前的“春哥纯爷们”堪与匹敌&#xff01; 虽然说C徐坤打篮球是一个老梗了&#xff0c;但是确实非常搞笑&#xff0c;今天就跟着小编一起来回忆一下吧&#xff01; “我是练习两年半的…

qt - 隐式共享与d-pointer技术

文章目录前言1. 隐式共享2. d-pointer在隐式共享中的应用3. 二进制代码兼容4. d-pointer模式的实现5. QObject中的d-pointer前言 一般情况下&#xff0c;一个类的多个对象所占用的内存是相互独立的。如果其中某些对象数据成员的取值完全相同&#xff0c;我们可以令它们共享一块…

ESP32学习二-更新Python版本(Ubuntu)

一、简介 在一些场景里边&#xff0c;因为Python的版本过低&#xff0c;导致一些环境无法安装。这里来介绍以下&#xff0c;如何升级自己已安装的Python版本。例如如下情况&#xff1a; 二、实操 1.查看本地版本 python --version 2.添加源 sudo add-apt-repository ppa:jona…

FPGA时序知识点(基本方法总结就两点:1.降低时钟频率2.减小组合逻辑延迟(针对Setup Slack公式来的)

1.我们说的所有时序分析都是建立在同步电路的基础上的&#xff0c;异步电路不能做时序分析&#xff08;或者说只能做伪路径约束&#xff08;在设伪路径之前单bit就打拍&#xff0c;多bit就异步fifo拉到目的时钟域来&#xff09;&#xff09;。——FPGA 设计中寄存器全部使用一个…

逐一解释一下四个 “内存屏障” 是什么

什么是内存屏障&#xff1f;硬件层⾯&#xff0c;内存屏障分两种&#xff1a;读屏障&#xff08;Load Barrier&#xff09;和写屏障&#xff08;Store Barrier&#xff09;。内存屏障有两个作⽤&#xff1a; 阻⽌屏障两侧的指令重排序&#xff1b;强制把写缓冲区/⾼速缓存中的…

Matplotlib绘图

1.散点图 X1 [[3.393533211, 2.331273381], [3.110073483, 1.781539638], [1.343808831, 3.368360954], [3.582294042, 4.679179110], [2.280362439, 2.866990263], [7.423436942, 4.696522875], [5.745051997, 3.533989803], [9.172168622, 2.51…

面试官:说说MySQL主从复制原理

MySQL Replication&#xff08;主从复制&#xff09;是指数据变化可以从一个MySQL Server被复制到另一个或多个MySQL Server上&#xff0c;通过复制的功能&#xff0c;可以在单点服务的基础上扩充数据库的高可用性、可扩展性等。 一、背景 MySQL在生产环境中被广泛地应用&…

第十四届蓝桥杯题解

声明&#xff1a;以下都无法确定代码的正确性&#xff0c;是赛时代码&#xff0c;希望大家见谅&#xff01;思路可以参考&#xff0c;等后续可以评测之后再去修改博客内错误&#xff0c;也希望大家能够指正错误&#xff01; 试题A&#xff1a;日期统计 分析&#xff1a;这道题…

Linux工具——yum和vim

目录&#x1f34f;Linux软件包管理器-yum&#x1f34e;yum简介&#x1f34e;rzsz工具&#x1f34e;注意事项&#x1f34e;软件包查看&#x1f34e;如何安装和卸载软件&#x1f34f;Linux编辑器-vim&#x1f34e;vim的基本概念&#x1f34e;vim的基本操作&#x1f34e;vim正常模…

uniapp中canvas绘制图片内容空白报错原因总结

uniapp中canvas绘制图片内容空白报错原因总结&#xff0c;看完需要10分钟 问题图: 效果图&#xff1a; 目录 &#x1f9e8;&#x1f9e8;&#x1f9e8;首先定义画布canvas canvas画布初始值没有&#xff0c;导致没有绘制成功 &#x1f9e8;&#x1f9e8;&#x1f9e8;2.绘制图…

【二叉树】全家桶-管饱,你敢吃吗?

【二叉树扩展学习】&#x1f4af;&#x1f4af;&#x1f4af;1.【二叉树的创建】2.【二叉树的销毁】3.【二叉树的前序遍历】4.【二叉树的中序遍历】5.【二叉树的后序遍历】6.【二叉树的层序遍历】7.【二叉树的高度】8.【二叉树结点的个数】9.【第K层二叉树的结点个数】10.【二叉…