排序的实现

1,插入排序

时间复杂度O(N)

思路:当插入第i个元素时,前面i-1个元素已经排好,将第i个元素与前面的元素比较,找到插入的位置,原来位置的元素向后挪。

动图展示:

从上图可以看出,先把第一个数据看成有序,第二个数据和他比较,如果小的话,将第一个数据往后挪动,第二个数据插入到第一个位置。依次类推,再将前两个数据看成是有序的,第三个数据与前两个数据比较插入,前三个数据看成是有序的,第四个数据与前面数据比较插入......直到最后一个数据也比较完成,数据就有序了

代码实现:

//插入排序
void InsertSort(int* a, int n)
{//i<n-1,防止越界for (int i = 0; i < n - 1; i++){//单趟,一个数据和前面的数据比较插入//[0,end]区间有序int end = i;//tmp为要与前面比较的元素,先保存下来int tmp = a[end + 1];while (end >= 0){//如果大于,数据往后挪动if (a[end] > tmp){a[end + 1] = a[end];end--;}else{break;}}//插入数据a[end + 1] = tmp;}
}

2,希尔排序

时间复杂度O(N^1.3)

希尔排序分思路:

1,预排序,经过多组预排序,让整个数组很接近有序

2,插入排序,最有一趟进行插入排序

预排序:先将数据分成gap组,再对每组进行插入排序。假设gap=3

一组预排序:

预排序的实现,就是在原数组上进行插入排序,这个插入排序与上面的不同,上面的插入排序可以认为跨度是1,而这里的插入排序跨度是gap。

预排序的代码:

for (int i = 0; i < gap; i++)
{//排一组,i=0时,排蓝色一组for (int j = i; j < n - gap; j += gap){//[0,end]有序,让end+gap位置与前面进行比较插入int end = j;int tmp = a[end + gap];while (end >= 0){if (tmp < a[end]){a[end + gap] = a[end];end -= gap;}else{break;}}a[end + gap] = tmp;}
}

 上面预排序的代码,实际上是一组一组排,i=0时,将蓝色一组排好,i=1时,将红色这一组排好,i=2时,将黄色这一组排好,起始代码可以进行改进,如下:

