c语言实现八大排序详细解析

首先先看排序算法的整体分类

排序:所谓排序,就是使一串记录,按照其中的某个或某些关键字的大小,递增或递减的排列起来的操作。

稳定性:假定在待排序的记录序列中,存在多个具有相同的关键字的记录,若经过排序,这些记录的相对次 序保持不变,即在原序列中,r[i]=r[j],且r[i]在r[j]之前,而在排序后的序列中,r[i]仍在r[j]之前,则称这种排 序算法是稳定的;否则称为不稳定的。

内部排序:数据元素全部放在内存中的排序。

外部排序:数据元素太多不能同时放在内存中,根据排序过程的要求不能在内外存之间移动数据的排序。

1  插入排序

1.1 直接插入排序

其基本思想是:把待排序的记录按其关键码值的大小逐个插入到一个已经排好序的有序序列中,直到所有的记录插入完为 止,得到一个新的有序序列 。

void InsertSort(int* a, int n)
{assert(a);//最后一次,是要把n - 1这个数进行排序,则已经//排好序的尾部为n-2for (int i = 0; i < n-1; ++i){//end表示已经排好序的尾标int end = i;//首先保存要排序的数,一会就会被覆盖了int tmp = a[end + 1];//只要前面的数大于end + 1,则前面的这些数都向后挪动一个位置while (end >= 0 && a[end] > tmp){a[end + 1] = a[end];--end;}a[end + 1] = tmp;}
}

总结:

1. 元素集合越接近有序,直接插入排序算法的时间效率越高

2. 时间复杂度:O(N^2)

3. 空间复杂度:O(1),它是一种稳定的排序算法

4. 稳定性:稳定

1.2 希尔排序

基本思想是:先选定一个整数,把待排序文件中所有记录分成个n/gap组,所有距离为gap的记录分在同一组内,并对每一组内的记录进行排序。然后重复上述分组和排序的工作。当到达gap=1时,所有记录在统一组内排好序。


void ShellSort(int* a, int n)
{assert(a);int gap = n;//不能写成大于0,因为gap的值始终>=1while (gap > 1){//只有gap最后为1,才能保证最后有序//所以这里要加1gap = gap / 3 + 1;//这里只是把插入排序的1换成gap即可//但是这里不是排序完一个分组,再去//排序另一个分组,而是整体只过一遍//这样每次对于每组数据只排一部分//整个循环结束之后,所有组的数据排序完成for (int i = 0; i < n - gap; ++i) //当gap==1时,等同于直接插入排序{int end = i;int tmp = a[end + gap];while (end >= 0 && a[end] > tmp){a[end + gap] = a[end];end -= gap;}a[end + gap] = tmp;}}
}

总结:

1. 希尔排序是对直接插入排序的优化。

2. 当gap > 1时都是预排序,目的是让数组更接近于有序。当gap == 1时,数组已经接近有序的了,这样就 会很快。这样整体而言,可以达到优化的效果。我们实现后可以进行性能测试的对比。

3. 希尔排序的时间复杂度不好计算,因为gap的取值方法很多,导致很难去计算,因此在好些树中给出的 希尔排序的时间复杂度都不固定,因为我们此处的gap是按照Knuth提出的方式取值的,而且Knuth进行了大量的试验统计,我们暂时就按照: 

 4. 稳定性:不稳定

2  选择排序

基本思想: 每一次从待排序的数据元素中选出最小(或最大)的一个元素,存放在序列的起始位置,直到全部待排序的数据元素排完 。

2.1直接选择排序

