常见的几种内排序算法以及实现(C语言)(转)


所有未排序的数组是经过检查合法的
主要的内排序包括冒泡、插入、希尔、堆排序、归并、快速、桶排序等
其C语言实现的源文件下载地址:http://download.csdn.net/detail/mcu_tian/9530227
冒泡排序
冒泡排序应该是排序中最简单的算法了
主要思路如下:
1: 比较相邻的元素。如果第一个比第二个大,就交换他们两个。
2:对每一对相邻元素作同样的工作,从开始第一对到结尾的最后一对。在这一点,最后的元素应该会是最大的数。
3:针对所有的元素重复以上的步骤,除了最后一个。
4: 持续每次对越来越少的元素重复上面的步骤,直到没有任何一对数字需要比较。
C语言的一般实现如下:
void bubble_sort(int *array,int num)
{
int i = 0;
int j = 0;
int temp;
for(;j < num;++j)
{
for(i= num;i >j ;–i)
{
if(array[i] < array[i-1])
{
temp =array[i];
array[i] = array[i-1];
array[i-1] = temp;
}
}
}
}
冒泡算法实现和原理都很简单,而且是稳定的排序算法,但是该算法不论什么情况下,算法的比较交换的次数都是恒定的,都为1+2+3+4+… …+n-1
算法的复杂度为O(n^2)
插入排序
插入排序是最简单常用的排序算法,将数组分为两部分,排好序的数列,以及未排序的数列,将未排序的数列中的元素与排好序的数列进行比较,然后将该元素插入到已排序列的合适位置中。
直接插入排序
直接插入排序是插入排序中最简单的一种实现
该算法的主要思路是
⒈ 从第一个元素开始,该元素可以认为已经被排序
⒉ 取出下一个元素,在已经排序的元素序列中从后向前扫描
⒊ 如果该元素(已排序)大于新元素,将该元素移到下一位置
⒋ 重复步骤3,直到找到已排序的元素小于或者等于新元素的位置
⒌ 将新元素插入到下一位置中
⒍ 重复步骤2~5
该排序算法的C语言的一般实现如下:
void insertion_sort(int *array,int num)
{
int i,j;
int temp;
i = 0;
j = 0;
for(;i < num;i++)
{
for(j=i;(j > 0)&&(array[j] < array[j-1]);j–)
{
temp =  array[j-1];
array[j-1] = array[j];
array[j] = temp;
}
}
}
该算法的最坏情况,如逆序,那么复杂度为O(N^2)
最好的情况,如已经预先排好序或者基本排好,那么复杂度为O(N)
上面实现的算法中,排序数量比较大的时候,在比较插入操作时,直接比较操作的代价和交换操作很大,是呈线性增长。
因此该算法适用于少量数据的排序。
 
