九种排序,一次满足

我们在算法题进行练习提升时,经常会看到题目要求数据从大到小输出,从小到大输出,前一半从小到大输出,后一半从大到小输出等,那么这时候就需要用到排序算法,通过排序算法将数据按照一定的顺序进行排序。本文将从易到难介绍以下常见的排序算法:

冒泡排序

冒泡排序相比各位都不陌生,它是最基础的交换排序,从名得知,冒泡排序中每一个元素都可以向气泡一样,根据上方的气泡大小一点一点进行移动

动图演示:

 要实现冒泡排序,我们需要确定待排元素的下标,然后从待排元素的下标+1位置依次比较

假设有五个数据:

第一步:第一个元素要和后面的四个数据比较,比较完成后第一个元素的排序完成

第二步:第二个元素要和后面的三个元素比较,比较完成后第二个元素的排序完成

第三步:第三个元素要和后面的两个元素比较,比较完成后第三个元素的排序完成

第四步:第四个元素要和后面的一个元素比较,比较完成后第四个元素的排序完成

第五步:第五个元素是最后一个元素,经过上面四次排序后,第五个元素的排序也完成了

在这里我们可以设置两个循环来实现:外循环控制当前要排序的元素下标,内循环控制当前下标要比较的次数

五个元素,下标为0-4,当下标为1的元素进行排序时,需要和后面的元素比较三次

因此内循环的次数 j 和下标 i 的关系为:j<n-i-1

