[数据结构]排序 --2

news2025/4/15 16:56:37

目录

8、快速排序

8.1、Hoare版

8.2、挖坑法

8.3、前后指针法

9、快速排序优化

9.1、三数取中法

9.2、采用插入排序

10、快速排序非递归

11、归并排序

12、归并排序非递归

13、排序类算法总结

14、计数排序

15、其他排序

15.1、基数排序

15.2、桶排序


8、快速排序

快速排序是Hoare于1962年提出的一种二叉树结构的交换排序方法

基本思想:任取待排序元素序列中的某元 素作为基准值,按照该排序码将待排序集合分割成两子序列,左子序列中所有元素均小于基准值,右子序列中所有 元素均大于基准值,然后最左右子序列重复该过程,直到所有元素都排列在相应位置上为止

8.1、Hoare版

1. 把第一个值作为基准值 pivot

2. right 从右边走,遇到比 pivot 大的就停下;left 从左边走,遇到比 pivot 小的就停下

3. 交换 left 和 right 的值

4. left 和 right 继续走,直到 left 和 right 相遇

5. 相遇的位置就是要找的位置,把基准值与该位置交换

    public static void quickSort(int[] array) {
        quick(array,0,array.length-1);
    }
    private static void quick(int[] array,int start,int end) {
        if(start >= end) {
            return;
        }
        int pivot = partitionHoare(array,start,end);
        quick(array,start,pivot-1);
        quick(array,pivot+1,end);
    }
    private static int partitionHoare(int[] array, int left, int right) {
        int tmp = array[left];
        int pivot = left;
        while (left < right) {
            // 单独的循环 不能一直减到超过最左边的边界
            while (left < right && array[right] >= tmp) {
                right--;
            }
            while (left < right && array[left] <= tmp) {
                left++;
            }
            swap(array,left,right);
        }
        swap(array,pivot,left);
        return left;
    }

两个问题:

1. 为什么 array[right] >= tmp 必须带等于号

        可能会出现 left 和 right 无限交换的死循环

2. 为什么从 right 先走而不是 left

        如果 left 先走可能会出现相遇的是比基准大的数据,最后把大的数据放到了最前面

快速排序总结:

1. 快速排序整体的综合性能和使用场景都是比较好的,所以才敢叫快速排序

2. 时间复杂度:O(N*logN)

        最好的情况下:O(N*logN)

        最坏情况下:O(N^2)  -- 逆序/有序

3. 空间复杂度:O(logN)  -- 递归了logN层

        最好的情况下:O(logN)

        最坏情况下:O(N)   -- 逆序/有序

4. 稳定性:不稳定

8.2、挖坑法

1. 把第一个值拿出来作为基准值 tmp,第一个值的位置就是第一个坑

2. right 从右边走,遇到比 pivot 大的就停下,然后把这个值放到上一个坑里,right 就形成了新的坑

3. left 从左边走,遇到比 tmp 小的就停下,然后把这个值放到坑里,left 就是新的坑

4. 循环2、3,直到 left 和 right 相遇

5. 相遇的位置就是要找的位置,把基准值放在这个位置

    public static void quickSort(int[] array) {
        quick(array,0,array.length-1);
    }
    private static void quick(int[] array,int start,int end) {
        if(start >= end) {
            return;
        }
        int pivot = partitionHole(array,start,end);
        quick(array,start,pivot-1);
        quick(array,pivot+1,end);
    }
    private static int partitionHole(int[] array, int left, int right) {
        int tmp = array[left];
        while (left < right) {
            // 单独的循环 不能一直减到超过最左边的边界
            while (left < right && array[right] >= tmp) {
                right--;
            }
            array[left] = array[right];

            while (left < right && array[left] <= tmp) {
                left++;
            }
            array[right] = array[left];
        }
        array[left] = tmp;
        return left;
    }

8.3、前后指针法

1. 定义两根指针cur和prev,初始位置如下图

2. cur开始往后走,如果遇到比key小的值,则++prev,然后交换prev和cur指向的元素,再++cur,如果遇到比key大的值,则只++cur

3. 当cur访问过最后一个元素后,将key的元素与prve访问的元素交换位置。cur访问完整个数组后的各元素位置如下图

private static int partition(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;
}

总结:
1. Hoare
2. 挖坑法
3. 前后指针法
这3种方式  每次划分之后的前后顺序 有可能是不一样的

9、快速排序优化

优化的出发点:减少递归的次数

9.1、三数取中法