希尔排序
希尔排序是把记录按下标的一定增量分组,对每组使用直接插入排序算法排序;随着增量逐渐减少,每组包含的关键词越来越多,当增量减至1时,整个文件恰被分成一组,算法便终止。
希尔排序是特殊的插入排序
上述的增量会逐渐减少,直至减少到1,该过程中,增量会形成一个序列,称为增量序列。
希尔排序的算法的时间复杂度跟增量序列密切相关。
具体实现如下:
1:按希尔增量序列进行排序,即增量序列为(N/2,N/4………1)
C语言的实现如下
void shell_sort(int *array,int num)//
{
int increment = 0;
int temp = 0;
int j = 0;
int k = 0;
int m = 0;
for(increment = num/2;increment > 0;increment /= 2)
{
printf(“increment:%d \n “,increment);
for(j = 0;j < increment;j++)
{
for(k = j;k < num;k = k+increment)
{
printf(“k:%d \n “,k);
for(m = k;(m >j)&&(array[m] < array[m-increment]);m = m -increment)
{
temp = array[m];
array[m] = array[m-increment];
array[m-increment] = temp;
}
}
}
}
}
使用希尔增量时希尔排序的最坏时间复杂度为O(N^2)
2:按照Hibbard增量序列进行排序,即增量序列为(2^k-1………7,3,1) 其中(2^k-1)<n
此种增量的希尔排序的最坏运行时间为O(N^3/2)
3:按照sedgwick增量序列进行排序,增量序列为(1,5,19,41,109……)
此种增量的希尔排序的的最坏运行时间为O(N^7/6)
以上两种的实现,跟前面的希尔增量序列实现的代码差不多1,除了最外层的循环迭代由于增量与序列的不同,稍微有点变化之外。
堆排序
堆排序(Heapsort)是指利用堆积树(堆)这种数据结构所设计的一种排序算法。
将待排序的的序列构建成堆,大根堆,即父节点比子节点的数值要大,小根堆,父节点比子节点要小。
然后将堆的根(最大值或者最小值)取下,剩余的数据再构建成堆,再取下根值,如此迭代,直到只剩最后一个值。
出于效率的原因,堆在数组中实现,其中数组的下表对应着堆积树的节点序列,取下的根节点,将堆中的最后一个元素进行交换,那么一直到最后,该数组就是为一个排列好的数组。
其实现如下:
void heap_sort(int *array,int num)
{
/*
初次建立大根堆,注意数组下表与堆元素序列的对应问题,数组的下表是从0开始的
o(n)
*/
int k;
for(k = num/2;k >= 0;k–)
{
int flag;
int tmp;
int i = k ;
while(2*i+1 < num)
{
if(2*i+1 == num-1)
{
flag = 2*i + 1;
}
else
{
if(array[2*i+1] > array[2*i+2])
{
flag = 2*i + 1;
}
else
{
flag = 2*i + 2;
}
}
if(array[i] > array[flag]) break;
else
{
tmp = array[flag];
array[flag] = array[i];
array[i] = tmp;
i = flag;
}
}
}
/*取下根,与堆的最后一个元素交换,再重新建堆,如此迭代往复*/
int max;
int end;
int i;
for(i = 0;i < num;i ++)
{
//put the max num to the end
end = num -1 -i;
max = array[0];
array[0] = array[end];
array[end] = max;
//rebuild the  heap,the length of array is end – 1
int flag;
int tmp;
int i = 0;
while(2*i+1 < end)
{
if(2*i+1 == end-1)
{
flag = 2*i + 1;
}
else
{
if(array[2*i+1] > array[2*i+2])
{
flag = 2*i + 1;
}
else
{
flag = 2*i + 2;
}
}
if(array[i] > array[flag]) break;
else
{
tmp = array[flag];
array[flag] = array[i];
array[i] = tmp;
i = flag;
}
}
}
}
在实现过程的时候,第一阶段堆的构建最多用到2N次比较,在取掉最大值,重新建堆的一次过程中,最多用到2logi。
因此在最坏的情况下,总数最多为2NlogN-O(N)次比较。
在实践中慢于sedgewick增量排序,是 不稳定的排序方法
归并排序
归并排序(MERGE-SORT)是建立在归并操作上的一种有效的排序算法,该算法是采用分治法(Divide and Conquer)的一个非常典型的应用。
将已有序的子序列合并,得到完全有序的序列;即先使每个子序列有序,再使子序列段间有序。若将两个有序表合并成一个有序表,称为二路归并,也就是下面用到的方法。
归并排序使用递归实现,递归的终止条件为当一个序列只有一个元素的时候,为已排序序列,即返回,然后返回的两个序列都为已排序序列,使用归并进行合并排序。
其实现C代码如下:
//两个序列在同一个数组中,而且在位置上是相邻的,根据形参将两个序列标记出来,将两个序列归并结果到临时数组中,然后在复制到数组中。
//实现过程中,很多地雷,尤其数组下标,一不小心就越界了。core dump
void merge(int *array,int *tmp_array,int lpos,int rpos,int end)
{
int left_end = rpos – 1;
int right_end = end;
int left_pos = lpos;
int right_pos = rpos;
int i;
int tmp = 0;
while((left_pos <= left_end)&&(right_pos <= right_end))
{
if(array[left_pos] < array[right_pos])
{
tmp_array[tmp] = array[left_pos];
++left_pos;
}
else
{
tmp_array[tmp] = array[right_pos];
++right_pos;
}
++tmp;
}
while(left_pos <= left_end)
{
tmp_array[tmp] = array[left_pos];
left_pos++;
++tmp;
}
while(right_pos <= right_end)
{
tmp_array[tmp] = array[right_pos];
right_pos++;
++tmp;
}
for(i = 0;i < tmp;i++)
{
array[lpos + i] = tmp_array[i];
}
}
//递归的实现,终止条件为只有一个数,返回
//递归返回之后,该序列部分为已经排好序
//将两次的返回序列,进行归并排序
void m_sort(int *array,int *tmp_array,int left,int right)
{
int centre = (left + right)/2;
if(left < right)
{
m_sort(array,tmp_array,left,centre);
m_sort(array,tmp_array,centre+1,right);//centre记住只能+,不能是-,坑死老爹了,要是-的话,如left = 0,right = 1的时候,centre就是 -1呀,都越界到天边去了。调了好久。
merge(array,tmp_array,left,centre+1,right);
}
}
//这个也可以不要其实就可以了,但是为了保持与前面排序算法的实现保的函数形参保持一致,还是加上了。
void recursion_merge_sort(int *array,int num)
{
int *tmp_array;
tmp_array = malloc(num*sizeof(int));
assert(tmp_array != NULL);
m_sort(array,tmp_array,0,num-1);
free(tmp_array);
}
该实现,并没有在每次递归中使用临时数组,而是公用了一个指针传递过来的数组,这样大大的减少了算法过程中,不会导致内存线性的消耗。
归并排序的算法复杂度为O(NlogN),但是一般不用于主存的内部排序,因为可能增加排序的时候附加的内存,主要用在外部排序,对于内部排序,主要还是快排。
快速排序
快速排序采用的思想是分治思想。
快速排序是找出一个元素(理论上可以随便找一个)作为基准(pivot),然后对数组进行分区操作,使基准左边元素的值都不大于基准值,基准右边的元素值 都不小于基准值,如此作为基准的元素调整到排序后的正确位置。递归快速排序,将其他n-1个元素也调整到排序后的正确位置。最后每个元素都是在排序后的正 确位置,排序完成。所以快速排序算法的核心算法是分区操作,即如何调整基准的位置以及调整返回基准的最终位置以便分治递归。
快速排序的关键问题在找基准值的问题,由于找的值不能太小也不太大,大概使分区后,两个区的元素数量基本上没有太大的偏差。
基准值的选取不能是最大值和最小值,虽然这样最后能够完成排序,但是算法的效率就会大大的打折扣。
若是随机选取值,都有可能取得值过大或者过小。
比较安全的做法是使用三数中值分割法,即使用两端的值加上中间位置的值中的中间值作为基准值,这样可以消除最坏的情况。
在选取基准值之后,然后就类似于归并递归式一样,进行分割递归。
但是待排序的数组小于20以后,可以选取直接使用插入排序,因为对于小数组进行分割递归的话,其效率往往还不如直接使用插入。
下面为C实现的代码
//使用三数中值法选取中至作为基准值,然后将三个数值中的中值放在倒数第二个,最大值放置于最后面
//这样放置元素之后,那么这三个值最小值和最大值已经放在了合适的位置,不需要再进行比较移动了,在后面的算法中可以体现
//返回数组中的倒数第二个值,即基准值,这样放的优点是能够使左右两边的在遍历的时候,可以应对极端情况,可以遍历到所有元素。
int median_three(int *array,int left,int right)
{
int centre;
int tmp;
int i;
centre = (left+right)/2;
if(array[left] > array[right])
{
tmp = array[right];
array[right] = array[left];
array[left]= tmp;
}
if(array[centre] > array[right])
{
tmp = array[right];
array[right] = array[centre];
array[centre]= tmp;
}
if(array[left] > array[centre])
{
tmp = array[centre];
array[centre] = array[left];
array[left] = tmp;
}
tmp = array[centre];
array[centre] = array[right-1];
array[right-1] = tmp;
return array[right-1];
}
//快排实现
void q_sort(int*array,int left,int right)
{
int pivot;
int left_i;
int right_i;
int tmp;
if(right-left < 3)
{
insertion_sort(array+left,right-left+1);  //当待排的数量小于3的时候,就直接快排,其实小于20可以,这里是了验证
}
else
{
pivot = median_three(array,left,right);//找出基准值
left_i = left + 1; //从左边加一个,在三数中值的时候,小于中间值的已经放在了左边,因此没有必要再进行比较操作
right_i = right -2;//同上,加上中间值放在倒数第二个位置
while(1)
{
while(array[left_i] < pivot )//相等就停止,左右两边都是,这样可以使相等的值,最大限度地在基准值的左右两边均匀分布
{
++left_i;
}
while(array[right_i] > pivot)
{
–right_i;
}
if(left_i < right_i)
{
tmp = array[left_i];
array[left_i] = array[right_i];
array[right_i] = tmp;
}
else //当左边的游标等于或者大于右边的右边时候,该趟分割结束
{
break;
}
}
//由于校准值放在数组的倒数第二个,因此将其放到合适的位置去,即与左游标对应的值与其进行交换即可
tmp = array[left_i];
array[left_i] = array[right-1];
array[right-1] = tmp;
//继续迭代
q_sort(array,left,left_i);
q_sort(array,left_i+1,right);
}
}
void quick_sort(int *arrary,int num)
{
q_sort(arrary,0,num-1);
}
快速排序是实践中最快的已知算法,平均运行时间为O(NlogN),最坏的情况是O(N^2)
只要不要在选取校准值太坏以及以及在处理相等的值时停止,最坏的情况基本上是可以避免的。
桶排序
桶排序非常高效
但是该算法只能用于整数排序。
算法的实现
其具体的算法实现为,使用一个数组,初始化的值为0,数组长度不小于于待排序的所有数据的最大值
遍历一遍待排序的数据序列,将数列中的数据对应到数组中的下标,将数组中该元素置为1或者加1。
例如
满足条件的数组A[i] ,初始化值都为0
待排序的序列a,b,c
遍历一遍待排序的序列,将序列中的元素对应到元素的位置,将值+1,例如:A[a] +=1;
然后再遍历一遍数组,
for(i = 0;i < max;i++)
{
while(A[i])
{
输出该值;
A[i]–
}
}
则输出的值的序列就是排序过后的序列了。
该算法的运算复杂度为O(max)    max  为待排序列中的最大值
max的确定可以遍历一遍数组确定,也可以根据输入的范围估计。
但是该算法不能用于浮点排序,只能用于整数排序,如果是有负数,那么负数和下标的对应关系需要注意。
而且当max很大的时候,并且排序的元素不是很多的时候,会占用大量的内存空间,造成大量的内存浪费,效率反而会降低。
因此该种算法只适用于该max值不大的整数排序。
在C++中可以使用bool
或者使用位运算,用位中0和1标识序列中存在的数值
但是该只能用于没有重复的数据,或者是可以忽略重复的数据。
位的排序在以前的博客中有写到,链接为:
http://blog.csdn.net/mcu_tian/article/details/46834589
参考资料:数据结构与算法分析:C语言描述(原书第2版)

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

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