/*
优化的选择排序,每次可以选择一个最大的和一个最小的
然后把他们放在合适的位置,
即最小的放在第一个位置,最大的放在最后一个位置,
然后再去选择次小的和次大的,依次这样进行,
直到区间只剩一个值或没有
*/
void SelectSort(int* a, int n)
{assert(a);int begin = 0, end = n - 1;while (begin < end){int min = begin, max = begin;for (int i = begin; i <= end; i++){if (a[i] >= a[max])max = i;if (a[i] < a[min])min = i;}//最小的放在Swap(&a[begin], &a[min]);//如果最大的位置在begin位置//说明min是和最大的交换位置//这个时候max的位置就发生了变换//max变到了min的位置//所以要更新max的位置if (begin == max)//防止被交换的数恰好是后续需要交换的最大或最小值max = min;Swap(&a[end], &a[max]);++begin;--end;//PrintArray(a, n);}
}

总结:

1. 直接选择排序思考非常好理解,但是效率不是很好。实际中很少使用

2. 时间复杂度:O(N^2)

3. 空间复杂度:O(1) 4. 稳定性:不稳定

2.2 堆排序

堆排序(Heapsort)是指利用堆积树(堆)这种数据结构所设计的一种排序算法,它是选择排序的一种。它是 通过堆来进行选择数据。需要注意的是排升序要建大堆,排降序建小堆。

void AdjustDown(int* a, int n, int root)
{int parent = root;int child = parent * 2 + 1;while (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;}}
}void HeapSort(int* a, int n)
{assert(a);// 建堆,先从最后两个叶子上的根(索引为(n - 2) / 2开始建堆// 先建最小的堆,直到a[0](最大的堆)// 这就相当于在已经建好的堆上面,新加入一个// 根元素,然后向下调整,让整个完全二叉树// 重新满足堆的性质for (int i = (n - 2) / 2; i >= 0; --i){AdjustDown(a, n, i);}//end:表示最后一个位置int end = n - 1;//只剩一个数时,就不需要调整了while (end > 0){//0位置和最后一个位置交换Swap(&a[0], &a[end]);AdjustDown(a, end, 0);--end;}
}

堆排序详解可以看往期文章:http://t.csdn.cn/fgffOicon-default.png?t=N6B9http://t.csdn.cn/fgffO

总结:

1. 堆排序使用堆来选数,效率就高了很多。

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

3. 空间复杂度:O(1)

4. 稳定性:不稳定

3  交换排序

3.1 冒泡排序

void Swap(int* p1, int* p2)
{int tmp = *p1;*p1 = *p2;*p2 = tmp;
}void BubbleSort(int* a, int n)
{assert(a);int end = n;while (end > 0){/*加一个标记,如果中间没有发生交换说明前面的值都比后面的小即本身就是有序的,最好的情况下,它的时间复杂度就为N*/int flag = 0;for (int i = 1; i < end; ++i){if (a[i - 1] > a[i]){Swap(&a[i - 1], &a[i]);flag = 1;}}if (flag == 0){break;}--end;}
}

3.2 快速排序