既然有序数组或者有序数组片段会使效率下降,我们就可以让基准值每次都取大小靠中的数,然后在进行快速排序这样就可以避免了。但不是完全避免,只是减少了最坏情况出现的概率,最坏情况还是O(n²),但有效提升了运行效率,主要提升的部分是数组中有序的数组片段,减少了循环次数。

    // 求中位数的下标
    private static int middleNum(int[] array,int left,int right) {
        int mid = (left+right)/2;
        if(array[left] < array[right]) {
            if(array[mid] < array[left]) {
                return left;
            }else if(array[mid] > array[right]) {
                return right;
            }else {
                return mid;
            }
        }else {
            //array[left] > array[right]
            if(array[mid] < array[right]) {
                return right;
            }else if(array[mid] > array[left]) {
                return left;
            }else {
                return mid;
            }
        }
    }
    private static void quick(int[] array,int start,int end) {
        if(start >= end) {
            return;
        }

        //1 2 3 4 5 6 7
        int index = middleNum(array,start,end);
        swap(array,index,start);
        //4 2 3 1 5 6 7

        int pivot = partition(array,start,end);
        quick(array,start,pivot-1);
        quick(array,pivot+1,end);
    }

9.2、采用插入排序

往往一棵树的最后两层的结点占整棵树的绝大多数,所以当递归到一定深度时,采用直接插入排序

    public static void insertSort(int[] array,int left,int right) {
        for (int i = left+1; i <= right; i++) {
            int tmp = array[i];
            int j = i-1;
            for (; j >= left ; j--) {
                if(array[j] > tmp) {
                    array[j+1] = array[j];
                }else {
                    break;
                }
            }
            array[j+1] = tmp;
        }
    }
    private static void quick(int[] array,int start,int end) {
        if(start >= end) {
            return;
        }
        if(end - start + 1 <= 15) {
            insertSort(array, start, end);
            return;
        }

        //1 2 3 4 5 6 7
        int index = middleNum(array,start,end);
        swap(array,index,start);
        //4 2 3 1 5 6 7

        int pivot = partition(array,start,end);
        quick(array,start,pivot-1);
        quick(array,pivot+1,end);
    }

10、快速排序非递归

1. 先调用partition方法找到基准
2. 基准左边和右边有没有2个及以上个元素,有就把下标放到栈中

3. 判断栈空不空,不空出栈2个,第一个是新的end,第二个是新的start

4. 栈不为空时,循环执行上述1.2.3

    public static void quickSortNor(int[] array) {
        int start = 0;
        int end = array.length-1;
        Stack<Integer> stack = new Stack<>();
        int pivot = partition(array,start,end);
        if(pivot > start+1) {
            stack.push(start);
            stack.push(pivot-1);
        }
        if(pivot+1 < end) {
            stack.push(pivot+1);
            stack.push(end);
        }
        while (!stack.isEmpty()) {
            end = stack.pop();
            start = stack.pop();
            pivot = partition(array,start,end);
            if(pivot > start+1) {
                stack.push(start);
                stack.push(pivot-1);
            }
            if(pivot+1 < end) {
                stack.push(pivot+1);
                stack.push(end);
            }
        }
    }

11、归并排序

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

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

    private static void mergeSortFun(int[] array,int start,int end) {
        if(start >= end) {
            return;
        }
        int mid = (start+end)/2;
        mergeSortFun(array,start,mid);
        mergeSortFun(array,mid+1,end);
        //合并
        merge(array,start,mid,end);
    }

    private static void merge(int[] array, int left, int mid, int right) {
        // s1,e1,s2,e2 可以不定义,这样写为了好理解
        int s1 = left;
        int e1 = mid;
        int s2 = mid+1;
        int e2 = right;
        //定义一个新的数组
        int[] tmpArr = new int[right-left+1];
        int k = 0;//tmpArr数组的下标
        //同时满足 证明两个归并段 都有数据
        while (s1 <= e1 && s2 <= e2) {
            if(array[s1] <= array[s2]) {
                tmpArr[k++] = array[s1++];
            }else {
                tmpArr[k++] = array[s2++];
            }
        }
        while (s1 <= e1) {
            tmpArr[k++] = array[s1++];
        }
        while (s2 <= e2) {
            tmpArr[k++] = array[s2++];
        }
        //把排好序的数据 拷贝回原来的数组array当中
        for (int i = 0; i < tmpArr.length; i++) {
            array[i+left] = tmpArr[i];
        }
    }

两个有序数组合并为一个有序数组代码:

