[数据结构]排序 --2

目录

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 7int index = middleNum(array,start,end);swap(array,index,start);//4 2 3 1 5 6 7int 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 7int index = middleNum(array,start,end);swap(array,index,start);//4 2 3 1 5 6 7int 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.mzph.cn/pingmian/76676.shtml

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

相关文章

虎跃办公AI:重构智能办公的「模型交响乐团」

虎跃办公AI&#xff1a;重构智能办公的「模型交响乐团」 ——当全球40大模型在办公场景中奏响协奏曲 在某科创园区的会议室里&#xff0c;市场总监李薇正用AI生成产品发布会方案&#xff0c;设计团队同步调校着AI渲染的3D主视觉&#xff0c;法务AI自动扫描着合同风险条款——这…

JdbcTemplate基本使用

JdbcTemplate概述 它是spring框架中提供的一个对象&#xff0c;是对原始繁琐的JdbcAPI对象的简单封装。spring框架为我们提供了很多的操作模板类。例如:操作关系型数据的JdbcTemplate和MbernateTemplate&#xff0c;操作nosql数据库的RedisTemplate&#xff0c;操作消息队列的…

vue+leaflet 区域划分_反向遮罩层

leaflet 区域划分_遮罩层 geojson在线生成器网址:(https://datav.aliyun.com/portal/school/atlas/area_selector) 点击前往阿里云geojson生成器 效果图: 实现下面效果,只需要把addSateLayer函数的调用取消掉就好了. //添加遮罩层代码function addMask() {var latlngs;var fe…

ESP32开发之ubuntu环境搭建

1. 在Ubuntu官网下载Ubuntu server 20.04版本https://releases.ubuntu.com/20.04.6/ 2. 在vmware下安装Ubuntu 3. 改Ubuntu静态IP $ sudo vi /etc/netplan/00-installer-config.yaml# This is the network config written by ‘subiquity’ network: renderer: networkd eth…

HTTP 1.1 比 HTTP1.0 多了什么?(详尽版)

相较于HTTP 1.0&#xff0c;1.1 版本增加了以上特性&#xff1a; 1. 新增了连接管理即 keepalive&#xff0c;允许持久连接。 定义&#xff1a; Keepalive允许客户端和服务器在完成一次请求-响应后&#xff0c;保持连接处于打开状态&#xff0c;以便后续请求复用同一连接&am…

【深度学习】PyTorch实现VGG16模型及网络层数学原理

一、Demo概述 代码已附在文末 1.1 代码功能 ✅ 实现VGG16网络结构✅ 在CIFAR10数据集上训练分类模型 1.2 环境配置 详见【深度学习】Windows系统Anaconda CUDA cuDNN Pytorch环境配置 二、各网络层概念 2.1 卷积层&#xff08;nn.Conv2d&#xff09; nn.Conv2d(in_cha…

解决RecyclerView在调用smoothScrollToPosition后最后一个item底部超出屏幕的问题

要解决RecyclerView在调用smoothScrollToPosition后最后一个item底部超出屏幕的问题&#xff0c;可以使用自定义的LinearSmoothScroller&#xff0c;使其底部对齐屏幕。步骤如下&#xff1a; 创建自定义的SmoothScroller类&#xff1a; 继承LinearSmoothScroller并重写getVerti…

k8s亲和力和非亲和力

在 Kubernetes 中&#xff0c;亲和力&#xff08;Affinity&#xff09;和非亲和力&#xff08;Anti-Affinity&#xff09;是用于控制 Pod 调度策略的机制&#xff0c;它们可以帮助优化资源利用率、提高应用性能和可用性。以下是亲和力和非亲和力的详细解释&#xff1a; 亲和力…

开发一款游戏需要哪些岗位角色参与?

常见分类 1. 游戏策划&#xff08;Game Designer&#xff09; 核心职责&#xff1a;设计游戏的玩法、规则、内容和整体体验。 具体工作&#xff1a; 系统设计&#xff1a;设计游戏的战斗、经济、成长、社交等核心系统。 数值设计&#xff1a;平衡角色属性、装备数值、经济系…

Asp.NET Core WebApi 创建带鉴权机制的Api

构建一个包含 JWT&#xff08;JSON Web Token&#xff09;鉴权的 Web API 是一种常见的做法&#xff0c;用于保护 API 端点并验证用户身份。以下是一个基于 ASP.NET Core 的完整示例&#xff0c;展示如何实现 JWT 鉴权。 1. 创建 ASP.NET Core Web API 项目 使用 .NET CLI 或 …

Jenkins 发送钉钉消息

这里不介绍 Jenkins 的安装&#xff0c;可以网上找到很多安装教程&#xff0c;重点介绍如何集成钉钉消息。 需要提前准备钉钉机器人的 webhook 地址。&#xff08;网上找下&#xff0c;很多教程&#xff09; 下面开始配置钉钉机器人&#xff0c;登录 Jenkins&#xff0c;下载 …

CentOS中离线安装DockerCompos并用其部署Rabbitmq(使用离线导入导出docker镜像方式)

场景 DockerDockerCompose实现部署jenkins,并实现jenkinsfile打包SpringBootVue流水线项目过程详解、踩坑记录(附镜像资源、离线包资源下载)&#xff1a; DockerDockerCompose实现部署jenkins,并实现jenkinsfile打包SpringBootVue流水线项目过程详解、踩坑记录(附镜像资源、离…

stm32week11

stm32学习 八.stm32基础 2.stm32内核和芯片 F1系统架构&#xff1a;4个主动单元和4个被动单元 AHB是内核高性能总线&#xff0c;APB是外围总线 总线矩阵将总线和各个主动被动单元连到一起 ICode总线直接连接Flash接口&#xff0c;不需要经过总线矩阵 AHB&#xff1a;72MHz&am…

贪心算法:部分背包问题深度解析

简介&#xff1a; 该Java代码基于贪心算法实现了分数背包问题的求解&#xff0c;核心通过单位价值降序排序和分阶段装入策略实现最优解。首先对Product数组执行双重循环冒泡排序&#xff0c;按wm(价值/重量比)从高到低重新排列物品&#xff1b;随后分两阶段装入&#xff1a;循环…

13. Langchain异步处理:提升应用性能的关键技巧

引言&#xff1a;从"顺序等待"到"并行加速" 2025年某电商平台引入LangChain异步处理后&#xff0c;大促期间订单处理能力提升5倍&#xff0c;系统响应延迟降低70%。本文将基于LangChain的异步架构&#xff0c;详解如何通过并行执行流式处理&#xff0c;让…

ros2-rviz2控制unity仿真的6关节机械臂,探索从仿真到实际应用的过程

文章目录 前言&#xff08;Introduction&#xff09;搭建开发环境&#xff08;Setup Development Environment&#xff09;在window中安装Unity&#xff08;Install Unity in window&#xff09;创建Docker容器&#xff0c;并安装相关软件&#xff08;Create Docker containers…

计算机组成原理笔记(十四)——3.4指令类型

一台计算机的指令系统可以有上百条指令&#xff0c;这些指令按其功能可以分成几种类型&#xff0c;下面分别介绍。 3.4.1数据传送类指令 一、核心概念与功能定位 数据传送类指令是计算机指令系统中最基础的指令类型&#xff0c;负责在 寄存器、主存、I/O设备 之间高效复制数…

各开源协议一览

在 GitHub 上&#xff0c;开源项目通常会使用一些常见的开源协议来定义项目的使用、修改和分发规则。以下是目前 GitHub 上最常见的几种开源协议及其差异和示例说明&#xff1a; TL;DR 协议宽松程度是否强制开源专利保护适用场景MIT最宽松否无希望代码被广泛使用Apache 2.0宽松…

51c自动驾驶~合集17

我自己的原文哦~ https://blog.51cto.com/whaosoft/13793157 #汇聚感知、定位、规划控制的自动驾驶系统 自动驾驶技术在应用到车辆上之后可以通过提高吞吐量来缓解道路拥堵&#xff0c;通过消除人为错误来提高道路安全性&#xff0c;并减轻驾驶员的驾驶负担&#xff0c;从…

小程序开发指南

小程序开发指南 目录 1. 小程序开发概述 1.1 什么是小程序1.2 小程序的优势1.3 小程序的发展历程 2. 开发准备工作 2.1 选择开发平台2.2 开发环境搭建2.3 开发模式选择 3. 小程序开发流程 3.1 项目规划3.2 界面设计3.3 代码开发3.4 基本开发示例3.5 数据存储3.6 网络请求3.7 …