九种排序,一次满足

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

冒泡排序

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

动图演示:

 要实现冒泡排序,我们需要确定待排元素的下标,然后从待排元素的下标+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.rhkb.cn/news/454048.html

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

相关文章

排序02 Multi-gate Mixture-of-Experts (MMoE)

MMoE: 不一定适合业务场景 输入向量&#xff08;包含四种特征&#xff09;到三个神经网络&#xff08;专家&#xff09;&#xff0c;不共享参数。实践中超参数专家神经网络个数需要调&#xff0c;会尝试4个或者8个专家。 左边另一个神经网络softmax输出的向量&#xff0c;三个…

element-plus 官方表格排序问题

element-plus 官方API 默认表格排序存在问题&#xff0c;一个list 被多组排序 修改后&#xff1a; 注意点&#xff1a; 这里一定要使用 sortable"custom"&#xff0c;自定义 sort-change 方法 使用 sortable true 的情况排序会冲突&#xff0c;出现莫名奇妙的问题…

Oracle SQL练习题,从小白到入门 - 上

从事DBA以来&#xff0c;越来越认识到自己SQL水平不足&#xff0c;想想sql语句还停留在大二寒假学习的黑马的Mysql《Mysql 十天精通》基础篇进阶篇&#xff0c;将近100集一天就学完了&#xff0c;黑马yyds。 再后来&#xff0c;做项目用Spring的MyBatis是真的香&#xff0c;练…

string类的学习(上)

string类与我们再C语言中接触到的字符串数据相似&#xff0c;但是做出了一些重大的提升&#xff0c;封装为类&#xff0c;实现了总多的接口&#xff0c;丰富了其功能&#xff0c;为简化了字符串的使用&#xff0c;那现在我们就开始深入学习string类吧。 1.什么事string类 C语言…

Java项目:155 springboot酒店管理系统(含论文+ppt+开题报告+说明文档)

作者主页&#xff1a;源码空间codegym 简介&#xff1a;Java领域优质创作者、Java项目、学习资料、技术互助 文中获取源码 项目介绍 ​ 后台&#xff1a; 1.登录&#xff1a;输入账号、密码&#xff0c;即可登录。 2.套房管理&#xff1a;可对房间房型进行管理。 3.入住管…

elk部署安装

elk部署 前提准备1、elasticsearch2、kibana3、logstash 前提准备 1、提前装好docker docker-compose相关命令 2、替换docker仓库地址国内镜像源 cd /etc/docker vi daemon.json # 替换内容 {"registry-mirrors": [ "https://docker.1panel.dev", "ht…

L1练习-鸢尾花数据集处理(分类/聚类)

背景 前文&#xff08;《AI 自学 Lesson1 - Sklearn&#xff08;开源Python机器学习包&#xff09;》&#xff09;以鸢尾花数据集的处理为例&#xff0c;本文将完善其代码&#xff0c;在使用 sklearn 的部分工具包基础上&#xff0c;增加部分数据预处理、数据分析和数据可视化…

FL Studio 2024 发布,添加 FL Cloud 插件、AI 等功能

作为今年最受期待的音乐制作 DAW 更新之一&#xff0c;FL Studio 2024发布引入了新功能&#xff0c;同时采用了新的命名方式&#xff0c;从现在起将把发布年份纳入其名称中。DAW 的新增功能包括在 FL Cloud 中添加插件、AI 驱动的音乐创作工具和 FL Studio 的新效果。 FL Clou…

Java 解决阿里云OSS服务器私有权限图片通过URL无法预览的问题

简单描述一下此场景的业务: 由于系统中需要将上传的图片在系统中展示(private私有权限不能直接通过url直接展示),不想通过先下载下来然后以流的形式返回给前台展示这种方法很不友好,毕竟现在前台展示方式都是通过图片URL进行展示,所以就上官网查看API文档,果然找到了解决…

视频美颜平台是如何搭建的?基于直播美颜SDK源码的开发技术详解

今天&#xff0c;笔者将详细讲解如何基于直播美颜SDK源码搭建视频美颜平台的技术路径。 一、理解视频美颜技术 视频美颜技术主要通过图像处理算法对视频流进行实时处理&#xff0c;包括肤色优化、瑕疵修复、面部特征增强等。实现这一目标需要高效的图像处理算法和稳定的实时渲…

