「数据结构」八大排序2:快排、归并排序

🎇个人主页:Ice_Sugar_7
🎇所属专栏:初阶数据结构
🎇欢迎点赞收藏加关注哦!

八大排序2

  • 🍉快速排序
    • 🍌霍尔版本
    • 🍌挖坑法
    • 🍌前后指针法
  • 🍉快排优化
    • 🍌三数取中
    • 🍌小区间直接插入
    • 🍌非递归快排
  • 🍉归并排序
    • 🍌递归
    • 🍌非递归
  • 🍉计数排序(了解即可)

🍉快速排序

快排的基本思想是从序列中选某一个元素为key,然后开始多次排序,每次排完后key左边的值都小于key,右边则都大于key。然后对key左右的区间([begin,key - 1]和[key+1,end],左右都是闭区间)分别进行递归,划分为更小的区间,直到成为有序区间再返回。最终整个区间就是有序的

所以,快排的递归过程可以看作是一棵二叉树

●每次选择一个基准元素,将小于基准的元素放在左子树,大于基准的元素放在右子树。然后对左子树和右子树分别进行快排。这个过程可以一直递归下去
●直到每个子树只剩下一个或没有元素(即递归到叶子节点)时返回

快排的框架如下:

void QuickSort(int* a, int left, int right) {  //left和right是区间端点(都是闭区间)if (right <= left)  //right如果小于left,那么这个区间不存在;right==left说明这个区间只有一个元素return;int key = PartSort(a, left, right);  //PartSort是找key的函数,下面会讲QuickSort(a, left, key - 1);QuickSort(a, key + 1, right);
}

快排的核心就是找key,有三种方法找key,下面依次介绍

先解释两个概念:找大和找小:

找大:一直向左走或向右走,直到找出比key大的值,或者找不到
找小:一直向左走或向右走,直到找出比key小的值,或者找不到

🍌霍尔版本

霍尔版本的快排是原始版本,先看动图:
在这里插入图片描述
这种方法具体步骤如下:
●假设序列最左边的值为key,让右边(right)先走,找小。
●找到后停下,轮到左边(left)走,找大。找到后停下,交换此时left和right处的元素。
●重复这个过程,直到left和right相遇,交换此时left和key处的值
●到这里,序列以key为分界线,划分左、右两个区间

注意:假设哪边为key,那么一开始就要让另一边先走