public static int[] mergeArray(int[] arrayl,int[] array2) {
    // 注意判断参数
    int[] tmp = new int[array1.length+array2.length];
    int k = 0;
    int s1 = 0;
    int el = array1.length-1;
    int s2 = 0;
    int e2 = array2.length-1;
    while (s1 <= el && s2 <= e2) {
        if(array1[s1] <= array2[s2]) {
            tmp[k++] = array1[s1++];
        }else {
            tmp[k++]=array2[s2++];
        }
    }
    while (s1 <= el) {
        tmp[k++] = array1[s1++];
    }
    while (s2 <= e2) {
        tmp[k++]= array2[s2++];
    }
    return tmp;
}

归并排序总结
1. 归并的缺点在于需要O(N)的空间复杂度,归并排序更多的是解决在磁盘中的外排序问题。
2. 时间复杂度:O(N*logN)
3. 空间复杂度:O(N)

        ‌递归调用栈空间:O(logN)

        合并操作空间:O(N)
4. 稳定性:稳定


海量数据的排序

外部排序:排序过程需要在磁盘等外部存储进行的排序
前提:内存只有 1G,需要排序的数据有 100G
因为内存中因为无法把所有数据全部放下,所以需要外部排序,而归并排序是最常用的外部排序
1. 先把文件切分成 200 份,每个 512 M
2. 分别对 512 M 排序,因为内存已经可以放的下,所以任意排序方式都可以
3. 进行2路归并,同时对 200 份有序文件做归并过程,最终结果就有序了(在外部存储进行)

12、归并排序非递归

1. 找到 left、mid、right 的位置和关系,然后调用merge合并