// 三数取中法,三个中取一个中间值int GetMidIndex(int* a, int begin, int end)
{int mid = begin + ((end - begin) >> 1);if (a[begin] < a[mid]){if (a[mid] < a[end]){return mid;}else if (a[begin] > a[end]){return begin;}else{return end;}}else // begin >= mid{if (a[mid] > a[end]){return mid;}else if (a[begin] < a[end]){return begin;}else{return end;}}}int PartSort1(int* a, int begin, int end)
{int midindex = GetMidIndex(a, begin, end);Swap(&a[begin], &a[midindex]);int key = a[begin];int start = begin;/*这里要从右边走,如果从左边走,可能最后一步,如果找不到大于基准值的,会导致begin == end即相遇,但是右边还没有走,所以这里的值一定大于基准值,最后交换就会出问题,所以一定要从右边走,即使最后一次找不到小于基准值的,会和左边相遇,而左边此时还没走,一定比基准值小,最后交换肯定没有问题*/while (begin < end){// end 找小while (begin < end && a[end] >= key)--end;// begin找大while (begin < end && a[begin] <= key)++begin;Swap(&a[begin], &a[end]);}//最后的交换一定要保证a[begin] < a[start], 所以要从右边走Swap(&a[begin], &a[start]);return begin;
}int PartSort2(int* a, int begin, int end)
{//begin是坑int key = a[begin];while (begin < end){while (begin < end && a[end] >= key)--end;// end给begin这个坑,end就变成了新的坑。a[begin] = a[end];while (begin < end && a[begin] <= key)++begin;// end给begin这个坑,begin就变成了新的坑。a[end] = a[begin];}a[begin] = key;return begin;
}/*
前后指针法
*/
int PartSort3(int* a, int begin, int end)
{int midindex = GetMidIndex(a, begin, end);Swap(&a[begin], &a[midindex]);int key = a[begin];int prev = begin;int cur = begin + 1;while (cur <= end){// cur找小,把小的往前翻,大的往后翻if (a[cur] < key && ++prev != cur)Swap(&a[cur], &a[prev]);++cur;}Swap(&a[begin], &a[prev]);return prev;
}// []
void QuickSort(int* a, int left, int right)
{if (left >= right)return;if (right - left + 1 < 10){InsertSort(a+left, right - left + 1);}else{int div = PartSort3(a, left, right);//[left, div-1]//[div+1, right]QuickSort(a, left, div - 1);QuickSort(a, div + 1, right);}
}
//用栈模拟递归,用队列也可以实现
void QuickSortR(int* a, int left, int right)
{Stack st;StackInit(&st, 10);//先入大区间if (left < right){StackPush(&st, right);StackPush(&st, left);}//栈不为空,说明还有没处理的区间while (StackEmpty(&st) != 0){left = StackTop(&st);StackPop(&st);right = StackTop(&st);StackPop(&st);//快排单趟排序int div = PartSort1(a, left, right);// [left div-1]// 把大于1个数的区间继续入栈if (left < div - 1){StackPush(&st, div - 1);StackPush(&st, left);}// [div+1, right]if (div+1 < right){StackPush(&st, right);StackPush(&st, div + 1);}}}

3.3 三路快排

快排的总结:

1. 快速排序整体的综合性能和使用场景都是比较好的,所以才敢叫快速排序

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

3. 空间复杂度:O(logN)

4. 稳定性:不稳定

4 归并排序

归并排序的思想: 归并排序(MERGE-SORT)是建立在归并操作上的一种有效的排序算法,该算法是采用分治法(Divide and Conquer)的一个非常典型的应用。将已有序的子序列合并,得到完全有序的序列;即先使每个子序列有 序,再使子序列段间有序。若将两个有序表合并成一个有序表,称为二路归并。 

递归版

void _MergeSort(int* a, int left, int right, int* tmp)
{if (left >= right)return;int mid = left + ((right - left) >> 1);// [left, mid]// [mid+1, right]_MergeSort(a, left, mid, tmp);_MergeSort(a, mid+1, right, tmp);int begin1 = left, end1 = mid;int begin2 = mid + 1, end2 = right;int index = left;while (begin1 <= end1 && begin2 <= end2){if (a[begin1] <= a[begin2])tmp[index++] = a[begin1++];elsetmp[index++] = a[begin2++];}while (begin1 <= end1){tmp[index++] = a[begin1++];}while (begin2 <= end2){tmp[index++] = a[begin2++];}memcpy(a+left, tmp+left, sizeof(int)*(right - left+1));
}void MergeSort(int* a, int n)
{assert(a);int* tmp = (int*)malloc(sizeof(int)*n);_MergeSort(a, 0, n - 1, tmp);free(tmp);
}

非递归版

/*
非递归排序与递归排序相反,将一个元素与相邻元素构成有序数组,
再与旁边数组构成有序数组,直至整个数组有序。
要有mid指针传入,因为不足一组数据时,重新计算mid划分会有问题
需要指定mid的位置
*/
void merge(int* a, int left, int mid, int right, int* tmp)
{// [left, mid]// [mid+1, right]int begin1 = left, end1 = mid;int begin2 = mid + 1, end2 = right;int index = left;while (begin1 <= end1 && begin2 <= end2){if (a[begin1] <= a[begin2])tmp[index++] = a[begin1++];elsetmp[index++] = a[begin2++];}while (begin1 <= end1){tmp[index++] = a[begin1++];}while (begin2 <= end2){tmp[index++] = a[begin2++];}memcpy(a+left, tmp+left, sizeof(int)*(right - left+1));
}/*
k用来表示每次k个元素归并
*/
void mergePass(int *arr, int k, int n, int *temp)
{int i = 0;//从前往后,将2个长度为k的子序列合并为1个while(i < n - 2*k + 1){merge(arr, i, i + k - 1, i + 2*k - 1, temp);i += 2*k;}//合并区间[i, n - 1]有序的左半部分[i, i + k - 1]以及不及一个步长的右半部分[i + k, n - 1]if(i < n - k ){merge(arr, i, i + k - 1,n-1, temp);}}// 归并排序非递归版
void MergeSortNonR(int *arr,int length)
{int k = 1;int *temp = (int *)malloc(sizeof(int) * length);while(k < length){mergePass(arr, k, length, temp);k *= 2;}free(temp);
}

归并排序的总结:

1. 归并的缺点在于需要O(N)的空间复杂度,归并排序的思考更多的是解决在磁盘中的外排序问题。

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

3. 空间复杂度:O(N)

4. 稳定性:稳定

5 计数排序

思想:计数排序又称为鸽巢原理,是对哈希直接定址法的变形应用。

操作步骤:

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

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

void CountSort(int* a, int n)
{int max = a[0], min = a[0];for (int i = 0; i < n; ++i){if (a[i] > max)max = a[i];if (a[i] < min)min = a[i];}//找到数据的范围int range = max - min + 1;int* countArray = (int*)malloc(range*sizeof(int));memset(countArray, 0, sizeof(int)*range);//存放在相对位置,可以节省空间for (int i = 0; i < n; ++i){countArray[a[i] - min]++;}//可能存在重复的数据,有几个存几个int index = 0;for (int i = 0; i < range; ++i){	while (countArray[i]--){a[index++] = i+min;}}
}

计数排序的总结:

1. 计数排序在数据范围集中时,效率很高,但是适用范围及场景有限。

2. 时间复杂度:O(MAX(N,范围))

3. 空间复杂度:O(范围) 

4. 稳定性:稳定

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

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

相关文章

unity摄像机跟随玩家

1.下载摄像机包 2.创建摄像机 3.拖拽玩家

SOME/IP学习笔记1

SOA概念 在SOA中,每个服务就好像我们每一个人在社会中扮演的角色,在对别人提供着服务的同时,同时也享受着别人提供出来的服务,人与人之间,既是彼此独立的,又是需要互相通讯的。服务提供者将功能具象为一组接口,这样使用者就能知道如何调用服务,完成某件事情,得到某个…

如何编写一个易于维护的考试系统源码

编写一个易于维护的考试系统源码对于开发人员来说非常重要。一个易于维护的系统可以使代码更易于理解、修改和扩展&#xff0c;从而提高开发效率和系统稳定性。 第一步&#xff1a;良好的项目结构 良好的项目结构是一个易于维护的源码的基础。可以按照模块、功能或层次等方式…

为机器人装“大脑” 谷歌发布RT-2大模型

大语言模型不仅能让应用变得更智能&#xff0c;还将让机器人学会举一反三。在谷歌发布RT-1大模型仅半年后&#xff0c;专用于机器人的RT-2大模型于近期面世&#xff0c;它能让机器人学习互联网上的文本和图像&#xff0c;并具备逻辑推理能力。 该模型为机器人智能带来显著升级…

静态路由下一跳地址怎么确定(静态路由配置及讲解)

一、用到的所有命令及功能 ①ip route-static 到达网络地址 子网掩码 下一跳 // 配置静态路由下一跳指的是和当前网络直接连接的路由器的接口地址非直连网段必须全部做路由路径是手工指定的&#xff0c;在大规模网络上不能用&#xff0c;效率低&#xff0c;路径是固定的稳定的…

C++ 左值和右值

C 左值和右值 左值、右值左值引用、右值引用std::move()std::move()的实现引用折叠 完美转发forward()的实现函数返回值是左值还是右值如何判断一个值是左值还是右值 左值、右值 在C11中所有的值必属于左值、右值两者之一&#xff0c;右值又可以细分为纯右值、将亡值。在C11中…

RabbitMQ 教程 | 第5章 RabbitMQ 管理

&#x1f468;&#x1f3fb;‍&#x1f4bb; 热爱摄影的程序员 &#x1f468;&#x1f3fb;‍&#x1f3a8; 喜欢编码的设计师 &#x1f9d5;&#x1f3fb; 擅长设计的剪辑师 &#x1f9d1;&#x1f3fb;‍&#x1f3eb; 一位高冷无情的编码爱好者 大家好&#xff0c;我是 DevO…

python解析帆软cpt及frm文件(xml)获取源数据表及下游依赖表

#!/user/bin/evn python import os,re,openpyxl 输入&#xff1a;帆软脚本文件路径输出&#xff1a;帆软文件检查结果Excel#获取来源表 def table_scan(sql_str):# remove the /* */ commentsq re.sub(r"/\*[^*]*\*(?:[^*/][^*]*\*)*/", "", sql_str)# r…

基于Java+Swing实现超级玛丽游戏

基于JavaSwing实现超级玛丽游戏 一、系统介绍二、功能展示三、其他系统 一、系统介绍 超级玛丽小游戏的JAVA程序&#xff0c;进入游戏后首先按空格键开始&#xff0c;利用方向键来控制的马里奥的移动&#xff0c;同时检测马里奥与场景中的障碍物和敌人的碰撞&#xff0c;并判断…

2023年电赛E题报告模板(K210版)--可直接使用

任务 图1 任务内容 要求 图2 基本要求内容 图3 发挥部分内容 说明 图4 说明内容 评分标准 图5 评分内容 正文 &#xff08;部分&#xff09; 摘要 本文使用K210芯片设计了一个运动目标控制与自动追踪系统。系统包括使用深度学习进行识别激光位置&#xff0c;其中红色激…

论文代码学习—HiFi-GAN(4)——模型训练函数train文件具体解析

文章目录 引言正文模型训练代码整体训练过程具体训练细节具体运行流程 多GPU编程main函数&#xff08;通用代码&#xff09;完整代码 总结引用 引言 这里翻译了HiFi-GAN这篇论文的具体内容&#xff0c;具体链接。这篇文章还是学到了很多东西&#xff0c;从整体上说&#xff0c…

数据分析基础-Excel图表的美化操作(按照教程一步步操作)

一、原始数据 包含月份和对应的销量和产量。 时间销量产量1月60722月38673月28344月58685月67596月72357月61428月24319月556710月243511月122112月2645 二、原始的图表设计-采用Excel自带模板 三、优化思路 1、删除多余元素 2、弱化次要元素 对于可以弱化的元素&#xff0c…

VMware vSphere整体解决方案及实验拓扑

VMware vSphere整体解决方案及实验拓扑 VMware vSphere完整的解决方案 VMware vSphere有两个核心组件&#xff1a;ESXI&#xff0c;vCenter。ESXI实现的是单机虚拟化&#xff0c;而vCenter实现集群虚拟化&#xff0c;把所有的ESXI统一进行管理。当然了&#xff0c;要想是实现…

构建vue项目配置和环境配置

目录 1、环境变量process.env配置2、vue package.json多环境配置vue-cli-service serve其他用法vue-cli-service build其他用法vue-cli-service inspect其他用法3、vue导出webpack配置4、配置打包压缩图片文件5、打包去掉多余css(由于依赖问题暂时未实现)6、打包去除console.…

SW - 装配图用的组合零件的制作步骤

文章目录 SW - 装配图用的组合零件的制作步骤概述笔记END SW - 装配图用的组合零件的制作步骤 概述 一套相关零件做好后, 需要做装配体, 将零件都装上, 看看是否有纰漏. 如果不做总装图, 真不放心. 万一废了, 耽误的时间大把的. 做总装图的时间比做零件的2个星期比起来, 代价…

打印Winform控件实现简陋版的分页打印(C#)

本文的代码可以从这里获取&#xff1a;winformDemo.rar 张祥裕/分享的资源名称 - Gitee.com 作者的水平有限&#xff0c;如有错误&#xff0c;望指正。 为了简单起见&#xff0c;纸张大小&#xff0c;打印机等信息按照默认的来&#xff0c;本文的实现方案是&#xff1a;打印Pa…

使用正则表达式 移除 HTML 标签后得到字符串

需求分析 后台返回的数据是 这样式的 需要讲html 标签替换 high_light_text: "<span stylecolor:red>OPPO</span> <span stylecolor:red>OPPO</span> 白色 01"使用正则表达式 function stripHTMLTags(htmlString) {return htmlString.rep…

vue中各种混淆用法汇总

✨在生成、导出、导入、使用 Vue 组件的时候&#xff0c;像我这种新手就会常常被位于不同文件的 new Vue() 、 export default{} 搞得晕头转向。本文对常见用法汇总区分 new Vue() &#x1f4a6;Vue()就是一个构造函数&#xff0c;new Vue()是创建一个 vue 实例。该实例是一个…

阿里云ssl免费数字证书快过期 如何更换

1.登陆阿里云 找到ssl 查看快过期的证书 数字证书管理服务-ssl证书 2.创建免费的证书&#xff0c;对应过期证书的域名 3.下载新证书 pem key放在本地 此处记录本地的下载路径 /Users/dorsey/Downloads/10791167_lzzabc.cn_nginx/lzzabc.cn.pem /Users/dorsey/Downloads/1…

初阶数据结构——二叉树题目

文章目录 一、单值二叉树二、检查两颗树是否相同三、另一棵树的子树四、二叉树的前序遍历五、对称二叉树 一、单值二叉树 单值二叉树 如果二叉树每个节点都具有相同的值&#xff0c;那么该二叉树就是单值二叉树。只有给定的树是单值二叉树时&#xff0c;才返回 true&#xff…