void BubbleSort(int* arr, int n) {for (int i = 0; i < n; i++) {int exchange = 0;//如果后面都是有序的,那么就不用再继续循环了,也是对冒泡排序的一个小优化for (int j = 0; j < n - i - 1; j++) {if (arr[j] < arr[j + 1]) {Swap(&arr[j], &arr[j + 1]);exchange = 1;}}if (exchange == 0) {break;}}
}

直接插入排序

直接插入算法是简单的插入排序,算法思想是:将带排序的数据插入到一个已经排序好的序列中,让该序列成为一个新的有序序列

动图演示:

这就跟我们玩扑克牌一样,在抓了N张牌后,手中的牌基本上都是乱序的,我们每个人都会按照各自的习惯将手中最左侧或者最右侧放置的是抓取N张牌中最小的牌,每次从当前位置每次将相邻的一张牌进行调整,直到手上的牌是顺序的。

假设下列乱序数组进行直接插入排序:

第一次调整时,第一个元素3自身形成一个个数为1的有序序列,记录有序序列往下一个元素,此时该元素为2,如果有序序列往下一个元素比有序序列中最大的数小,那么该元素需要插入到有序序列中形成新的有序序列

插入后数组元素排序如下:

插入的思想简单明了,那么插入的逻辑呢,它是怎么实现的呢?

       待排元素如果需要插入有序序列中,那么该元素需要和有序序列中的元素从大到小一一比较,直到找到比待插元素小的元素,在比待插元素小的下一个位置进行插入,那么有序序列的最大元素和比待插元素大的元素都需要往后挪动一位,腾出位置让待插元素插入

因为我们已经记录了有序序列的下一个元素,所以有序序列向后挪动的过程中,可以通过覆盖的方式实现,即arr[end+1]=arr[end],end记录的是有序序列的最大下标。

覆盖:

插入:

通过不断遍历剩下待排元素,将数组变成一个有序序列

//直接插入排序
void InsertSort(int* arr, int n) {for (int i = 0; i < n - 1; i++) {int end = i;//tmp指向end的下一个元素,并记录int tmp = arr[end + 1];while (end >= 0) {//后比前大,将end覆盖end的后一位,循环覆盖,直到end出循环,//出循环end的位置是-1,因为如果待排元素比当前有序序列的最小元素还小//+1后存放tmpif (arr[end] > tmp) {arr[end+1] = arr[end];end--;}//待插入元素比有序序列最大的数大,不需要插入,此次while循环直接退出else {break;}}arr[end + 1] = tmp;}
}

while循环的条件图解:

 根据代码可以看出,元素集合越接近有序,直接插入排序算法的时间效率就越高

但是如果一个元素集合是倒序的,那么此时时间复杂度达到最大,为O(N^2);

希尔排序

希尔排序是在直接插入排序算法的基础上改进而来,它的效率是优于直接插入排序的。

希尔排序的基本思想是:

选定一个整数gap(一般是gap=n/3+1),把待排序文件所有记录进行分组,所有距离相等的记录分在同一组内,对每一组内的记录进行排序,然后gap=gap/3+1,再将数组分成各组,进行插入排序,当gap=1时,相当于直接插入排序。

上面的话非常隐晦难懂,我们直接上图:

在gap>1时,对数组进行预排序(其实就是带有间隔的直接插入排序)

在gap=1时,对数组进行直接插入排序

gap的值为N,那么当前数组就要分成N组,每组N/gap个数据

 希尔排序的代码和直接插入排序算法的代码十分相似:

//希尔排序
void ShellSort(int* arr, int n) {int gap = n;while (gap > 1) {gap = gap / 3 + 1;//与直接插入排序相似,数据只是间隔开for (int i = 0;i<n-gap; i++) {int end = i;int tmp = arr[end + gap];while (end >= 0) {if (arr[end] < tmp) {arr[end + gap] = arr[end];end = end - gap;//超出范围跳出循环,将要调换的数进行调换}else {break;}}arr[end + gap] = tmp;}}}

希尔排序的时间复杂度为O(N^1.3),相对直接插入排序的时间效率有了提高。

直接选择排序

       直接选择排序基本思想:在待排序列中选出最大或最小的元素,存放在序列的起始位置,直到全部带排序的元素排完

1.假设有N个元素待排,将第一个元素与后面的N-1个元素比较,找到N-1个元素中比第一个元素小(大)的最小(最大元素),将第一个元素与选择到的元素相交换

2.在剩下的集合中,重复上述操作,直到集合剩一个元素

//直接选择排序(找最大值或最小值)
void _SelectSort(int* arr, int n) {for (int i = 0; i < n; i++) {//假设第i元素为最大值,记录下标int max = i;for (int j = i + 1; j < n; j++) {//在循环中不断更新坐标if (arr[j] > arr[max]) {max = j;}}//值交换Swap(&arr[i], &arr[max]);}
}

当然,我们也可以对上述排序进行改进:同时找最大和最小值,采用双指针

//直接选择排序(同时找最大值和最小值)
void SelectSort(int* arr, int n) {int begin = 0;int end = n-1;while (begin < end) {int maxi = begin;int mini = begin;for (int i = begin + 1; i <= end; i++) {if (arr[i] < arr[mini]) {mini = i;}if (arr[i] > arr[maxi]) {maxi = i;}}//9  3  2   经过两次交换后还是9   3   2    我们需要 2   3    9    //特殊情况if (maxi == begin) {maxi = mini;}Swap(&arr[mini], &arr[begin]);Swap(&arr[maxi], &arr[end]);++begin;--end;}
}

 直接选择排序的思路非常简单,但是它的效率并不高,时间复杂度达到了O(N^2)

堆排序

堆排序是利用数据结构——堆来设计的一种排序算法。

堆的底层结构就是数组,因此数组排序可以使用堆排序

在上文中详细解释了堆 这种数据结构和向下调整算法和向上调整算法,以及根据排序建堆的种类,在这里不再过多赘述。

只上一个例子的图解:

 

//向下调整算法(小堆)
void AdjustDowm(int* arr, int parent, int n) {//找最小子结点int child = parent * 2 + 1;//求出的是左孩子的索引while (child < n) {//右孩子存在且右孩子比左孩子小if (child + 1<n && arr[child] > arr[child + 1]) {//n为数组长度,那么有效索引为n-1child++;}if (arr[child] < arr[parent]) {Swap(&arr[child], &arr[parent]);parent = child;child = parent * 2 + 1;}else {break;}}
}
//堆排序
void HeapSort(int* arr, int n) {//先建堆for (int i = (n - 1 - 1) / 2; i >= 0; i--) {//int i = (n - 1 - 1) / 2  求到的是一个父结点AdjustDowm(arr, i, n);}int end = n - 1;while (end > 0){Swap(&arr[0], &arr[end]);AdjustDowm(arr, 0, end);end--;}
}

快速排序

快速排序的基本思想是:

1.取任意待排元素为基准值,将待排元素序列分成两个序列,右边序列的元素全比基准值大,左边序列元素全比基准值小

2.将左右序列在各自区域中再取基准值,再将一个序列分成两个序列,左右子序列重复该过程,直到所有元素排列在相应位置上

看到这幅图时,相信你会联想到二叉树,是的,这里和二叉树一样用到了遍历的思想 

对于一个数组,有着左区间和右区间,根据遍历的思想:

void QuickSort(int* arr, int left,int right) {//递归结束条件if (left >= right) {return;}int keyi = _QuickSort(arr, left, right);//找当前区域的基准值//左序列均小于基准值,右序列均大于基准值//左右序列再找基准值,重复该过程//类似二叉树左右子树遍历QuickSort(arr, left, keyi - 1);QuickSort(arr, keyi + 1, right);
}

显然,找基准值在快速排序中成为了最重要的问题

1.hoare版本

算法思路:

1.以序列最左侧为假基准值

2.创建左右指针,左指针指向假基准值+1位置,右指针指向当前序列最大下标处

3.左指针向右找比假基准值大的数,右指针向左找比假基准值小的数

4.当左右指针找到对应的数据时,若左指针指向位置小于等于右指针指向位置,则对应数据交换位置,随后左指针和右指针更新位置

5.当左指针指向位置大于右指针时,将右指针所指向位置数据与假基准值数据向交换,此时右指针所指向位置的数据就是该区间的真正基准值

图解: 

int _QuickSort(int* arr, int left, int right) {//1.以待排序列最左侧为假基准值,left指针指向假基准值+1,right指向序列最右侧int keyi = left;++left;while (left <= right) {//2.left向右寻找比假基准值大的数,right向左寻找比假基准值小的数//3.left和right交换,交换后left++,right--,直到left>right停下while (left <= right && arr[left] < arr[keyi]) {++left;}while (left <= right && arr[right] > arr[keyi]) {--right;}if (left <= right) {Swap(&arr[left], &arr[right]);++left;--right;}}//4.将假基准值和right指向的元素向交换,此时right指向的就是这个序列真正的基准值Swap(&arr[right], &arr[keyi]);return right;
}

2.挖坑法

算法思路:

1.创建左右指针,左指针指向当前序列最左侧,右指针指向当前序列最右侧

2.将第一个数据存放在临时变量key中,形成第一个坑位,同时将第一个数据作为基准值

注意:左指针指向的位置不能大于右指针

3.右指针从右向左找比基准值小的数据,找到后将数据放在坑中,随后当前位置更新成新的坑位

4.左指针从左到右找比基准值大的数据,找到后将数据放在坑中,随后当前位置更新成新的坑位

5.在最后一个坑位,将临时变量key中的数据存放在坑中,此时的坑位就是基准值的位置

图示: 

 

//挖坑法
int _QuickSort2(int* arr, int left, int right) {//先将第一个元素保存在key中,形成第一个坑位//left找比key大,right找比key小int min = arr[left];int hole = left;int key = arr[hole];while (left < right) {while (left < right && arr[right] >= key) {--right;}arr[hole] = arr[right];hole = right;while (left < right && arr[left] <= key) {++left;}arr[hole] = arr[left];hole = left;}arr[hole] = key;return hole;
}

3.lomuto前后指针

算法思路:

1.以第一个元素为基准值,变量key指向第一个元素,设置变量prve指向基准值,变量cur指向prve下一个位置

2.cur从左往右找比基准值小的数,如果arr[cur]<arr[key],prve向后走一步,与cur交换,如果arr[cur]>arr[key],cur继续往后走

3.最后key的值和prve的值交换,此时prve指向的位置的数据为基准值

//lomuto前后指针法
//创建前后指针,从左往右找比基准值小的进行交换,使小的都排在基准值左边
int _QuickSort3(int* arr, int left, int right) {int key = left;int prve = left;int cur = prve + 1;while (cur<=right) {if (arr[key] > arr[cur]&&++prve!=cur) {Swap(&arr[cur], &arr[prve]);}++cur;}Swap(&arr[key], &arr[prve]);return prve;
}

非递归排序

借助数据结构——栈,我们可以实现非递归排序

非递归排序,可以说是将快速排序中的递归借由栈来实现

通过在栈中确定区间,入栈两次出栈一次再入栈两次求得新的区间

//非递归版本快排----借助数据结构栈
void QuickSortNonR(int* arr, int left, int right) {ST st;STInit(&st);STPush(&st,right);STPush(&st,left);while (!StackEmpty(&st)) {int begin = STTop(&st);STPop(&st);int end = STTop(&st);STPop(&st);//在[begin,end]范围内找基准值int key = begin;int prve = begin;int cur = prve + 1;while (cur <= end) {if (arr[key] > arr[cur] && ++prve != cur) {Swap(&arr[cur], &arr[prve]);}++cur;}Swap(&arr[key], &arr[prve]);key= prve;//以基准值为中,分成两个区间if (key + 1 < end) {STPush(&st, end);STPush(&st, key + 1);}if (key - 1 > begin) {STPush(&st, begin);STPush(&st, key - 1);}}STDestroy(&st);
}

归并排序

归并排序是建立在归并操作上的一种有效的排序算法,通过将序列不断细分,再进行合并成一个有序序列

我们可以创建一个新的数组,数组的大小和原数组一致,该数组用来进行合并时两个区间该插入的数据,最后通过覆盖原数组得到排序序列

//建立在归并操作上的一种有效的排序算法。将已有的子序列合并,得到完全有序的序列
void _MergeSort(int*arr, int left, int right, int*tmp)
{//类似二叉树遍历,区间递归到叶子结点,每一次递归都将原本的区间分为两个区间if (left >= right) {return;}int min = (right + left) / 2;//[left,min],[min+1,right]_MergeSort(arr, left, min, tmp);_MergeSort(arr, min+1, right, tmp);//遍历到最后,是叶子结点int begin1 = left;int end1 = min;int begin2 = min + 1;int end2 = right;int index = begin1;//对于每一个区间while (begin1<=end1 &&begin2<=end2) {//哪个小哪个就往新数组里面插入if (arr[begin1] < arr[begin2]) {tmp[index++] = arr[begin1++];}else {tmp[index++] = arr[begin2++];}}//循环到最后一定有一个区间还有数据没入新数组,那么直接将这个区间的数据插入新数组,这个区间内的数据一定是有序的//将两个有序区间插入新数组,一个插完了,那么剩下的一个区间一定是有序的while (begin1 <= end1) {tmp[index++] = arr[begin1++];}while (begin2 <= end2) {tmp[index++] = arr[begin2++];}//通过循环将新数组覆盖旧数组for (int i = 0; i < right - left + 1; i++) {arr[i] = tmp[i];}
}
void MergenSort(int* arr, int n) {//将一个数组分成多个区间,每个区间进行排序,再整合区间进行排序//不在原数组上进行排序int* tmp = (int*)calloc(n,sizeof(int));int len = sizeof(arr) / sizeof(arr[0]);_MergeSort(arr, 0, len-1, tmp);free(tmp);tmp = NULL;
}

非比较排序(计数排序)

计数排序是哈希定址法的变形应用

算法思想:

1.统计相同元素出现次数

2.根据统计结果将序列回收到原来的序列中

哈希定址:数组对应的下标为序列的数据,数组下标对应的元素是该数据出现的次数

但是该法只使用于整数,在数据范围集中时,效率很高,使用场景非常有限

假设有以下3个数据进行排序:1,3,9999,如果通过哈希定址法,就会造成大量的空间浪费

而对于比较集中的数据:10,14,17,18,我们也不需要开辟十八个空间,只需要开辟最大值-最小值+1个空间即可

void CountSort(int* arr, int n) {//哈希定址法的变形应用//1.计算相同元素出现的次数//2.根据统计的结果将序列回收到原来的序列中//找到最大值和最小值int max = arr[0];int min = arr[0];for (int i = 1; i < n; i++) {if (arr[i] > max){max = arr[i];}if (arr[i] < min) {min = arr[i];}}//定址数组个数int range = max - min + 1;int* tmp = (int*)calloc(range, sizeof(int));if (tmp == NULL) {perror("calloc:");}//哈希定值for (int i = 0; i < n; i++) {tmp[arr[i]-min]++;}//排序int j = 0;for (int i = 0; i < range; i++) {while (tmp[i]--) {arr[j++] = i+min;}}
}

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

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

相关文章

解决PyCharm 2023 Python Packages列表为空

原因是因为没有设置镜像源 展开 > 之后&#xff0c;这里 点击齿轮 添加一个阿里云的源 最后还需要点击刷新 可以选择下面的任意一个国内镜像源&#xff1a; 清华&#xff1a;https://pypi.tuna.tsinghua.edu.cn/simple 阿里云&#xff1a;http://mirrors.aliyun.com/…

设计模式之-策略模式配合枚举

1、定义枚举接收不同的参数使用不同的handler, 2、定义个handerl接口&#xff0c;统一方法处理&#xff0c;每个handler实现该接口 public interface IMethodHandler<T, R> {/*** 处理统一入口** param req*/R process(T req); } java3、定义一个简单工厂统一处理 Comp…

送给正在入行的小白:最全最有用的网络安全学习路线已经安排上了,零基础入门到精通,收藏这一篇就够了

在这个圈子技术门类中&#xff0c;工作岗位主要有以下三个方向&#xff1a; 安全研发安全研究&#xff1a;二进制方向安全研究&#xff1a;网络渗透方向 下面逐一说明一下。 第一个方向&#xff1a;安全研发 你可以把网络安全理解成电商行业、教育行业等其他行业一样&#xf…

k8s 1.28.2 集群部署 harbor v2.11.1 接入 MinIO 对象存储

文章目录 [toc]提前准备什么是 HarborHarbor 架构描述Harbor 安装的先决条件硬件资源软件依赖端口依赖 Harbor 在 k8s 的高可用Harbor 部署Helm 编排YAML 编排创建 namespace导入镜像部署 Redis部署 PostgreSQL部署 Harbor core部署 Harbor trivy部署 Harbor jobservice部署 Ha…

RTSP流图片采样助手(yolov5)

在监控和视频分析领域&#xff0c;实时采样视频流中的图像数据是十分重要的。本文将介绍一个基于Python和Tkinter构建的RTSP流图片采样助手的设计与实现&#xff0c;旨在简化RTSP流的采样过程&#xff0c;并支持根据用户定义的特殊标签进行筛选。 项目概述 该项目的主要功能包…

Data+AI下的数据湖和湖仓一体发展史

DataAI下的数据湖和湖仓一体发展史 前言数据湖的“前世今生”AI时代的救星&#xff1a;湖仓一体湖仓一体实践演进未来趋势&#xff1a;智能化、实时化结语 前言 数据湖&#xff1f;湖仓一体&#xff1f;这是什么高科技新名词&#xff1f; 别急&#xff0c;我们慢慢聊。想象一…

ICT产业新征程:深度融合与高质量发展

在信息时代的浪潮中&#xff0c;每一场关于技术革新与产业融合的盛会都闪耀着智慧的光芒&#xff0c;引领着未来的方向。9月25日&#xff0c;北京国家会议中心内&#xff0c;一场聚焦全球信息通信业的顶级盛事——第32届“国际信息通信展”&#xff08;PT展&#xff09;隆重拉开…

Maven基于构建阶段分析多余的依赖

基于构建阶段 test compile 实现依赖分析 执行maven 命令: mvn dependency:analyze 关注:Maven-dependency-plugin 分析结果: [INFO] --- maven-dependency-plugin:2.10:analyze (default-cli) impl --- 配置依赖未使用的依赖项&#xff1a; [INFO] --- maven-dependency-…

Linux基础项目开发day2:量产工具——输入系统

文章目录 前言一、数据结构抽象1、数据本身2、设备本身3、input_manager.h 二、触摸屏编程1、touchscreen.c 三、触摸屏单元测试1、touchscreen.c2、上机测试 四、网络编程netiput.c 五、网络单元测试1、netiput.c2、client.c3、上机测试 六、输入系统的框架1、框架思路2、inpu…

数据库设计与开发—初识SQLite与DbGate

一、SQLite与DbGate简介 &#xff08;一&#xff09;SQLite[1][3] SQLite 是一个部署最广泛、用 C 语言编写的数据库引擎&#xff0c;属于嵌入式数据库&#xff0c;其作为库被软件开发人员嵌入到应用程序中。 SQLite 的设计允许在不安装数据库管理系统或不需要数据库管理员的情…

sublime配置(竞赛向)

我也想要有jiangly一样的sublime 先决条件 首先&#xff0c;到官网上下载最新的sublime4&#xff0c;然后在mingw官网上下载最新的mingw64 mingw64官网&#xff1a;左边菜单栏点击dowloads,然后选择MinGW-W64-builds(可能会有点慢)——然后有时候会变成选LLVM-minGW,接着选择…

linux c国际化

一种locale表示一种文化的各种数据的表示或显示方式&#xff0c;一种locale分成多个部分&#xff0c;不同的部分由category表示&#xff0c;每一种category下面定义了很多关键字keyword locale -a 查看所有支持的locale&#xff0c; locale 不带参 查看当前locale的各个categ…

大语言模型怎么写好提示词,看这篇就够了

对于任何输入&#xff0c;大语言模型都会给出相应的输出&#xff0c;这些输入都可以成为提示词&#xff0c;通常&#xff0c;提示词由指令和输入数据组成&#xff0c;指令是任务&#xff0c;输入数据是完成的要求&#xff0c;其中指令应该明确&#xff0c;用词不能模棱两可&…

centos7.9升级rockylinux8.8

前言 查看centos的版本 &#xff0c;我这台服务器是虚拟机,下面都是模拟实验 升级前一定要把服务器上配置文件&#xff0c;数据等进行备份 [rootlocalhost ~]#cat /etc/redhat-release CentOS Linux release 7.9.2009 (Core) [rootlocalhost ~]#uname -a Linux jenkins_ser…

【C++进阶】AVL树的实现

1. AVL的概念 AVL树是最先发明的⾃平衡⼆叉查找树&#xff0c;AVL是⼀颗空树&#xff0c;或者具备下列性质的⼆叉搜索树&#xff1a;它的左右⼦树都是AV树&#xff0c;且左右⼦树的⾼度差的绝对值不超过1。AVL树是⼀颗⾼度平衡搜索⼆叉树&#xff0c;通过控制⾼度差去控制平衡…

SLM201A系列24V, 15mA - 60mA单通道线性恒流LED驱动芯片 灯带灯条解决方案

SLM201A系列型号&#xff1a; SLM201A15aa-7G SLM201A20aa-7G SLM201A25aa-7G SLM201A30aa-7G SLM201A35aa-7G SLM201A40aa-7G SLM201A45aa-7G SLM201A50aa-7G SLM201A55aa-7G SLM201A60aa-7G SLM201A 系列产品是用于产生单通道、高…

基于FPGA的以太网设计(一)

以太网简介 以太网&#xff08;Ethernet&#xff09;是一种计算机局域网技术。IEEE组织的IEEE 802.3标准制定了以太网的技术标准&#xff0c;它规定了包括物理层的连线、电子信号和介质访问控制的内容。以太网是目前应用最普遍的局域网技术&#xff0c;取代了其他局域网标准如…

【unity小技巧】Unity6 LTS版本安装和一些修改和新功能使用介绍

文章目录 前言安装新功能变化1、官方推荐使用inputsystem进行输入控制2、修复了InputSystem命名错误导致listen被遮挡的bug3、自带去除unity启动画面logo功能4、unity官方的behavior行为树插件5、linearVelocity代替过时的velocity方法待续 完结 前言 2024/10/17其实unity就已…

gitlab:ssh设置

我用的是window&#xff0c;先打开终端&#xff1a; 1、输入 ssh-skygen 执行 然后输入路径&#xff0c;路径地址就是后面括号内的内容 2、然后直接下一步下一步即可&#xff0c;像上面那样就成了 3、打开公钥&#xff0c;复制 4、打开gitlab&#xff0c;在我的 Edit profil…

JUnit 单元测试(详解)

&#x1f680; 个人简介&#xff1a;某大型国企资深软件开发工程师&#xff0c;信息系统项目管理师、CSDN优质创作者、阿里云专家博主&#xff0c;华为云云享专家&#xff0c;分享前端后端相关技术与工作常见问题~ &#x1f49f; 作 者&#xff1a;码喽的自我修养&#x1f9…