电脑异常情况总结

文章目录 笔记本无症状息屏黑屏 笔记本无症状息屏黑屏 &#x1f34e; 问题描述&#xff1a; 息屏导致黑屏&#xff1b;依次操作计算机--》右键--》管理--》事件查看器--》Windows日志--》系统&#xff1b;从息屏到异常黑屏之间出现了很多错误&#xff0c;如下&#xff1a;事件…

大规模创新类竞赛评审方案的建模与研究

随着科技的发展和教育制度的改革&#xff0c;近年来涌现出一批以“创新”为主题的竞赛项目。这类竞赛的运行模式为&#xff0c;参赛队伍提交文档、视频或幻灯片等文本形式的作品&#xff0c;专家对参赛队伍提交的作品评阅判分&#xff0c;一份作品将由多位专家独立进行评阅打分…

WPF入门_04绑定

WPF绑定使得原本需要多行代码实现的功能,现在只需要简单的XAML代码就可以完成之前多行后台代码实现的功能。WPF绑定可以理解为一种关系,该关系告诉WPF从一个源对象提取一些信息,并将这些信息来设置目标对象的属性。 目标属性总是依赖属性。然而,源对象可以是任何内容,可以…

mysql8以上版本第一次下载后的登录问题

mysql8以上版本第一次下载后的登录问题 在官网下载mysql后&#xff0c;按照MySQL下载和安装教程操作就可以 如果出现问题&#xff0c;参考https://blog.csdn.net/weixin_63107823/article/details/136588474 注意ini配置文件&#xff0c;如果你是复制的别人的代码&#xff0…

一些简单的编程题(Java与C语言)

引言&#xff1a; 这篇文章呢&#xff0c;小编将会举一些简单的编程题用来帮助大家理解一下Java代码&#xff0c;并且与C语言做个对比&#xff0c;不过这篇文章所出现的题目小编不会向随缘解题系列里面那样详细的讲解每一到题&#xff0c;本篇文章的主要目的是帮助小编和读者们…

算法魅力-双指针的实战

目录 1.双指针的介绍 1. 左右指针&#xff08;对撞指针&#xff09; 2. 快慢指针 2.题目练习讲解 2.1 移动零 算法思路 代码展示 画图效果效果 2.2 复写零 算法思路 代码展示 2.3 快乐数 算法思路 代码展示 2.4 盛最多水的容器 算法思路 代码展示 结束语 1.双指针的…

LeetCode第101题. 对称二叉树

文章目录 &#x1f60a;1.题目&#x1f609;2.解法 &#x1f60a;1.题目 尝试一下该题 &#x1f609;2.解法 /*** Definition for a binary tree node.* struct TreeNode {* int val;* struct TreeNode *left;* struct TreeNode *right;* };*/ bool isSameTree…

考研读研生存指南,注意事项

本视频课程&#xff0c;涉及考研读研的方方面面&#xff0c;从考研初试→复试面试→研究生生活→导师相处→论文专利写作混毕业&#xff0c;应有尽有。有了他&#xff0c;你的研究生生涯稳了。 读研考研注意事项&#xff0c;研究生生存指南。_哔哩哔哩_bilibili 一、考研初试注…

数据泄露危机:提升文件安全意识的紧迫性

在当今数字化时代,数据已成为企业最宝贵的资产之一。然而,随着技术的进步,数据泄露事件的频率和规模也在不断攀升。这不仅给企业带来巨大的经济损失,还可能导致声誉受损、客户流失等一系列严重后果。因此,提升文件安全意识,加强数据保护措施,已成为企业管理中不可忽视的重要议题…

【人工智能】Transformers之Pipeline(二十):令牌分类(token-classification)

目录 一、引言 二、令牌分类&#xff08;token-classification&#xff09; 2.1 概述 2.2 Facebook AI/XLM-RoBERTa 2.3 pipeline参数 2.3.1 pipeline对象实例化参数 2.3.2 pipeline对象使用参数 2.3.3 pipeline返回参数 ​​​​​​​​​​​​​​ 2.4 pipeline…