相关文章

常见编程命名缩写

命名缩写 通用缩写翻译控件缩写翻译addressaddr地址calendarcdr日历applicationapp应用程序messageDialogmsgdlg消息框asynchronizationasyn异步drawerdrw抽屉averageavg平均数buttonGroupbtngrp按钮分组bitmapbmp位图checkBoxchk复选框bufferbuf缓冲区containercntr容器chara…

funCode课程实训(C++ )

funcode是一个简单的游戏制作引擎&#xff0c;适合c初学者操作&#xff0c;可以帮助初学者更好的了解c环境&#xff0c;以及各种函数的实现&#xff0c;本学期我们用funcode作为C最后的课程设计&#xff0c;所以我就使用funcode制作一个打地鼠的小游戏。以下是对这个小程序的描…

Nodejs,Npm,React安装教程

React安装 1.下载node.js安装包 下载二进制包 选择比较稳定的版本进行安装&#xff0c;v8.9 2.安装 直接把文件解压复制到某个目录下&#xff0c; sudo cp -r node-v8.9.0 /opt/node #你下载的版本sudo touch /etc/profile.d/node.sh #新建一个脚本文件sudo gedit /etc/…

Ubuntu下的提示信息彩色显示

【问题】 虽然已经折腾过了&#xff1a; 【已解决】Ubuntu中让终端只显示当前路径&#xff0c;而不显示绝对路径 但是&#xff0c;终端中的prompt提示信息&#xff0c;不是彩色的&#xff0c;导致的结果是&#xff1a; 当终端中输出信息很多时&#xff1a; 【已解决】Ubun…