2. 定义 gap 表示当前的分组是每组几个数据

    public static void mergeSortNor(int[] array) {
        int gap = 1;//每组几个数据
        while (gap < array.length) {
            for (int i = 0; i < array.length; i = i+gap*2) {
                int left = i;
                
                // mid、right 可能会越界
                int mid = left+gap-1;
                int right = mid+gap;

                if(mid >= array.length) {
                    mid = array.length-1;
                }
                if(right >= array.length) {
                    right = array.length-1;
                }
                merge(array,left,mid,right);
            }
            gap*=2;
        }
    

13、排序类算法总结

14、计数排序

思想:计数排序又称为鸽巢原理,是对哈希直接定址法的变形应用。 操作步骤:
1. 统计相同元素出现次数
2. 根据统计的结果将序列回收到原来的序列中

具体实现:

1.申请一个数组count,大小为待排序数组array的范围 M

2.遍历待排序数组array,把数字对应的count数组的下标内容进行++

3.遍历计数数组count 写回到待排序数组array,此时需要注意写的次数和元素值要一样

4. 最后数组array中存储的就是有序的序列

    public static void countSort(int[] array) {
        //求数组的最大值 和 最小值  O(N)
        int minVal = array[0];
        int maxVal = array[0];
        for (int i = 1; i < array.length; i++) {
            if(array[i] < minVal) {
                minVal = array[i];
            }
            if(array[i] > maxVal) {
                maxVal = array[i];
            }
        }
        //确定计数数组的 长度
        int len = maxVal - minVal + 1;
        int[] count = new int[len];
        //遍历array数组 把数据出现的次数存储到计数数组当中   O(N)
        for (int i = 0; i < array.length; i++) {
            count[array[i]-minVal]++;
        }
        //计数数组已经存放了每个数据出现的次数
        //遍历计数数组 把实际的数据写回array数组  O(M) M表示数据范围
        int index = 0;
        for (int i = 0; i < count.length; i++) {
            while (count[i] > 0) {
                //这里需要重写写回array 意味着得从array的0位置开始写
                array[index] = i+minVal;
                index++;
                count[i]--;
            }
        }
    }

计数排序的特性总结
1.计数排序是非基于比较的排序

2. 计数排序在数据范围集中时,效率很高,但是适用范围及场景有限;计数排序的场景:指定范围内的数据

3. 时间复杂度:O(MAX(N,M))   M表示数据范围
4. 空间复杂度:O(M)
5. 稳定性:稳定;上述代码是不稳定的写法

15、其他排序

15.1、基数排序

基数排序(Radix Sort)是一种非比较型的排序算法,它通过逐位比较元素的每一位(从最低位到最高位)来实现排序。基数排序的核心思想是将整数按位数切割成不同的数字,然后按每个位数分别进行排序。基数排序的时间复杂度为 O(d*(n+r)),其中 n 为元素个数,d 是最大数字的位数,r 为基数(桶的个数)

时间复杂度分析:

分配依次将每个数放到对应的桶中 O(n)

收集依次将每个桶里的元素拿出来 O(r)  (桶里的元素是用链表连接的)

每轮:分配+收集 O(n+r)

如果最大的数字有d位,就需要排d轮

所以时间复杂度为:O(d*(n+r))

15.2、桶排序

算法思想:划分多个范围相同的区间,每个子区间自排序,最后合并

计数排序、基数排序、桶排序 都是非基于比较的排序

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

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

相关文章

第16届蓝桥杯c++省赛c组个人题解

偷偷吐槽&#xff1a; c组没人写题解吗&#xff0c;找不到题解啊 P12162 [蓝桥杯 2025 省 C/研究生组] 数位倍数 题目背景 本站蓝桥杯 2025 省赛测试数据均为洛谷自造&#xff0c;与官方数据可能存在差异&#xff0c;仅供学习参考。 题目描述 请问在 1 至 202504&#xff…

记一次InternVL3- 2B 8B的部署测验日志

1、模型下载魔搭社区 2、运行环境&#xff1a; 1、硬件 RTX 3090*1 云主机[普通性能] 8核15G 200G 免费 32 Mbps付费68Mbps ubuntu22.04 cuda12.4 2、软件&#xff1a; flash_attn&#xff08;好像不用装 忘记了&#xff09; numpy Pillow10.3.0 Requests2.31.0 transfo…

使用SSH解决在IDEA中Push出现403的问题

错误截图&#xff1a; 控制台日志&#xff1a; 12:15:34.649: [xxx] git -c core.quotepathfalse -c log.showSignaturefalse push --progress --porcelain master refs/heads/master:master fatal: unable to access https://github.com/xxx.git/: The requested URL return…

Tauri 2.3.1+Leptos 0.7.8开发桌面应用--Sqlite数据库的写入、展示和选择删除

在前期工作的基础上&#xff08;Tauri2Leptos开发桌面应用--Sqlite数据库操作_tauri sqlite-CSDN博客&#xff09;&#xff0c;尝试制作产品化学成分录入界面&#xff0c;并展示数据库内容&#xff0c;删除选中的数据。具体效果如下&#xff1a; 一、前端Leptos程序 前端程序主…

《车辆人机工程-》实验报告

汽车驾驶操纵实验 汽车操纵装置有哪几种&#xff0c;各有什么特点 汽车操纵装置是驾驶员直接控制车辆行驶状态的关键部件&#xff0c;主要包括以下几种&#xff0c;其特点如下&#xff1a; 一、方向盘&#xff08;转向操纵装置&#xff09; 作用&#xff1a;控制车辆行驶方向…

使用多进程和 Socket 接收解析数据并推送到 Kafka 的高性能架构

使用多进程和 Socket 接收解析数据并推送到 Kafka 的高性能架构 在现代应用程序中&#xff0c;实时数据处理和高并发性能是至关重要的。本文将介绍如何使用 Python 的多进程和 Socket 技术来接收和解析数据&#xff0c;并将处理后的数据推送到 Kafka&#xff0c;从而实现高效的…

Redis 哨兵模式 搭建

1 . 哨兵模式拓扑 与 简介 本文介绍如何搭建 单主双从 多哨兵模式的搭建 哨兵有12个作用 。通过发送命令&#xff0c;让Redis服务器返回监控其运行状态&#xff0c;包括主服务器和从服务器。 当哨兵监测到master宕机&#xff0c;会自动将slave切换成master&#xff0c;然后通过…

【网络安全 | 项目开发】Web 安全响应头扫描器(提升网站安全性)

原创项目,未经许可,不得转载。 文章目录 项目简介工作流程示例输出技术栈项目代码使用说明项目简介 安全响应头是防止常见 Web 攻击(如点击劫持、跨站脚本攻击等)的有效防线,因此合理的配置这些头部信息对任何网站的安全至关重要。 Web 安全响应头扫描器(Security Head…

基于PySide6与pycatia的CATIA绘图比例智能调节工具开发全解析

引言:工程图纸自动化处理的技术革新 在机械设计领域,CATIA图纸的比例调整是高频且重复性极强的操作。传统手动调整方式效率低下且易出错。本文基于PySide6+pycatia技术栈,提出一种支持智能比例匹配、实时视图控制、异常自处理的图纸批处理方案,其核心突破体现在: ​操作效…

STM32硬件IIC+DMA驱动OLED显示——释放CPU资源,提升实时性

目录 前言 一、软件IIC与硬件IIC 1、软件IIC 2、硬件IIC 二、STM32CubeMX配置KEIL配置 三、OLED驱动示例 1、0.96寸OLED 2、OLED驱动程序 3、运用示例 4、效果展示 总结 前言 0.96寸OLED屏是一个很常见的显示模块&#xff0c;其驱动方式在用采IIC通讯时&#xff0c;常用软件IIC…

泛型的二三事

泛型&#xff08;Generics&#xff09;是Java语言的一个重要特性&#xff0c;它允许在定义类、接口和方法时使用类型参数&#xff08;Type Parameters&#xff09;&#xff0c;从而实现类型安全的代码重用。泛型在Java 5中被引入&#xff0c;极大地增强了代码的灵活性和安全性。…

编程思想——FP、OOP、FRP、AOP、IOC、DI、MVC、DTO、DAO

个人简介 &#x1f440;个人主页&#xff1a; 前端杂货铺 &#x1f64b;‍♂️学习方向&#xff1a; 主攻前端方向&#xff0c;正逐渐往全干发展 &#x1f4c3;个人状态&#xff1a; 研发工程师&#xff0c;现效力于中国工业软件事业 &#x1f680;人生格言&#xff1a; 积跬步…

【区块链安全 | 第三十九篇】合约审计之delegatecall(一)

文章目录 外部调用函数calldelegatecallcall 与 delegatecall 的区别示例部署后初始状态调用B.testCall()函数调用B.testDelegatecall()函数区别总结漏洞代码代码审计攻击代码攻击原理解析攻击流程修复建议审计思路外部调用函数 在 Solidity 中,常见的两种底层外部函数调用方…

linux多线(进)程编程——(6)共享内存

前言 话说进程君的儿子经过父亲点播后就开始闭关&#xff0c;它想要开发出一种全新的传音神通。他想&#xff0c;如果两个人的大脑生长到了一起&#xff0c;那不是就可以直接知道对方在想什么了吗&#xff0c;这样不是可以避免通过语言传递照成的浪费吗&#xff1f; 下面就是它…

信息安全管理与评估2021年国赛正式卷答案截图以及十套国赛卷

2021年全国职业院校技能大赛高职组 “信息安全管理与评估”赛项 任务书1 赛项时间 共计X小时。 赛项信息 赛项内容 竞赛阶段 任务阶段 竞赛任务 竞赛时间 分值 第一阶段 平台搭建与安全设备配置防护 任务1 网络平台搭建 任务2 网络安全设备配置与防护 第二…

高并发秒杀系统设计:关键技术解析与典型陷阱规避

电商、在线票务等众多互联网业务场景中&#xff0c;高并发秒杀活动屡见不鲜。这类活动往往在短时间内会涌入海量的用户请求&#xff0c;对系统架构的性能、稳定性和可用性提出了极高的挑战。曾经&#xff0c;高并发秒杀架构设计让许多开发者望而生畏&#xff0c;然而&#xff0…

微信小程序实战案例 - 餐馆点餐系统 阶段 2 – 购物车

阶段 2 – 购物车&#xff08;超详细版&#xff09; 目标 把“加入购物车”做成 全局状态&#xff0c;任何页面都能读写在本地 持久化&#xff08;关闭小程序后购物车仍在&#xff09;新建 购物车页&#xff1a;数量增减、总价实时计算、去结算入口打 Git Tag v2.0‑cart 1. …

sql 向Java的映射

优化建议&#xff0c;可以在SQL中控制它的类型 在 MyBatis 中&#xff0c;如果返回值类型设置为 java.util.Map&#xff0c;默认情况下可以返回 多行多列的数据

Visual Studio未能加载相应的Package包弹窗报错

环境介绍&#xff1a; visulal studio 2019 问题描述&#xff1a; 起因&#xff1a;安装vs扩展插件后&#xff0c;重新打开Visual Studio&#xff0c;报了一些列如下的弹窗错误&#xff0c;即使选择不继续显示该错误&#xff0c;再次打开后任然报错&#xff1b; 解决思路&am…

【HD-RK3576-PI】Docker搭建与使用

硬件&#xff1a;HD-RK3576-PI 软件&#xff1a;Linux6.1Ubuntu22.04 1.Docker 简介 Docker 是一个开源的应用容器引擎&#xff0c;基于 Go 语言开发&#xff0c;遵循 Apache 2.0 协议。它可以让开发者将应用程序及其依赖项打包到一个轻量级、可移植的容器中&#xff0c;并在任…