暴力数据结构之排序大杂烩

1. 冒泡排序:O(N^2)

逻辑解析: 冒泡排序并没有什么实际意义,但是有教学意义,相信大部分小白在学习的初期第一个接触的排序就是冒泡排序。那么接下来我们了解一下他的底层逻辑:

冒泡排序顾名思义就是将最大(小)的值像一个一个小气泡一样逐渐运动到最上层,也就是将一串数字从头开始两两比较,逐步将最值移动到最尾端,最终实现排序。其时间复杂度为O(N^2)。

 

void Swap(int* p1, int* p2)
{int tmp = *p1;*p1 = *p2;*p2 = tmp;
}//冒泡排序O(N^2)
void BubbleSort(int* a, int n)
{for (int i = 0; i < n; i++){int flag = 0;for (int j = 1; j < n - i; j++){if (a[i] > a[j]){Swap(&a[i], &a[j]);flag = 1;}}//已经排好序,所以第一次遍历没有调整,直接退出if (flag == 0){break;}}
}

2. 插入排序:O(N^2)

逻辑解析: 插入排序就是设置一个end,假设区间[0,end]都是有序的,此时取出第end+1个数字插入到前面的有序数列,以此类推,最值end到尾端,此时整个数列就是一个有序数列。时间复杂度为:O(N^2)

void Swap(int* p1, int* p2)
{int tmp = *p1;*p1 = *p2;*p2 = tmp;
}//插入排序O(N^2)
void InsertSort(int* a, int n)
{for (int i = 0; i < n - 1; i++){//默认[0,end]的区间是有序的,取a[end+1]插入前面的区间int end = i;int tmp = a[end + 1];while (end >= 0){if (a[end] > tmp){a[end + 1] = a[end];end--;}else{break;}}a[end + 1] = tmp;}
}

3. 希尔排序:O(N^1.3)

逻辑解析:希尔排序与插入排序的共同点是,希尔排序共进行多次,最后一次时的gap=1,也就是插入排序,在前面的时候先将gap置为n,也就是数组元素个数,这时每次对gap/3后+1,使gap/3递减的同时始终满足gap最小值为1,即保证最后一趟一定是插入排序。在插入排序之前要保证数组分为不相邻的gap组,保证每个组都是有序的,然后在最后一次插入排序使整个数组有序即可。时间复杂度为:O(N^1.3)

//希尔排序O(N^1.3)
void ShellSort(int* a, int n)
{int gap = n;while (gap > 1){// +1保证最后一个gap一定是1// gap > 1时是预排序// gap == 1时是插入排序gap = gap / 3 + 1;for (int i = 0; i < n - gap; i++){int end = i;int tmp = a[end + gap];while (end >= 0){if (a[end] > tmp){a[end + gap] = a[end];end -= gap;}else{break;}}a[end + gap] = tmp;}}
}

4. 堆排序:O(N*logN)

逻辑解析:堆排序是基于堆的逻辑结构设计的,所以了解堆排序首先要知道堆的相关知识,详情移步堆的相关知识 ,关于堆排序实质就是对数组的一些操作,即首先根据升(降)序来决定创建大(小)堆,然后生成一个堆。这时就直接将根节点与最后的叶子结点交换,再向下调整即可。即将数组的首尾交换后进行操作。时间复杂度为:O(N*logN)

void Swap(int* p1, int* p2)
{int tmp = *p1;*p1 = *p2;*p2 = tmp;
}void AdjustDown(int* a, int n, int parent)
{// 先假设左孩子小int child = parent * 2 + 1;while (child < n)  // child >= n说明孩子不存在,调整到叶子了{// 找出小的那个孩子if (child + 1 < n && a[child + 1] < a[child]){++child;}if (a[child] < a[parent]){Swap(&a[child], &a[parent]);parent = child;child = parent * 2 + 1;}else{break;}}
}//堆排序O(N*logN)
void HeapSort(int* a, int n)
{// 降序,建小堆// 升序,建大堆// 向上调整建堆 O(N*logN)// 向下调整建堆 O(N)//从离叶子结点最近的上一层根节点开始//n-1代表最末尾的叶子结点,那么((n - 1) - 1) / 2就是该叶子结点的根结点for (int i = (n - 1 - 1) / 2; i >= 0; i--){AdjustDown(a, n, i);}// O(N*logN)int end = n - 1;while (end > 0){//交换根叶结点后向下调整Swap(&a[0], &a[end]);AdjustDown(a, end, 0);--end;}
}

5. 选择排序: O(N^2)

逻辑解析: 选择排序和冒泡排序一样都没有什么实践意义,同样也没有什么教学意义,所以一般只做了解即可。关于选择排序,大体思路十分简单,就是将最大值与最小值下标都初始置为第一个元素的下标,然后从数组首尾分别向中间遍历,将最大值放在末尾,最小值放在首位,然后缩小遍历区间,再寻找该区间的最大值与最小值,放在两端,以此类推,直到遍历完整个数组即可。

//选择排序
//在[begin,end]范围内选择最值放到两边
void SelectSort(int* a, int n)
{int begin = 0;int end = n - 1;while (begin < end){int min = begin;int max = begin;for (int i = begin + 1; i <= end; i++){if (a[i] > a[max]){max = i;}if (a[i] < a[min]){min = i;}}if (begin == max){max = min;}Swap(&a[begin], &a[min]);Swap(&a[end], &a[max]);begin++;end--;}
}

6. 快速排序(递归实现):O(N*logN)

  1.霍尔方法实现

逻辑解析: 首先讲解一下霍尔发明的排序方法,就是初始时刻将数组最左边的元素置为 key 元素,然后设置两个标识 begin 与 end ,起初 begin 在最左边,end 在最右边,然后向中间遍历,如果 a[begin] 遇到比 a[key] 小的值就向后遍历,同理 a[end] 遇到比 a[key] 大的值就向前遍历,直到遇见不符合的情况,然后将 a[begin] 与 a[end] 交换,直到 begin == end ,这时将 a[key] 与 a[begin] 交换(与 a[end] 也可以) ,目的就是保证 key 元素的左边元素均小于它本身,右边元素都大于它本身,然后递归实现第一次排序后 key 元素的左右部分即可。

//快排的注意一点就是以一个端点为 key 开始端,那么另一个端点就先开始动
//即上述以左边为 key ,就是右边先开始动,直到 begin 和 end 遇见,此时的
//相遇点一定要小于 a[key] 的值//快速排序优化
void QuickSort(int* a, int left, int  right)
{//当只有一个数字就返回if (left >= right){return;}// 小区间优化,不再递归分割排序,减少递归的次数if ((right - left + 1) < 10){//这里 a+left = a[left],即从a[left]开始插入排序InsertSort(a + left, right - left + 1);}else{// 三数取中int midi = GetMidi(a, left, right);Swap(&a[left], &a[midi]);int begin = left;int end = right;int key = left;while (begin < end){//右找小//从最右边开始找出比a[key]小的值while (begin < end && a[key] <= a[end]){end--;}//左找大//从最左边开始找出比a[key]大的值while (begin < end && a[key] >= a[begin]){begin++;}//将左边比a[key]大的与右边比a[key]小的交换//保证begin == end 的位置左边的数字全部比a[key]小//同理,右边的数字全部比a[key]大Swap(&a[begin], &a[end]);}//当begin == end ,即两个目标相遇时就交换相遇的位置与a[key]的位置//然后以新的 key 为割点分为左部分与右部分,再对两部分依次递归Swap(&a[begin], &a[key]);key = begin;//递归实现,使第一次遍历后 key 位置的左右部分有序即可QuickSort(a, left, key - 1);QuickSort(a, key + 1, right);}
}

 2. 双指针方法实现 

逻辑解析:双指针就是初始化两个指针 cur  与 prev ,初始时刻 prev 指向开头,cur 指向 prev 的下一个位置,设置 key 元素为开头元素。然后 cur 向后遍历,遇到比 key 小的就让 prev++,然后交换 cur 与 prev 元素,遍历到 cur 指向元素末端之后结束,然后交换 prev 与 key 元素,然后递归对 key 元素的左右部分进行排序即可。

//快速排序优化
void QuickSort(int* a, int left, int  right)
{//当只有一个数字就返回if (left >= right){return;}// 小区间优化,不再递归分割排序,减少递归的次数if ((right - left + 1) < 10){//这里 a+left = a[left],即从a[left]开始插入排序InsertSort(a + left, right - left + 1);}else{// 三数取中int midi = GetMidi(a, left, right);Swap(&a[left], &a[midi]);int begin = left;int end = right;int key = left;while (begin < end){//右找小//从最右边开始找出比a[key]小的值while (begin < end && a[key] <= a[end]){end--;}//左找大//从最左边开始找出比a[key]大的值while (begin < end && a[key] >= a[begin]){begin++;}//将左边比a[key]大的与右边比a[key]小的交换//保证begin == end 的位置左边的数字全部比a[key]小//同理,右边的数字全部比a[key]大Swap(&a[begin], &a[end]);}//当begin == end ,即两个目标相遇时就交换相遇的位置与a[key]的位置//然后以新的 key 为割点分为左部分与右部分,再对两部分依次递归Swap(&a[begin], &a[key]);key = begin;//递归实现,使第一次遍历后 key 位置的左右部分有序即可QuickSort(a, left, key - 1);QuickSort(a, key + 1, right);}
}//单趟排序
int QSort2(int* a, int left, int right)
{int mid = GetMid(a, left, right);Swap(&a[left], &a[mid]);int key = left;int prev = left;int cur = prev + 1;while (cur <= right){if (a[cur] < a[key] && ++prev != cur){Swap(&a[cur], &a[prev]);}cur++;}Swap(&a[prev], &a[key]);return prev;
}//快速排序双指针实现形式
void QuickSortDouble(int* a, int left, int right)
{if (left >= right){return;}int key = QSort2(a, left, right);QuickSort(a, left, key - 1);QuickSort(a, key, right);
}

7. 快速排序(非递归实现):O(N*logN)

逻辑解析: 虽然是非递归实现,但是其思路还是递归的思路,我们在递归版本的代码实质就是在对待排序数组的某些区间进行操作,所以非递归版本就沿用这个思路,依旧是对待排序数组的区间进行一系列操作,这里是通过栈来实现,即在栈中依次存入 begin 与 end 两个值,这两个值就是初始时刻待排序数组的全部区间,然后进行一趟排序,找到 key 元素,此时要排序的区间就分为了左区间 [begin,key - 1] 与右区间 [key + 1,end],这时就依次先对右区间再对左区间进行入栈操作,主要是因为栈遵循先进后出的原则,相关文章:暴力数据结构之栈与队列(栈的相关操作)

然后利用 while 循环实现不断分区间的操作即可,大体思路与递归相似。

//快速排序非递归实现(借助栈实现)
//主要是对区间进行操作,逐步二分,相当于深度优先遍历
void QuickSortNR(int* a, int left, int right)
{ST st;STInit(&st);STPush(&st, right);STPush(&st, left);while (!STEmpty(&st)){int begin = STTop(&st);STPop(&st);int end = STTop(&st);STPop(&st);//取完栈为空,跳出while循环//单次排序//其中包含取中操作int key = QSort2(a, begin, end);if (key + 1 < end){STPush(&st, end);STPush(&st, key + 1);}if (key - 1 > begin){STPush(&st, key - 1);STPush(&st, begin);}}STDestroy(&st);
}

8. 归并排序(递归实现):O(N*logN)

 

逻辑解析:归并排序就是首先两两比较再四四比较以此类推,直到排序完成,代码实现的思路是,创建一个新的数组,然后将原数组分为两个区间,左区间与右区间,然后分别对左右区间不断细分直到左右区间都只有一个数据,这时就对他们比较,将较小的值放在新数组,以此类推。概括起来就是不断二分直到不可再分为止,然后利用新开的数组再进行归并,最终实现有序。递归版本是先实现单趟排序的逻辑,再进行递归实现整个数组排序的实现。

void _MergeSort(int* a, int* tmp, int begin, int end)
{if (begin == end){return;}//如果[begin,mid][mid+1,end]有序就直接合并即可int mid = (begin + end) / 2;_MergeSort(a, tmp, begin, mid);_MergeSort(a, tmp, mid + 1, end);//归并int begin1 = begin, end1 = mid;int begin2 = mid + 1, end2 = end;int i = begin;while (begin1 <= end1 && begin2 <= end2){if (a[begin1] < a[begin2]){tmp[i++] = a[begin1++];}else{tmp[i++] = a[begin2++];}}while (begin1 <= end1){tmp[i++] = a[begin1++];}while (begin2 <= end2){tmp[i++] = a[begin2++];}memcpy(a + begin, tmp + begin, (end - begin + 1) * sizeof(int));
}//归并排序(递归实现)时间复杂度:O(N*logN)
//分为左右区间,分别排序再合并到新数组即可
void MergeSort(int* a, int n)
{int* tmp = (int*)malloc(sizeof(int) * n);if (tmp == NULL){perror("malloc error");return;}_MergeSort(a, tmp, 0, n - 1);free(tmp);tmp = NULL;
}

9. 归并排序(非递归实现): O(N*logN)

逻辑解析: 非递归版本就是首先进行一一比较,即第一个与第二个比较,第二个与第三个比较,以此类推,直到遇见末尾,然后再两两比较,即第一与第二个为一组,第二与第三个为一组,两组之间比较,然后存入新数组中,以此类推直到原数组分组到不可再分,代码实现就是使用 gap 进行分组,然后循环进行比较,注意不能越界,所以在越界时就要进行改正操作,最后排序完毕将新数组中已排序好的数据存入原数组即可。

//归并排序(非递归实现)
void MergeSortNR(int* a, int n)
{int* tmp = (int*)malloc(sizeof(int) * n);if (tmp == NULL){perror("malloc error");return;}//gap表示每一组归并的数据个数int gap = 1;while (gap < n){//i表示每次归并的起始位置for (int i = 0; i < n; i += gap * 2){//[begin1,end1][begin2,end2]int begin1 = i, end1 = i + gap - 1;int begin2 = i + gap, end2 = i + 2 * gap - 1;// 第二组都越界不存在,这一组就不需要归并if (begin2 >= n)break;// 第二的组begin2没越界,end2越界了,需要修正一下,继续归并if (end2 >= n)end2 = n - 1;//归并左右区间int j = i;while (begin1 < end1 && begin2 < end2){//取出较小的值存入新建的数组if (a[begin1] < a[begin2]){tmp[j++] = a[begin1++];}else{tmp[j++] = a[begin2++];}}//如果原区间剩余部分数据则直接尾插入新建数组while (begin1 < end1){tmp[j++] = a[begin1];}while (begin2 < end2){tmp[j++] = a[begin2];}//每次排序后都要拷贝一遍memcpy(a + i, tmp + i, sizeof(int) * (end2 - i + 1));}gap *= 2;}free(tmp);tmp = NULL;
}

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

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

相关文章

PID——调参的步骤

第一步&#xff1a;确定比例增益P 确定比例增益 P 时&#xff0c;首先去掉 PID 的积分项和微分项&#xff0c;一般是令 Ti0、 Td0&#xff08;具体见PID 的参数设定说明&#xff09;&#xff0c;使PID 为纯比例调节。 输入设定为系统允许的最大值60%~70%&#xff0c;由0逐渐加…

idea项目maven下载依赖报错

报错&#xff1a; 1、Failure to find bad.robot:simple-excel:jar:1.0 in https://maven.aliyun.com/repository/public was cached in the local repository, resolution will not be reattempted until the update interval of aliyunmaven has elapsed or updates are forc…

python的while循环与for循环总结

前两章中&#xff0c;我们跟着海绵宝宝的故事&#xff0c;掌握了 while 循环和 for 循环&#xff0c;这两种不同的循环模式。while 循环和 for 循环都需要有 循环体 和 缩进&#xff0c;我们来复习一下它俩的语法规则&#xff1a; while 循环与 for 循环辨析 学到这里&#x…

Microsoft Edge TTS引擎实现文字转语音小工具

Microsoft Edge TTS引擎实现文字转语音小工具 ​ 看了一篇文章关于使用Microsoft Edge TTS引擎进行文本转语音的介绍。正好单位工作上经常用到音视频的制作和转换。但是文字变成音频一直都是播音员口播实现。现在到了AI时代,各种功能强大的AI大模型已经应用到各个领域,大大提…

Docker镜像导入导出

Docker镜像导入导出 相关命令 docker export 容器id > x:/xx/xx.tar ##导出容器快照 docker import - x:/xx/xx.tar ##导入容器快照 docker save 镜像id > x:/xx/xx.tar ##导出镜像 docker load < x:/xx/xx.tar ##导入镜像命令详解 docker save …

在鲲鹏服务器搭建k8s高可用集群分享

高可用架构 本文采用kubeadm方式搭建k8s高可用集群&#xff0c;k8s高可用集群主要是对apiserver、etcd、controller-manager、scheduler做的高可用&#xff1b;高可用形式只要是为&#xff1a; 1. apiserver利用haproxykeepalived做的负载&#xff0c;多apiserver节点同时工作…

nginx反向代理了解

文章目录 Nginx反向代理反向代理系统调优Proxy Buffer相关指令 Nginx 具有高性能的http和反向代理的web服务器&#xff0c;同时也是一个pop3/smtp/imap代理服务器&#xff0c;使用c语言编写 **Web服务器&#xff1a;**也叫网页服务器&#xff0c;web server&#xff0c;主要功…

易联众智慧云胶片平台,助推医学影像服务“向云端”

在门诊室里,张女士焦急地告诉主治医师,自己忘了带CT胶片。“您别急,我用系统查询一下。”医生轻点几下鼠标进入云胶片平台,只用不到10秒就顺利完成了影像调取。“不仅我可以看到,您在手机上也能随时随地查阅。”张女士根据提示操作,不仅能调阅自己的影像档案,连抽血化验结果都可…

Spring MVC 启动流程?

在 web.xml 文件中给 Spring MVC 的 Servlet 配置了 load-on-startup&#xff0c;所以程序启动的时候会初始化 Spring MVC&#xff0c;在 HttpServletBean 中将配置的 contextConfigLocation属性设置到 Servlet 中&#xff0c;然后在FrameworkServlet 中创建了 WebApplicationC…

[GeoServer系列]Shapefile数据发布

【GeoServer系列】——安装与发布shapefile数据-CSDN博客 将待发布数据放置指定目录下 webapps\geoserver\data\data 创建存储仓库 新建矢量数据源 发布图层 设置边框 设置样式 使用 方式1 let highRoad new Cesium.WebMapServiceImageryProvider({url: http://local…

blender从视频中动作捕捉,绑定到人物模型

总共分为3个步骤&#xff1a; 1、从视频中捕捉动作模型 小K动画网-AIGC视频动捕平台 地址&#xff1a;https://xk.yunbovtb.com/ 需要注册 生成的FBX文件&#xff0c;不能直接导入到blender中&#xff0c; 方法有2种&#xff1a; 第一种&#xff1a;需要转换一下&#x…

Spring Cloud学习笔记(Nacos):Nacos持久化(未完成)

这是本人学习的总结&#xff0c;主要学习资料如下 - 马士兵教育 1、Overview2、单机使用MySQL 1、Overview 我们关闭单机下的Nacos后&#xff0c;再重新启动会发现之前配置的内容没有被删除。这时因为Nacos有内嵌的数据库derby&#xff0c;会自己持久化。 但是在集群的情况下…

QT6.0以上版本实现实时图像传输

目录 服务端开启摄像头&#xff0c;捕获存储图片TCP图像传输延时函数 客户端建立连接接收数据和处理缓冲区接收的一些想法 QT借助tcp实现图像传输&#xff0c;达到类似实时监控的目的。 QT到6.0以上后貌似原来的5.0的一些图像的捕获的函数都无法使用了&#xff0c;网上好像也没…

KAN(Kolmogorov-Arnold Network)的理解 3

系列文章目录 第一部分 KAN的理解——数学背景 第二部分 KAN的理解——网络结构 第三部分 KAN的实践——第一个例程 文章目录 系列文章目录前言KAN 的第一个例程 get started 前言 这里记录我对于KAN的探索过程&#xff0c;每次会尝试理解解释一部分问题。欢迎大家和我一起讨…

百度/迅雷/夸克,网盘免费加速,已破!

哈喽&#xff0c;各位小伙伴们好&#xff0c;我是给大家带来各类黑科技与前沿资讯的小武。 之前给大家安利了百度网盘及迅雷的加速方法&#xff0c;详细方法及获取参考之前文章&#xff1a; 刚刚&#xff01;度盘、某雷已破&#xff01;速度50M/s&#xff01; 本次主要介绍夸…

Python sorted 用法:深入解析排序函数的奥秘

Python sorted 用法&#xff1a;深入解析排序函数的奥秘 在Python编程中&#xff0c;sorted函数是一个强大的工具&#xff0c;用于对可迭代对象进行排序。然而&#xff0c;它的用法和功能远不止表面看起来那么简单。本文将深入剖析sorted函数的四个方面、五个方面、六个方面和…

simulink基础学习笔记

写在前面 这个笔记是看B站UP 快乐的宇航boy 所出的simulink基础教程系列视频过程中记下来的&#xff0c;写的很粗糙不完整&#xff0c;也不会补。视频教程很细跟着做就行。 lesson1-7节的笔记up有&#xff0c;可以加up的群&#xff0c;里面大佬挺活跃的。 lesson8 for循环 For …

【C++初阶学习】第十二弹——stack和queue的介绍和使用

C语言栈&#xff1a;数据结构——栈(C语言版)-CSDN博客 C语言队列&#xff1a;数据结构——队列&#xff08;C语言版&#xff09;-CSDN博客 前言&#xff1a; 在之前学习C语言的时候&#xff0c;我们已经学习过栈与队列&#xff0c;并学习过如何使用C语言来实现栈与队列&…

Python | 平均绩点

字符串的概念和特点 字符串既可以使用单引号&#xff0c;也可以使用双引号""来创建 可以使用运算符来拼接字符串&#xff0c;并返回字符串拼接后的结果。 first_name "Tom" last_name "Jerry" full_name first_name " " &quo…

OCR图片转Excel表格:没结构化的弊端

随着OCR技术的不断发展&#xff0c;将表格图片转为excel已不再是难题&#xff0c;但是&#xff0c;目前市面上的程序还大多处于仅能将图片表格转为普通的excel格式阶段&#xff0c;而不能将其结构化&#xff0c;这样就会产生许多的弊端&#xff0c;具体弊端如下&#xff1a; &l…