hustoj的搭建

最近开始接触服务器之类的&#xff0c;就自己搭建一个hustoj的服务器&#xff0c;hustoj系统的搭建在网上已经很完善了&#xff0c;这里我就简单的说一下&#xff0c;作为自己的学习笔记。 安装主要环境&#xff0c;Apache2&#xff0c;MySQL&#xff0c;php5和PHPmyadmin。 …

Shell字符串操作集合

字符操作字符串的长度获取字符串中某些字符的个数统计单词的个数bash提供的数组数据结构它是以数字为下标的和C语言从0开始的下一样awk里面的数组取子串匹配求子串sed有按行打印的功能记得用tr把空格换为行号tr来取子串head和tail查询字串子串替换tac 会将文本的内容倒置显示正…

百练4982 踩方格

总时间限制: 1000ms 内存限制: 65536kB描述有一个方格矩阵&#xff0c;矩阵边界在无穷远处。我们做如下假设&#xff1a;a. 每走一步时&#xff0c;只能从当前方格移动一格&#xff0c;走到某个相邻的方格上&#xff1b;b. 走过的格子立即塌陷无法再走第二次&#xff1b;…

Qt自定义QML模块

自定义QML模块 含义为将常用风格的Button&#xff0c;Text,RadioButton,或者自定义的控件作为一个控件进行使用&#xff0c;节省代码。 优点&#xff1a; 代码简洁&#xff0c;减少重复代码自定义的控件进行封装重复使用可以与QML自带的库区别开来优化项目结构 一、创建模块…

