常用排序算法原理简介及C实现
排序可能是接触最早而又直到现在仍然感觉魅力无穷的算法了,总结了一下十几种排序算法(都以升序为例)。参考了很多大大写的文章,大都是百度算法搜索出来的,感谢@百度
目录
冒泡排序
步骤:
将数列从第1位开始遍历至第N-1位,每次比较当前位与后一位大小并排好序。
这样1遍之后,数列中最大的数已移至最后一位(像冒泡泡一样)
对于移除数列中最后一位元素的新数列,继续上述操作,直至新数列长为1停止,则排序完成
伪代码:
Bubble_sort(sequence)
{
for i from 1 to length(sequence)
//不考虑最后一位,构造新数列
for j from 1 to length(sequence)-1-i
if compare_num(j,j+1)>0
swap(j,j+1)
}
复杂度:
一般情况下,需要进行n-1 趟排序,且每趟排序比较n-i 次关键字。时间复杂度即为
\[ f(n) = {n \cdot(n-i) \over 2 } =n^2 + \cdots =O(n^2)\]
选择排序
步骤
- 从数列中找到最小元素
- 将该元素与数列的第一个元素交换(即选择了最小的元素,归位)
- 排除数列的第一个元素,生成一个新数列,继续1,2,3,直到数列中元素个数为1。
伪代码
Selection_sort(sequence) { for i from 1 to length(sequence) min_index=i min_val=sequence[i] for j from i to length(sequence) if compare_num(j,val)<0 //如果第j个元素比标记元素小,则将标记元素置为第j个元素 //标记元素与第i个元素交换 }
复杂度
第1次循环比较N-1次,第2次是N-2次,.......,最后一次循环比较1次。所以比较的次数为
\[f(n)=(n-1)+(n-2)+...+1=\frac{(n-1+1) \cdot n}{2} = O(n^2)\]
虽然选择排序和冒泡排序时间复杂度一样,但是实际上交换的次数很少,最多交换
n-1
次,而冒泡排序最坏情况下要交换\(n^2/2\)次。所以前者性能更优。
插入排序
- 步骤
- 对于第
i
个数,依次与前面第i-1
,i-2
...个数j
比较,直到j<i
为止,期间如果不满足条件则将第j
个数移到j+1
的位置上。 - 将第
i
个数放到第j
个位置上 - 重复上述1,2,遍历
i
从第1
位到第N-1
位 ,则排序完成。
- 对于第
- 伪代码
Insertion_sort(A)
for j from 2 to length(A)
key=A[j]
//将A[j]插入已排好序的数列A[1..j-1]中
i=j-1
while i>0 and A[i]>key
A[i+1]=A[i]
i = i-1
A[i+1]=key
复杂度
$ f(n)=a \cdot n^2 +b \cdot n + c = O(n^2)$
希尔排序
步骤
定义增量序列\(D_M > D_{M-1} > \ldots > D_1=1\)
对于每个\(D_k\) 进行“\(D_k\) —间隔”排序(k=M,M-1,...1)
"\(D_k\) —间隔"排序指将原数列中第\(1, 1+D_k ,1+ 2 \cdot D_k , 1+ m \cdot D_k, \ldots\)个数组成的新数列排序。
结论:“\(D_k\) —间隔”有序的序列,在执行“\(D_{k-1}\)—间隔”排序后,仍然是“\(D_k\) —间隔”有序的。
插入排序实际上是增量为1的排序(比较的两数间隔),而希尔排序是一种分组插入排序,依次比较相隔\(D_k(k=M,M-1\ldots 1)\) 的两元素。
伪代码
Shell_sort(A) { //希尔增量序列 for (D=length(A)/2; D>0; D/=2) { //插入排序 for (P= D ; P<length(A); P++ ) { temp=A[P]; for (i=P;i>=D and A[i- D]>temp; i-=D) A[i]=A[i-D]; A[i]=temp; } } }
复杂度
最坏情况下\[f(n)=n^2\] ,但其复杂度还与增量序列的选取有关。例如,
Hibbard增量序列
- \(D_k=2^k-1\) (保证相邻元素为互质)
- 最坏情况下,\(f(n)=O(n^{\frac32})\) (已存在证明)
- 有猜想, \(f_{avg}(n)= O(n^{\frac54})\) (待证明)
Sedgewick增量序列
- {1,5,19,41,109,...},计算公式为\(9 \times 4^i -9 \times 2^i +1\)
- 猜想,\(f_{avg}(n)=O(n^{\frac76}), f_{worst}(n)=O(n^{\frac43})\) ,未证明。
快速排序
步骤
- 选择一个基准数
- 分别设置哨兵甲乙指向数列的首位和末位,哨兵乙不断向前进,并且每次比较当前数与基准数的大小,若小于基准数则停止;哨兵甲不断向后进,并且每次比较当前数与基准数的大小,若大于基准数则停止。
- 交换当前哨兵甲乙指向的数,继续按上述规则令甲乙行进,直到甲乙指向同一位置停止,将基准数与该位置交换;此时能保证基准数左侧的数都不大于基准数,基准数右侧的数都不小于基侧数。
- 将原数列划为两个新数列左侧和右侧,分别重复上述步骤,直到生成的数列只有一个元素,此时排序完成。
伪代码
Quick_sort(A,left,right)
{
base=A[left]
if left>=right
return
i,j=left,right
while i<j
{
while A[j]>=base and i<j
j--;
while A[i]<=base and i<j
i++;
if i<j
swap(A[i],A[j])
}
A[left],A[j]=A[j],base
Quick_sort(A,left,i-1);
Quick_sort(A,i+1,right);
}
复杂度
最坏情况下,可以认为每次数列分成的两个区间各包含
n-1
和1
个元素,则\(f(n)=\theta(n^2)\)
平均情况下,\(f(n)=O(n\log(n))\)
归并排序
步骤
分解:将
n
个元素分成各含n/2
个元素的子序列;解决:用合并排序法对两个子序列递归地排序;(子序列长度为1时递归停止,即单个元素视为已排好序)
合并:合并两个已排序的子序列以得到排序结果。
伪代码
Merge(A,p,q,r)
{
x,y=q-p+1,r-q
将A[p..q]复制到B[1..x], 将A[q+1..r]复制到C[1..y]
i,j,k=1,1,p
while i<=x and j<=y do
if B[i]<=C[j]
then
A[k]=B[i]
i=i+1
else
A[k]=C[j]
j=j+1
k=k+1
if i>x then 将C[j..y]复制到A[k..r]
else 将B[i..x]复制到A[k..r]
}
Merge_sort(A,p,r)
{
if p<r
then q=floor((p+r)/2)
Merge_sort(A,p,q)
Merge(A,p,q,r)
}
复杂度
\(f(n)=O(n\log(n))\)
地精排序
步骤
地精排序又称侏儒排序,取名源自一种荷兰的花园精灵。思想也很简单,
从首位开始,与相邻下一位比较,若小于下一位,则继续向后走;若大于下一位,交换两数位置,并回退到上一位,继续比较。到末位停止,排序完成。
伪代码
Gnome_sort(A)
{
i=0;
while i<length(A)
if i==0 or A[i-1] <= A[i]
i++;
else
{
swap(A[i],A[i-1]);
i--;
}
}
复杂度
在最好情况下,也就是数列已经排好的情况下,只要遍历一遍数列,\(f(n)=O(n)\) ;
但在最坏情况下,数列是反序时,每前进一步都要回退数步,此时\(f(n)=O(n^2)\) ,与冒泡排序相同。
鸡尾酒排序
步骤
鸡尾酒排序的排序效果像是“左右摇摆”,因为它每次循环是执行两次冒泡排序,将最大数移至数列末位,最小数移至数列首位。然后去掉首末位,在新数列继续两次排序......
伪代码
Cocktail_sort(A,left,right) { while left <right bubblesort(A,left,right);//冒泡排序找到其中最大的 right--; bubblesort(A,left,right);//冒泡排序找到其中最小的 left++; }
复杂度
它是对冒泡排序的改进,减少了遍历次数,一次循环便能找到两个数的正确位置;性能略优于冒泡排序;
但面对大数据量时,其复杂度与冒泡排序相同。\(f(n)=O(n^2)\)
奇偶排序
步骤
相对于冒泡排序是串行算法,奇偶排序可用于并行计算。即奇数列排一次序,偶数列排一次序,再反复,直至不再需要交换数据,则排序完成。
伪代码
Odd_even_sort(A)
{
change_flag,odd_flag=1,1;
while change_flag
{
change_flag=0;
for(i=1-odd_flag;i+1<length(A);i+=2)
{
if A[i]>A[i+1]
swap(A[i],A[i+1])
change_flag=1;
}
odd_flag=1-odd_flag;
}
}
复杂度
它的复杂度是\(O(n^2)\) ,但由于是并行计算,在多核的情况下,可以达到\({n^2}\over{m/2}\) ,m为排序分段 个数。
堆排序
步骤
堆:一颗顺序存储的完全二叉树。当结点元素都不大于子结点元素时,称为小根堆;当结点元素都不小于子结点元素时,称为大根堆。
首先构造一个大根堆(此时根结点为最大值)
交换第一个和最后一个元素,将最后一个元素出列。
将剩下元素重新调整为大根堆。
不断重复上述2,3,直至堆中只剩最后一个元素,元素出列。则所有出列的元素已排好序。
关于如何构造一个大根堆,这里有一个线性时间复杂度的调整算法。
当一个堆以数组形式顺序存储时,记父节点为
i
,则子节点为2i
和2i+1
。反之,可得到已知子节点的情形下,根节点的位置。伪代码
Build_Heap(A,i,N)
{
/*
* i->起点; N->数列长度
*/
//j表示当前位置
for(int j=i+N-1;j>i;j--)
{
//由于计算机中是以0为起点
if(j为奇数)
parent=j/2;
else
parent=(j-1)/2;
if A[parent]<A[j]
swap(A[parent],A[j]);
}
}
Heap_sort(A)
{
N=length(A);
Build_Heap(A,0,N);
for(int i=N-1;i>0;i--)
{
swap(A[0],A[i]);
Build_Heap(A,0,i);
}
}
复杂度
构造最大堆为\(O(n)\) ,堆排序的时间复杂度为\(O(n\log(n))\)
梳排序
步骤
梳排序改良自冒泡排序和快速排序,旨在消除数尾部的小数值。冒泡排序中,只比较相邻两项,间距为1,而梳排序中的间距是在循环中以固定比率递减,通常递减率设为
1.3
,起始间距为数列长度,且间距递减至1
,则排序已大致排好序,最后用冒泡排序修正。伪代码
Comb_sort(A)
{
for(int step=length(A); step >0; step/=1.3)
{
//开始冒泡排序
for( int i=0; i+step<N; i++)
{
if(A[i]>A[i+step])
{
swap(A[i],A[i+step]);
}
}
}
}
复杂度
梳排序效率比冒泡排序高得多,\(f(n)=O({n^2 \over 2^p} )\) ,p为递减率
圈排序
步骤
圈排序是遍历
n
个数,并且每次在形成一个完整的圈时停止。某元素应在的位置是数列中所有比它小的元素个数。例,0 1 2 3 4 5 56 42 12 31 26 73 原始数列 null 42 12 31 56 73 从第1个数开始,比56小的数有4个,将它移到位置4;关键字为26 null 26 12 31 56 73 比26小的数有1个,将它移到位置1;关键字为42 null 26 12 42 56 73 比42小的数有3个,将它移到位置3;关键字31 null 26 31 42 56 73 比31小的数有2个,将它移到位置2;关键字12 12 26 31 42 56 73 比12小的数有0个,将它移至位置0;此处为空,第一个圈完成。 12 26 31 42 56 73 第2个数26,就在位置1处,该圈完成 ... ... ... ... ... ... 第 i
个数 ...,该圈完成12 26 31 42 56 73 排序完成 伪代码
Cycle_sort(A)
{
for i from 0 to length(A)-1
{
//标记圈的起点
item=A[i];
pos=i;
//统计比当前item小的元素个数count
//将A[i]移至位置count处,并且新的item=A[count],pos=count
while pos!=i
{
//统计比当前item小的元素个数count
//将A[i]移至位置count处,并且新的item=A[count],pos=count
}
}
}
复杂度
任何情况下,\(f(n)=O(n^2)\) 它的实际效率比冒泡还低。
基数排序(LSD)
步骤
基数排序是按位比较的,取最大数有
n
位,有2种思路,LSD从低位开始排,MSD从高位开始排。LSD首先按照最低位排升序,然后按次低位排序,按次次低位排序.....按最高位排序。
最终数列以升序排列。
伪代码
Radix_sort(A)
{
val=Get_max(A);
digits=Get_digits(val);
for(int i=0; i<digits;i++)
{
B=Get_num(A,i);//获取数列中每个元素的第i位数字
sort(A,B);//将B按照取值依次放入r个分类里,并按照B中的顺序放置A
}
}
复杂度
设待排数列长度为
n
,比较关键字有k
个,则有k
趟循环,分配时间复杂度为\(O(n)\) ,收集放置的复杂度为\(O(r)\) ,则基数排序\(f(n)=O(k(n+r))\)
双调排序
步骤
伪代码
复杂度
标签:sort,数列,归纳,复杂度,元素,冒泡排序,排序 来源: https://www.cnblogs.com/zhangs7/p/10435772.html
本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享; 2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关; 3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关; 4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除; 5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。