for (int i = 0; i < n-gap; i++)
{//[0,end]有序,排end+gap位置int end = i;int tmp = a[end + gap];while (end >= 0){if (tmp < a[end]){a[end + gap] = a[end];end -= gap;}else{break;}}a[end + gap] = tmp;}

而这种是多组并着排,相当于蓝色组排一次,红色组排一次,黄色组再排一次。然后循环继续,又是 蓝色组排一次,红色组排一次,黄色组再排一次。

但是这两种方式对于效率方面没有提高。

最后,整个代码实现大致思路:控制gap,gap>1时进行预排序,gap=1时,进行最后那一组插入排序。

//希尔排序
void ShellSort(int* a, int n)
{int gap = n;//gap组while (gap > 1){//gap>1时,先进行预排序//gap=1时,进行插入排序gap = gap / 3 + 1;//排多组,一组一组排for (int i = 0; i < gap; i++){//排一组,i=0时,排蓝色一组for (int j = i; j < n - gap; j += gap){//[0,end]有序,让end+gap位置与前面进行比较插入int end = j;int tmp = a[end + gap];while (end >= 0){if (tmp < a[end]){a[end + gap] = a[end];end -= gap;}else{break;}}a[end + gap] = tmp;}}}}

3,选择排序

时间复杂度O(N^2)

思路:一组数据中,遍历一遍,选出最小的数和最大的数,分别放到第一个位置和最后一个位置,然后除这两个元素外,再将剩下的元素进行遍历选数,依次循环。

//选择排序
void SelectSort(int* a, int n)
{int begin = 0, end = n - 1;while (begin < end){//选数int mini = begin, maxi = begin;for (int i = begin+1; i <= end; i++){if (a[i] < a[mini]){mini = i;}if (a[i] > a[maxi]){maxi = i;}}swap(a[begin], a[mini]);if (maxi == begin)//最大数的位置是开始位置时,经过上一步代码,最大值会被换到mini位置{maxi = mini;}swap(a[end], a[maxi]);begin++;end--;}
}

4,快速排序

时间复杂度:O(N*logN)

1,hoare版本

动图展示:

思路:选取左边第一个位置做key,右边先走,右边找小,找到比key位置小的数停下,左边找大,找到比key位置大的数停下,然后交换。最后L与R会相遇,相遇位置一定比key小,再与key交换。这样就完成了一趟。最后的结果是,key左边的数都比key小,key右边的数都比key大, 也就是说key的位置确定了,不用动了。这时,整个数组被分成了三部分,[left,key-1] key [key+1,right],key这个位置已经确定了,所以我们接下来只需对[left,key-1]  [key+1,right]两部分排序就好了,同样,采用相同的方法。这就需要用到递归来解决了。

代码实现:

//快速排序  hoare
void QuickSort(int* a, int left, int right)//right指向最后一个元素
{if (left >= right)return;int keyi = left;int begin = left, end = right;while (begin < end){//右边找小while (begin < end && a[end] >= a[keyi]){--end;}//左边找大while (begin < end && a[begin] <= a[keyi]){++begin;}swap(a[begin], a[end]);//调用c++库里的swap}//相遇swap(a[begin], a[keyi]);keyi = begin;//[left,keyi-1] keyi [keyi+1,right]QuickSort(a, left, keyi - 1);//递归作区间QuickSort(a, keyi + 1, right);//递归右区间
}

 2,前后指针

动图展示:

思路:key为第一个位置下标,用cur遍历整个数组,开始时,prev指向第一个位置,cur指向prev+1位置,cur找小,找到比key位置小的 ,与prev下一个位置交换,cur找到大的++,直到遍历完整个数据。最后,prev位置和key位置交换,同理继续递归左右子区间。

代码实现

//快速排序  前后指针
void QuickSort1(int* a, int left, int right)//right指向最后一个元素
{if (left >= right)return;int keyi = left;int prev = left;int cur = prev + 1;while (cur <= right){if (a[cur] < a[keyi] && ++prev != cur)//prev和cur重合时,不交换{swap(a[cur], a[prev]);}++cur;}swap(a[prev], a[keyi]);keyi = prev;//[left,keyi-1] keyi [keyi+1,right]QuickSort1(a, left, keyi - 1);QuickSort1(a, keyi + 1, right);
}

 3,优化

对于快排,在排有序数组时,效率会大大降低,当数据量足够大的时候,会出现栈溢出的情况。

上面这种情况,n个数据,key一直在第一个位置,左区间不存在,一直递归右区间 。递归层数太深,会出现栈溢出的情况。

解决方法是,让key位置不是最小的,采用三数取中的方法。取第一个位置,中间位置和最后一个位置数据三个数据中的中间值。

还有一种情况,在递归调用时,为了减少递归调用次数,可以在数据量较小时,采用插入排序。

优化后的代码:

int GetMidi(int* a, int left, int right)
{int midi = (left + right) / 2;if (a[left] < a[midi]){if (a[midi] < a[right]){return midi;}else if (a[left] > a[right]){return left;}else{return right;}}else//a[left] >= a[midi]{if (a[midi] > a[right]){return midi;}else if (a[left] < a[right]){return left;}else{return right;}}
}
//快速排序  hoare
void QuickSort(int* a, int left, int right)//right指向最后一个元素
{if (left >= right)return;//减少递归次数if ((right - left + 1) < 10){InsertSort(a + left, right - left + 1);}else{//三数取中int midi = GetMidi(a, left, right);swap(a[midi], a[left]);int keyi = left;int begin = left, end = right;while (begin < end){//右边找小while (begin < end && a[end] >= a[keyi]){--end;}//左边找大while (begin < end && a[begin] <= a[keyi]){++begin;}swap(a[begin], a[end]);}//相遇swap(a[begin], a[keyi]);keyi = begin;//[left,keyi-1] keyi [keyi+1,right]QuickSort(a, left, keyi - 1);//递归作区间QuickSort(a, keyi + 1, right);//递归右区间}
}

4,非递归

这里用数据结构的栈来模拟实现

快排的本质是,选出key,对左右区间递归排序。对左区间进行排序,选出key,递归左右区间。左区间排好后,再对右区间进行排序,选出key。排序的逻辑还是不变(hoare或者是前后指针)。

思路:我们可以使用栈来存储要排序的区间,排序时取出栈顶的数据,也就是要排序的区间,

然后选出key,再对左右区间进行入栈(前提是区间合法),结束条件是栈为空,没有区间需要排序了。

 代码实现(这里的排序逻辑是前后指针方式)

//快排非递归
void QuickSortNor(int* a, int left, int right)
{stack<int> st;//先右后左st.push(right);st.push(left);while (!st.empty()){int begin = st.top();st.pop();int end = st.top();st.pop();//排序,选出keyiint keyi = left;int prev = left;int cur = prev + 1;while (cur <= right){if (a[cur] < a[keyi] && ++prev != cur){swap(a[cur], a[prev]);}++cur;}swap(a[prev], a[keyi]);keyi = prev;//[begin,keyi-1] keyi [keyi+1,end]//先入右区间if (keyi+1<end){st.push(end);st.push(keyi + 1);}if (begin < keyi - 1){st.push(keyi - 1);st.push(begin);}}
}

 5,归并排序

时间复杂度O(N*logN)

空间复杂度(N)

1,递归

动图展示:

思路:需要开辟一个数组,如果将数据分成两部分,这两部分都有序, 这两部分数据从开始位置比较,小的拿下来放入新数组中,直到把这两部分数据都拿下来。最后再把新数组拷贝回原数组中。但是分成的这两部分不一定是有序的,所以就要用到递归解决,递归使这两部分变成有序的,

然后再进行比较,拷贝(重复上面的逻辑)。

这里递归结束条件:分成的两部分只有一个数据。这时就可以开始归并了。

对于左右两部分的划分,用的是二分。begin为开始位置,mid为中间位置,end为最后位置,

左右两部分应划分为【begin,mid】,【mid+1,end】。

不能划分为【begin,mid-1】,【mid,end】。 

代码实现:

void _MerageSort(int* a, int* tmp,int begin, int end)
{if (begin == end)return;int mid = (begin + end) / 2;//左右区间有序就进行归并//[begin,mid]  [mid+1,end]//不能将区间分成[begin,md-1] [mid,end]会出现栈溢出_MerageSort(a, tmp,begin, mid);//让左边有序_MerageSort(a, tmp,mid+1, end);//让右边有序//两边有序后,开始归并int begin1 = begin, end1 = mid;int begin2 = mid + 1, end2 = end;int i = begin1;while (begin1 <= end1 && begin2 <= end2){if (a[begin1] <= a[begin2]){tmp[i++] = a[begin1++];}if (a[begin2] <= a[begin1]){tmp[i++] = a[begin2++];}}while (begin1 <= end1){tmp[i++] = a[begin1++];}while (begin2 <= end2){tmp[i++] = a[begin2++];}memcpy(a + begin, tmp + begin, sizeof(int) * (end - begin + 1));
}
//归并排序
void MerageSort(int* a, int n)
{int* tmp = (int*)malloc(sizeof(int) * n);if (tmp == NULL){perror("malloc fail");return;}_MerageSort(a, tmp,0, n - 1);
}

 2,非递归

 

思路:一次循环,对数组进行一一归并, 第二次循环,进行两两归并。可以定义一个gap变量,

表示每组归并的数据个数。gap=1时,就进行一一归并。

 

对于这两个区间,[begin1,end1] [begin2,end2],就拿第一次循环举例,gap=1时,这两个区间为[0,0],[1,1],然后这两个区间开始归并。for循环一次 后,只归并了前两个数据,就是前面图中的10,6进行归并了,要想继续归并后面的数据,下一个开始归并的起始位置为2*gap,也就是图中的7,for循环后,7和1就归并好了......  这样就完成了一次一一归并。

 

控制gap就可以完成了,实现两两归并,四四归并。 

但是,这个代码还有问题,对于所划分的区间[begin1,end1],[begin2,end2]可能存在越界的风险,end1,begin2,end2可能会越界。因为数据个数不一定是2的次方倍。

三种情况:

1,end1越界,begin2越界,end2越界

2,begin2越界,end2越界

3,end2越界

前两种情况,不用归并了,第二个区间不存在。

第三种情况,需要归并,只需对end2进行修改,让它指向最后一个元素即可。

完整代码展示:

//归并排序 非递归
void MerageSortNor(int* a, int n)
{int* tmp = (int*)malloc(sizeof(int) * n);if (tmp == NULL){perror("malloc fail");return;}int gap = 1;//每组数据个数//gap=1  一一归并while (gap < n){for (int i = 0; i < n; i += 2 * gap)//i每组归并的起始位置{int begin1 = i, end1 = i + gap - 1;int begin2 = i + gap, end2 = i + 2 * gap - 1;if (begin2 >= n)//不用归并了{break;}if (end2 >= n){end2 = n - 1;}int j = i;while (begin1 <= end1 && begin2 <= end2){if (a[begin1] <= a[begin2]){tmp[j++] = a[begin1++];}if (a[begin2] <= a[begin1]){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;
}

6,基数排序

1,统计相同元素出现的次数

2,根据统计的结果将序列回收到原数组中。

数组a中的数据与count数组直接映射。

这种情况是在数据较小的情况下,如果数据较大,比如101,100,105,102,103,102这组数据,就需要开辟空间为105的数组,消耗太大了。所以可以实现一种相对映射 。

101,100,105,102,103,102以这组数据为例,找出最大与最小,就可以确定出需要开辟的数据个数:range=最大值—最小值+1;映射时count[a[i]-min]++,这样就实现了相对映射,a[i]与count数组中的a[i]-min位置映射。

代码实现:

//计数排序
void CountSort(int* a, int n)
{int max = a[0], min =a[0];//找出最大,最小for (int i = 1; i < n; i++){if (a[i] > max){max = a[i];}if (a[i] < min){min = a[i];}}//需开辟的数据个数int range = max - min + 1;int* count = (int*)calloc(range, sizeof(int));if (count == NULL){perror("calloc fail");return;}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;//重新放回a中}}free(count);count = NULL;
}

时间复杂度O(max(N,range))

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

如若内容造成侵权/违法违规/事实不符,请联系一条长河网进行投诉反馈,一经查实,立即删除!

相关文章

CS61C 2020计算机组成原理Lab03

Exercise 1: Familiarizing yourself with Venus .data .word 2, 4, 6, 8 n: .word 9.text main: # add t0, x0, x0# addi 是 "add immediate"&#xff08;立即数加法&#xff09;的缩写&#xff0c;表示这是一个加法指令&#xff0c;其中一个加数是一个立即数&am…

macos tcl-tk python图形库软件包安装 port 和brew 包管理工具安装方法和使用总结

macos下安装这个tcl-tk 图形库&#xff0c; 使用port和brew 安装时是不一样的&#xff0c; 软件包名称不一样&#xff0c;安装后的软件文件路径信息也不一样。 在brew 包管理工具中&#xff0c;这个软件包的名称就是tcl-tk&#xff0c; 安装方法为 brew install tcl-tk , 而…

昂科烧录器支持Senasic琻捷电子的蓝牙低功耗芯片SNP746

芯片烧录行业领导者-昂科技术近日发布最新的烧录软件更新及新增支持的芯片型号列表&#xff0c;其中Senasic琻捷电子的蓝牙低功耗芯片SNP746已经被昂科的通用烧录平台AP8000所支持。 SNP746是一款蓝牙低功耗芯片&#xff0c;集成了压力传感器和加速度传感器的测量电路。它是为…

表达式求值(综合应用的难题)

一、各种表达式的含义与操作 请看下面链接里面的博客吧&#xff0c;这是一位大佬写的&#xff0c;里面的图很是不错&#xff0c;可以看看。 各种表达式的概念与操作 二、题目 给定一个表达式&#xff0c;其中运算符仅包含 ,-,*,/&#xff08;加 减 乘 整除&#xff09;&…

产业报告 | 2024年中国机器人产业研究报告

近日&#xff0c;世界机器人大会在北京亦庄国际会展中心举办。据悉&#xff0c;这是国内最大的机器人展会&#xff0c;今年的展会规模更是创下新高&#xff0c;共有169家企业参展&#xff0c;展出的产品数量超过600款&#xff0c;观展人次超过30万&#xff0c;足见各行各业对机…

QT widgets 窗口缩放,自适应窗口大小进行布局

1. 窗口布局 2. 尺寸策略&#xff1a;扩展 Fixed (固定): 行为&#xff1a;控件的大小是固定的&#xff0c;不会随着窗口大小的变化而改变。它的大小由控件的 sizeHint() 返回的值决定。 适用场景&#xff1a;当你希望控件的大小保持不变&#xff0c;不随布局调整时使用&#x…

前端vue-插值表达式和v-html的区别

创建vue实例的时候&#xff0c;可以有两种形式。 1.let appnew Vue({}) 2 const appnew Vue({}) 3 el是挂载点&#xff0c;是上面div的id值 4 data中的值可以展示在上面div中 5 v-html标签里面如果有内容&#xff0c;则我们的新内容会把标签里面的内容覆盖掉

解决 Torch not compiled with CUDA enabled 问题 | MiniCPM3-4B 【应用开发笔记】

最近在研究测试MiniCPM3-4B&#xff0c;这里记录一下遇到的cuda和torch版本问题 在调试和运行MiniCPM3-4B过程中如果出现找不到某个包&#xff0c;就用pip进行安装&#xff0c;如果提示GPU相关的问题则需要进一步检查 解决 Torch not compiled with CUDA enabled 问题 一、查看…

Arthas 全攻略:让调试变得简单

文章目录 一、简介二、命令列表 一、简介 注意 &#xff1a; 我安装的版本是&#xff1a;Arthas V3.7.2 官网&#xff1a;https://arthas.aliyun.com/doc/ 相关错误解决方案请看GitHub&#xff1a;https://github.com/alibaba/arthas/issues Alibaba开源的Java诊断工具。 从…

我的AI工具箱Tauri版-MicrosoftTTS文本转语音

本教程基于自研的AI工具箱Tauri版进行MicrosoftTTS文本转语音服务。 MicrosoftTTS文本转语音服务 是自研的AI工具箱Tauri版中的一款功能模块&#xff0c;专为实现高效的文本转语音操作而设计。通过集成微软TTS服务&#xff0c;用户可以将大量文本自动转换为自然流畅的语音文件…

圣多纳释放法,达到内心的平静

圣多纳释放法的关键在于&#xff1a;我们被情绪控制时&#xff0c;不应该压抑情绪或是发泄情绪。 利用释放法处理情绪是最健康的方法&#xff0c;可以帮助我们获得自由与平静。当我们面对讨厌的人时&#xff0c;我们真正要做的并非压抑或者爆发&#xff0c;而是将“讨厌”这种…

仪表放大器AD620

AD623 是一款低功耗、高精度的仪表放大器&#xff0c;而不是轨到轨运算放大器。它的输入电压范围并不覆盖整个电源电压&#xff08;轨到轨&#xff09;&#xff0c;但在单电源供电下可以处理接近地电位的输入信号。 AD620 和 AD623 都是仪表放大器&#xff0c;但它们在一些关键…

HTB-Netmon(prtg配置文件获取,CVE-2018-9276复现)

前言 各位师傅大家好&#xff0c;我是qmx_07&#xff0c;今天给大家讲解Netmon靶机 渗透流程 信息搜集 服务器开放了80HTTP、21FTP(匿名登录)、445SMB服务 FTP匿名登录 获取敏感文件 登录后台 网站登录需要 账号、密码 &#xff0c;尝试去FTP服务 碰下运气 通过翻阅ft…

基于Python flask的淘宝商品数据分析可视化系统,包括大屏和主题分析,还有回归预测

背景介绍 随着电子商务的迅猛发展&#xff0c;平台上积累了大量的用户行为和商品交易数据。这些数据蕴含着极大的商业价值&#xff0c;可以为市场趋势预测、商品优化以及用户行为分析提供重要的参考。淘宝作为全球最大的在线购物平台之一&#xff0c;拥有海量的商品和用户数据…

联想一体机怎么重装系统_联想一体机重装win10系统教程

联想一体机怎么重装系统&#xff1f;联想一体机重装系统有很多&#xff0c;有一键重装、有U盘重装、有硬盘重装等方式&#xff0c;最保险的方式是u盘重装系统。需要准备一个空U盘&#xff0c;然后利用第三方工具制作启动u盘&#xff0c;制作完成后进入pe重装系统&#xff0c;下…

集装箱机房可视化:高效管理与监控

通过图扑可视化平台实时监控集装箱机房的运行状态和环境参数&#xff0c;优化资源配置&#xff0c;提升运维效率&#xff0c;确保数据中心安全可靠运行。

Swagger 概念和使用以及遇到的问题

前言 接口文档对于前后端开发人员都十分重要。尤其近几年流行前后端分离后接口文档又变 成重中之重。接口文档固然重要,但是由于项目周期等原因后端人员经常出现无法及时更新&#xff0c; 导致前端人员抱怨接口文档和实际情况不一致。 很多人员会抱怨别人写的接口文档不…

dll注入的实现及session0注入

记录一下跟着红队蓝军师傅学免杀的过程 本节旨在学习dll注入和代码实现并不涉及免杀知识 dll注入流程 dll注入要么注入自己写的程序要么找个程序进行注入&#xff0c;一般是找其他程序进行注入 所以按照上面的步骤进行 其中申请空间&#xff0c;创建线程都是在远程的另一个进…

【Linux】-----进程第一弹

目录 概念 描述进程-PCB 查看进程 获取进程标识符 终止进程 fork创建进程 返回值说明 进程的状态 ①运行状态(R) ②浅度睡眠(S) ③深度睡眠(D) ④暂停状态(T) ⑤僵尸状态(Z)(重点) 是什么&#xff1f; 举例 危害 孤儿进程 ⑥死亡状态(X) 概念 课本上对于进程…

土豆王国小乐队携手阿派朗创造力乐园,打造2024年okgo儿童音乐节

艺术与科技的完美融合&#xff0c;为首都少年儿童带来音乐盛宴 北京&#xff0c;2024年9月19日 —— 备受期待的2024年okgo儿童音乐节即将于9月21日至22日在北京阿派朗创造力乐园盛大开幕。这场由土豆王国小乐队与阿派朗创造力乐园联合举办的音乐节&#xff0c;旨在为首都及全国…