POJ3984 迷宫问题【BFS】

好长时间没有敲过代码了&#xff0c;感觉之前学过的都忘了&#xff0c;趁着这个暑假&#xff0c;打算把之前学习的东西都复习一下&#xff0c;当然得慢慢来&#xff0c;毕竟好长时间不敲代码了&#xff0c;怎么着都有些生疏&#xff0c;再加上之前学的也不咋地&#xff0c;相当…

宏定义基本用法

宏定义 不带参数 宏定义又称为宏代换、宏替换&#xff0c;简称“宏”。 格式&#xff1a; #define 标识符 字符串其中的标识符就是所谓的符号常量&#xff0c;也称为“宏名”。 预处理&#xff08;预编译&#xff09;工作也叫做宏展开&#xff1a;将宏名替换为字符串。 掌…

广度优先搜索练习之神奇的电梯

广度优先搜索练习之神奇的电梯 Time Limit: 1000ms Memory limit: 65536K 题目描述 有一座已知层数为n的高楼&#xff0c;这座高楼的特殊之处在于只能靠电梯去上下楼&#xff0c;所以要去到某一层要非常耽误时间&#xff0c;然而更悲哀的是&#xff0c;这座高楼的电梯是限号…

ubuntu安装proxychains及自动补全

proxychains ProxyChains是本人目前为止用到的最方便的代理工具。 inux下代理一般是通过http_proxy和https_proxy这两个环境变量&#xff0c;但是很多软件并不使用这两个变量&#xff0c;导致流量无法走代理。在不使用vpn的前提下&#xff0c;linux并没有转发所有流量的真全局…

快速幂讲解

快速幂的目的就是做到快速求幂&#xff0c;假设我们要求a^b,按照朴素算法就是把a连乘b次&#xff0c;这样一来时间复杂度是O(b)也即是O(n)级别&#xff0c;快速幂能做到O(logn)&#xff0c;快了好多好多。它的原理如下&#xff1a; 假设我们要求a^b&#xff0c;那么其实b是可以…

如何查询资料

如何查询资料技术资料及问题查询查询方法分类查找提取关键字GitHub项目优先使用Google搜索引擎Copy Paste论文查找询问主管 测试修改使用总结分享 公司信息查询国内公司国外公司 如何查询资料 技术资料及问题查询 查询方法 资料与解决办法的查询大致分为7大类。 1.分类查…

山东省第八届 ACM 省赛 sum of power(SDUT 3899)

Problem Description Calculate ∑ni1im mod (10000000007) for given n&#xff0c;m. Input Input contains two integers n,m(1≤n≤1000,0≤m≤10). Output Output the answer in a single line. Example Input 10 0 Example Output 10 方法&#xff1a;快速幂和大数求和 …

Ubuntu主题更换

Ubuntu主题更换 目前的Ubuntu有Unity和Gnome两个比较流行的版本&#xff0c;以下为Gnome桌面环境的主题更换&#xff0c;其他桌面环境类似。 主题的下载地址&#xff0c;点击 Theme 将在网络上下载的主题文件进行解压&#xff0c;然后拷贝到 /usr/share/themes/ 目录下&…

awk简单使用

awk 用于在linux/unix下对文本和数据进行处理,支持用户自定义函数和动态正则表达式等先进功能。 命令格式&#xff1a; awk BEGIN{ print “start” } pattern { commend } END{print "end"} file awk "BEGIN{ print “start” } pattern { commend } END{pr…

Ubuntu 14.04 下 Virtual Judge 的搭建

前期准备工作 1.1 一个Linux系统 因为现场赛的缘故&#xff0c;我一直使用的都是ubuntu。 这里我测试用的是Ubuntu14.04 Desktop 64bit ,当然选择Server会更好一些. 系统的安装不再赘述&#xff0c;作为服务器请选用Server版本。1.2 更新源 在搭建环境之前&#xff0c;请确保…

BitMap的原理介绍与实现

BitMap 位图&#xff08;bitmap&#xff09;是一种非常常用的结构&#xff0c;在索引&#xff0c;数据压缩等方面有广泛应用。位图是通过将数组下标与应用中的一些值关联映射&#xff0c;数组中该下标所指定的位置上的元素可以用来标识应用中值的情况&#xff08;是否存在或者数…

MySQL与PHP连接

1、mysql_connect()-建立数据库连接 格式&#xff1a; resource mysql_connect([string hostname [:port] [:/path/to/socket] [, string username] [, string password]]) 例&#xff1a; $conn mysql_connect("localhost", "username", "pa…