int PartSort1(int* a, int left, int right) {int keyi = left;while (left < right){//右边先走,找小while (left < right && a[right] >= a[keyi]){--right;}//左边找大while (left < right && a[left] <= a[keyi]){++left;}Swap(&a[left], &a[right]);}Swap(&a[left], &a[keyi]);return left;
}

🍌挖坑法

相较于霍尔版,挖坑法的逻辑更好理解
在这里插入图片描述
●假设序列最左边的值为key,右边先走,直到遇见比key小的值,就把这个值填入坑中,然后自己成为新的坑
●right走完后,轮到left走,left是遇到比key大的值才停下来。然后同样把这个值扔进坑里,自己成为新的坑
●left和right相遇时,将key填入坑中(此时坑位就是left和right所在位置)

int PartSort2(int* a, int left, int right) {int hole = left;int tmp = a[hole]; //保存最开始坑位的值while (left < right){while (left < right && a[right] > tmp)  //相等的话可以不移动,不会死循环{--right;}a[hole] = a[right];  //遇到比坑位小的值hole = right;  //更新坑位下标while (left < right && a[left] < tmp){--left;}a[hole] = a[left];hole = left;  //更新坑位}a[hole] = tmp;return hole;
}

🍌前后指针法

●定义两个指针prev、cur,prev一开始位于最左边,cur在prev的下一个位置。让cur开始走,往右找小,如果遇到比key小的值,那就让prev++,然后交换a[prev]和a[cur]
(但如果prev++之后和cur一样的话,那就没必要交换了)

●当cur遇到比key大的值时,此时prev不走,cur照常走
按照这种规律,那prev就会在第一个比key大的值的前面停下来
而cur继续走,再遇到比key小的值时,由于prev++,所以就会将那个小的数和这个大的数交换位置,相当于把大的数和小的数分别甩到后面和前面

●当cur走到最右边时,循环结束,交换prev和key处的值
(此时cur处的值比a[key]小,就把它甩到前面了)

int PartSort3(int* a, int left, int right) {int mid = GetMidi(a, left, right); //采用三数取中的方法取中间数,优化快排,下面会讲这种方法Swap(&a[left], &a[mid]);int key = mid;int prev = left, cur = left + 1;while (cur <= right){if (a[cur] < a[key] && ++prev != cur)  //遇到比key小的值,就++prev,然后若prev和cur不相等,那就交换prev和cur{Swap(&a[prev], &a[cur]);}//如果比key大,那cur就继续走++cur;}Swap(&a[prev], &a[key]);return prev;
}

🍉快排优化

由于快排的递归过程可以看作是二叉树,所以我们可以根据二叉树的特点对快排进行优化,提高其效率

🍌三数取中

对于同样的n个元素,如果二叉树越斜,那么它就越深;而如果二叉树比较平衡,那么深度就比较浅(完全二叉树深度最浅)
前面我们快排取的key要么是最左,要么是最右,如果key处的值刚好是最大值或最小值的话,那对快排是相当不利的
而反之,如果key是序列的中位数,或者是接近中位数(总之就是尽可能不让它成为最值),那就可以极大提高快排的效率

所以写一个三数取中的函数,从left、right和mid(序列中间的那个数)三者中取大小在中间的数,然后把它和left处的值交换,让它成为key

int GetMidi(int* a, int left, int right) {int mid = (left + right) / 2;if (a[left] > a[right])  //左>右{if (a[mid] > a[left])return left;if (a[right] > a[mid])return right;elsereturn mid;}else  //右>左 {if (a[left] > a[mid])return left;if (a[mid] > a[right])return right;elsereturn mid;}
}

🍌小区间直接插入

对于完全二叉树而言,越往下结点数越多,递归的成本也越来越大
拿常规的快排来说(递归过程比较接近完全二叉树),递归到比较深层次时(此时区间长度相对而言比较小)我们不用快排,转而使用直接插入排序,可以降低时间成本

优化后代码如下:

void QuickSort1(int* a, int left, int right) {if (right <= left)return;if (right - left + 1 <= 10)  //区间长度小于等于10时就采用直接插入排序InsertSort(a, right - left + 1); else{int key = PartSort1(a, left, right);QuickSort1(a, left, key - 1);QuickSort1(a, key + 1, right);}
}

🍌非递归快排

当递归层数过深时,就会有栈溢出的风险,此时要使用非递归快排,这种思路通过来实现

思路:
●把区间端点下标(左右都是闭区间)入栈,然后出栈,取到端点下标,找key
●由key可以将原区间划分为左、右两个子区间,将这两个区间的端点入栈,然后继续找key,划分区间
●重复上面的步骤,当栈为空时,排序完成

void QuickSortNonR(int* a, int left, int right) {Stack st;StackInit(&st);StackPush(&st, left);  //左端点入栈StackPush(&st, right);while (!StackEmpty(&st)){int key = PartSort1(a, left, right);right = StackTop(&st);  //取栈顶元素StackPop(&st);  //出栈left = StackTop(&st);StackPop(&st);if (left < key - 1)  //区间至少有两个元素才入栈{StackPush(&st, left);StackPush(&st, key - 1);}if (right > key + 1)  //区间至少有两个元素才入栈{StackPush(&st, key + 1);StackPush(&st, right);}}
}

你会发现,虽然叫非递归,但是整个过程几乎和递归一模一样


🍉归并排序

🍌递归

和快排差不多,也是先分割区间,不过归并排序不用找key,而是直接从中间分割
分割到有序时,将元素从小到大尾插到临时数组tmp。插好后将tmp拷贝到原数组

示意图如下:
在这里插入图片描述

在这里插入图片描述

void _MergeSort(int* a,int* tmp, int left,int right) {if (left >= right)return;int mid = (left + right) / 2;int left1 = left, right1 = mid;  //左区间的左端点、右端点int left2 = mid + 1, right2 = right; //右区间的左端点、右端点_MergeSort(a, tmp, left1, right1);  //左区间进行排序_MergeSort(a, tmp, left2, right2);  //右区间进行排序int i = left;  //控制tmp的下标//合并有序数组(归并中的“并”)while (left1 <= right1 && left2 <= right2){if (a[left1] < a[left2])tmp[i++] = a[left1++];elsetmp[i++] = a[left2++];}//确保剩下的元素都进tmpwhile (left1 <= right1){tmp[i++] = a[left1++];}while (left2 <= right2){tmp[i++] = a[left2++];}memcpy(a + left, tmp + left, sizeof(int) * (right - left + 1));
}void MergeSort(int* a,int n) {int left = 0;int right = n - 1;int mid = (left + right) / 2;int* tmp = (int*)malloc(sizeof(int) * n);_MergeSort(a, tmp,left,right);free(tmp);
}

🍌非递归

将序列中的元素先合并为两个(1,1合并),然后两个两个合并为四个(2,2合并),再合并为八个……
使用非递归的话需要注意边界,因为每次是按2的倍数进行合并的,但是数据不一定是二的倍数,所以要对右区间的长度进行判断:
●如果右区间左端点已经比n大了,那说明右区间不存在,那就不用归并
●如果只有右区间右端点越界,那就把它修改为(n-1)

在这里插入图片描述

最后的memcpy也要注意,因为可能越界,所以不能直接拷贝2*gap个整型大小的空间

void MergeSortNonR(int* a, int n) {int* tmp = (int*)malloc(sizeof(int) * n);int gap = 1;  //每个区间的长度while (gap < n){int index = 0;  //临时数组的下标for (int i = 0; i < n; i += 2 * gap)  //对每一组进行归并{int left1 = i;  //左区间左端点int right1 = left1 + gap - 1;  //左区间右端点int left2 = left1 + gap;  //右区间左端点int right2 = left1 + 2 * gap - 1;  //右区间右端点if (left2 >= n)  //如果右区间的左端点都超出数组范围了,说明右区间不存在break;if (right2 >= n)  //如果右区间右端点越界,那就对它进行修正right2 = n - 1;//放进临时数组while (left1 <= right1 && left2 <= right2){if (a[left1] < a[left2])tmp[index++] = a[left1++];elsetmp[index++] = a[left2++];}//确保剩余元素进入数组while (left1 <= right1){tmp[index++] = a[left1++];}while (left2 <= right2){tmp[index++] = a[left2++];}memcpy(a + i, tmp + i, sizeof(int) *(right2 - i + 1));}gap *= 2;}free(tmp);
}

🍉计数排序(了解即可)

额外开一个空间tmp,并初始化为0。遍历序列,遇到某个数,就让额外空间下标对应的元素+1。其实就相当于一块计数板,记录相应的数出现的次数

这么说确实挺抽象的,举个栗子
比如6,1,7,3,9,2,4,6,从6开始遍历:
在这里插入图片描述
原理很简单:tmp相当于有序序列,遍历完原序列后我们遍历tmp,遇到出现次数不为0的就打印它的下标,出现几次就打印几次,也就可以打印出有序序列了

使用计数排序要先找出序列的最大值、最小值,才能确定tmp下标的范围。比如一个序列最小是100,最大是199,但是数组下标是从0开始的,直接建大小为200的tmp显然浪费空间,所以我们不一定说下标要和数对应,比如0对0,1对1这样子,我们可以0对100,1对101(这种转换数学中称为“映射”)

void CountSort(int* a, int n)
{int min = a[0], max = a[0];for (size_t i = 0; i < n; i++){if (a[i] < min)min = a[i];if (a[i] > max)max = a[i];}int range = max - min + 1;int* count = (int*)malloc(sizeof(int) * range);printf("range:%d\n", range);if (count == NULL){perror("malloc fail");return;}memset(count, 0, sizeof(int) * range);// 统计数据出现次数for (int i = 0; i < n; i++){count[a[i] - min]++;}// 排序int j = 0;for (int i = 0; i < range; i++){while (count[i]--){a[j++] = i + min;}}
}

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

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

相关文章

Spring核心基础:全面总结Spring中提供的那些基础工具类!

内容概要 Spring Framework 提供了众多实用的工具类&#xff0c;这些工具类在简化开发流程、提升代码质量和维护性方面发挥了重要作用&#xff0c;以下是部分关键工具类的总结及其使用场景&#xff1a; StringUtils&#xff1a;不仅提供了基础的字符串操作&#xff0c;如拼接…

LLM(大语言模型)——大模型简介

目录 概述 发展历程 大语言模型的概念 LLM的应用和影响 大模型的能力、特点 大模型的能力 涌现能力&#xff08;energent abilities&#xff09; 作为基座模型支持多元应用的能力 支持对话作为统一入口的能力 大模型的特点 常见大模型 闭源LLM&#xff08;未公开源…

uni-app 经验分享,从入门到离职(三)——关于 uni-app 生命周期快速了解上手

文章目录 &#x1f4cb;前言⏬关于专栏 &#x1f3af;什么是生命周期&#x1f9e9;应用生命周期&#x1f4cc; 关于 App.vue/App.uvue &#x1f9e9;页面生命周期&#x1f4cc;关于 onShow 与 onLoad 的区别 &#x1f9e9;组件生命周期 &#x1f4dd;最后 &#x1f4cb;前言 这…

北朝隋唐文物展亮相广西,文物预防性保护网关保驾护航

一、霸府名都——太原博物馆收藏北朝隋朝文物展 2月1日&#xff0c;广西民族博物馆与太原博物馆携手&#xff0c;盛大开启“霸府名都——太原博物馆北朝隋文物展”。此次新春展览精选了北朝隋唐时期150多件晋阳文物珍品。依据“巍巍雄镇”“惊世古冢”“锦绣名都”三个单元&am…

Web3行业研究逐步加强,“链上数据”缘何成为关注焦点?

据中国电子报报道&#xff0c;近日&#xff0c;由中关村区块链产业联盟指导&#xff0c;中国信息通信研究院牵头&#xff0c;欧科云链控股有限公司参与编写的《全球Web3产业全景与发展趋势研究报告&#xff08;2023年&#xff09;》正式发布。研究报告通过全面追踪国内外Web3产…

图论练习2

内容&#xff1a;路径计数DP&#xff0c;差分约束 最短路计数 题目大意 给一个个点条边的无向无权图&#xff0c;问从出发到其他每个点的最短路有多少条有自环和重边&#xff0c;对答案 解题思路 设边权为1&#xff0c;跑最短路 表示的路径数自环和重边不影…

[office] 怎么样在excel中插入虚线圆圈 #学习方法#微信#知识分享

怎么样在excel中插入虚线圆圈 Excel中可以插入圆形&#xff0c;然后将边框设置为虚线&#xff0c;从而得到虚线圆。 软件版本&#xff1a;Office2007 方法如下&#xff1a; 1.点击插入菜单中的形状&#xff0c;选择椭圆&#xff1a; 2.按下Shift键&#xff0c;同时拖动鼠标…

国产航顺HK32F030M: 超声波测距模块串口通信数据接收与处理

参考代码 /************************************************************************************************** * file usart_async_tx_no_int_rx_rxneint.c * brief 异步串口通信例程, 通过查询TXE标志发送数据,通过RXNE中断接收数据,当中断接收到数据后会将 * …

STL算法(中)

常用排序算法 sort 功能描述&#xff1a; 对容器内元素进行排序 函数原型&#xff1a; sort(iterator beg, iterator end, _Pred) ; // 按值查找元素&#xff0c;找到返回指定位置迭代器&#xff0c;找不到返回结束迭代器位置 // beg 开始迭代器 // end 结束迭代器 …

感悟笔记——2024年2月5日

今日阅读了一篇挺有深度的文章&#xff0c;主要阐述进入职场后的大部分人&#xff0c;是怎么逐渐沦为螺丝钉的?即使起点巨高的优等生&#xff0c;也不可避免。文章指路&#xff1a; 「优等生思维」正在将你变成「螺丝钉」和「老黄牛」从小到大&#xff0c;我一直都是那个「别…

2024最新版MySQL安装使用指南

2024最新版MySQL安装使用指南 Installation and Usage Guide to the Latest Oracle MySQL in 2024 By JacksonML 1. MySQL简介 MySQL是世界上最受欢迎的开源数据库之一。MySQL属于Oracle&#xff08;甲骨文&#xff09;公司的产品&#xff0c;其具有强大的功能&#xff0c;但…

【QT】opcuaServer 的构建

【QT】opcuaServer 的构建 前言opcuaServer实现测试 前言 在博文【opcua】从编译文件到客户端的收发、断连、节点查询等实现 中&#xff0c;我们已经介绍了如何在QT 中创建opucaClient 。在本期的博文中&#xff0c;我们基于之前的部署环境&#xff0c;介绍一下如何构建opcuaS…

对AI原生应用做“逆向”后,我找到了大多数大模型厂商注定失败的原因

大家好&#xff0c;我是卖萌酱。 在一整个2023年对大模型风风火火的流星赶月之后&#xff0c;大模型的竞逐已经来到了“下半场”。应接不暇推出一个又一个大模型已为GPT的技术神话祛魅&#xff0c;不得不说&#xff0c;2024年的大模型市场将更加关注大模型的应用和商业价值。 …

docker入门教程之将应用程序容器化

将应用程序容器化 在本指南的其余部分中&#xff0c;您将使用在 Node.js 上运行的简单待办事项列表管理器。如果您不熟悉 Node.js&#xff0c;请不要担心。本指南不需要任何 JavaScript 经验。 先决条件 您已安装最新版本的 Docker Desktop。您已经安装了 Git 客户端。您可以…

88 SRC挖掘-拿下CNVD证书开源闭源售卖系统

目录 1&#xff0e;开源系统、闭源系统、售卖系统2&#xff0e;如何寻找上述三类系统并进行安全测试3&#xff0e;如何挑简单的入手最快速度获取证书装x演示案例:某开源逻辑审计配合引擎实现通用某闭源审计或黑盒配合引擎实现通用某售卖审计或黑盒配合引擎实现通用 涉及资源&am…

10s初认识多线程创建四种方法

1 继承Thread类 2 实现Runnable接口 3 实现Callable接口 4 多线程池 1-2两方法 10s学会教程网址&#xff1a; http://t.csdnimg.cn/UPy1r 本文简略提及多线程池 -》提前创建多个线程&#xff0c;放在一个“容器”&#xff0c;用时取出&#xff0c;不用即放回池中 优点-》响应…

博客|基于Springboot的个人博客系统设计与实现(源码+数据库+文档)

个人博客系统目录 目录 基于Springboot的个人博客系统设计与实现 一、前言 二、系统功能设计 三、系统实现 1、管理员功能实现 &#xff08;1&#xff09;用户管理 &#xff08;2&#xff09;文章分类管理 &#xff08;3&#xff09;公告信息管理 &#xff08;4&#…

MySQL查询优化技巧和10个案例展示

优化MySQL查询的实战技巧&#xff1a; **避免使用SELECT ***&#xff1a;只获取需要的列&#xff0c;这样可以减少数据传输量&#xff0c;提高查询效率。使用索引&#xff1a;为查询频繁的列创建索引&#xff0c;可以显著提高查询速度。但请注意&#xff0c;索引并非万能&…

AI新宠Arc浏览器真可以取代Chrome吗?

每周跟踪AI热点新闻动向和震撼发展 想要探索生成式人工智能的前沿进展吗&#xff1f;订阅我们的简报&#xff0c;深入解析最新的技术突破、实际应用案例和未来的趋势。与全球数同行一同&#xff0c;从行业内部的深度分析和实用指南中受益。不要错过这个机会&#xff0c;成为AI领…

缩略图保持加密(TPE)论文

文献: R.Zhao,Y.Zhang,Y.Nan,W.Wen,X.Chai,andR. Lan, “Primitively visually meaningful image encryption: A new paradigm,” Inf. Sci. (Ny), Vol. 613, pp. 628–48, 2022. DOI: 10.1016/j.ins.2022.08.027. (1) 第1行:原始图像 第2行:加密图像 加密的目标: 原始…