常用排序算法.docx
《常用排序算法.docx》由会员分享,可在线阅读,更多相关《常用排序算法.docx(22页珍藏版)》请在冰豆网上搜索。
常用排序算法
一、冒泡(Bubble)排序
从第一个开始,两两比较,每一趟从选一个最大的数放最后 ,共n-1趟
----------------------------------Code从小到大排序n个数------------------------------------
voidBubbleSortArray()
{
for(inti=1;i {
for(intj=0;i {
if(a[j]>a[j+1])//比较交换相邻元素
{
inttemp;
temp=a[j];a[j]=a[j+1];a[j+1]=temp;
}
}
}
}
-------------------------------------------------Code------------------------------------------------
效率O(n²),适用于排序小列表。
二、选择排序
----------------------------------Code从小到大排序n个数--------------------------------
先把第一个当做最小的,从第二个开始依次比较,找到最小的,比较过程中只改变数组中指针i的值,最后把a[i]与a[min]交换,则第一个就是最小的了,继续下一循环,第i个每次都是后面最小的
voidSelectSortArray()
{
intmin_index;
for(inti=0;i {
min_index=i;
for(intj=i+1;j if(arr[j] if(min_index!
=i)//找到最小项交换,即将这一项移到列表中的正确位置
{
inttemp;
temp=arr[i];arr[i]=arr[min_index];arr[min_index]=temp;
}
}
}
-------------------------------------------------Code-----------------------------------------
效率O(n²),适用于排序小的列表。
三、插入排序
--------------------------------------------Code从小到大排序n个数-------------------------------------
voidInsertSortArray()
{
for(inti=1;i{
inttemp=arr[i];//temp标记为未排序第一个元素
intj=i-1;
while(j>=0&&arr[j]>temp)/*将temp与已排序元素从小到大比较,寻找temp应插入的位置*/
{
arr[j+1]=arr[j];
j--;
}
arr[j+1]=temp;
}
}
------------------------------Code--------------------------------------------------------------
最佳效率O(n);最糟效率O(n²)与冒泡、选择相同,适用于排序小列表
若列表基本有序,则插入排序比冒泡、选择更有效率。
四、壳(Shell)排序——缩小增量排序
-------------------------------------Code从小到大排序n个数-------------------------------------
voidShellSortArray()
{
for(intincr=3;incr<0;incr--)//增量递减,以增量3,2,1为例
{
for(intL=0;L<(n-1)/incr;L++)//重复分成的每个子列表
{
for(inti=L+incr;i {
inttemp=arr[i];
intj=i-incr;
while(j>=0&&arr[j]>temp)
{
arr[j+incr]=arr[j];
j-=incr;
}
arr[j+incr]=temp;
}
}
}
}
--------------------------------------Code-------------------------------------------
适用于排序小列表。
效率估计O(nlog2^n)~O(n^1.5),取决于增量值的最初大小。
建议使用质数作为增量值,因为如果增量值是2的幂,则在下一个通道中会再次比较相同的元素。
壳(Shell)排序改进了插入排序,减少了比较的次数。
是不稳定的排序,因为排序过程中元素可能会前后跳跃。
五、归并排序
----------------------------------------------Code从小到大排序---------------------------------------
voidMergeSort(intlow,inthigh)
{
if(low>=high) return;//每个子列表中剩下一个元素时停止
elseintmid=(low+high)/2;/*将列表划分成相等的两个子列表,若有奇数个元素,则在左边子列表大于右侧子列表*/
MergeSort(low,mid);//子列表进一步划分
MergeSort(mid+1,high);
int[]B=newint[high-low+1];//新建一个数组,用于存放归并的元素
for(inti=low,j=mid+1,k=low;i<=mid&&j<=high;k++)/*两个子列表进行排序归并,直到两个子列表中的一个结束*/
{
if(arr[i]<=arr[j];)
{
B[k]=arr[i];
I++;
}
else
{B[k]=arr[j];j++;}
}
for( ;j<=high;j++,k++)//如果第二个子列表中仍然有元素,则追加到新列表
B[k]=arr[j];
for( ;i<=mid;i++,k++)//如果在第一个子列表中仍然有元素,则追加到新列表中
B[k]=arr[i];
for(intz=0;z arr[z]=B[z];
}
-----------------------------------------------------Code---------------------------------------------------
效率O(nlogn),归并的最佳、平均和最糟用例效率之间没有差异。
适用于排序大列表,基于分治法。
六、快速排序
------------------------------------Code--------------------------------------------
/*快速排序的算法思想:
选定一个枢纽元素,对待排序序列进行分割,分割之后的序列一个部分小于枢纽元素,一个部分大于枢纽元素,再对这两个分割好的子序列进行上述的过程。
*/ voidswap(inta,intb){intt;t=a;a=b;b=t;}
intPartition(int[]arr,intlow,inthigh)
{
intpivot=arr[low];//采用子序列的第一个元素作为枢纽元素
while(low {
//从后往前栽后半部分中寻找第一个小于枢纽元素的元素
while(low=pivot)
{
--high;
}
//将这个比枢纽元素小的元素交换到前半部分
swap(arr[low],arr[high]);
//从前往后在前半部分中寻找第一个大于枢纽元素的元素
while(low {
++low;
}
swap(arr[low],arr[high]);//将这个枢纽元素大的元素交换到后半部分
}
returnlow;//返回枢纽元素所在的位置
}
voidQuickSort(int[]a,intlow,inthigh)
{
if(low {
intn=Partition(a,low,high);
QuickSort(a,low,n);
QuickSort(a,n+1,high);
}
}
----------------------------------------Code-------------------------------------
平均效率O(nlogn),适用于排序大列表。
此算法的总时间取决于枢纽值的位置;选择第一个元素作为枢纽,可能导致O(n²)的最糟用例效率。
若数基本有序,效率反而最差。
选项中间值作为枢纽,效率是O(nlogn)。
基于分治法。
七、堆排序
最大堆:
后者任一非终端节点的关键字均大于或等于它的左、右孩子的关键字,此时位于堆顶的节点的关键字是整个序列中最大的。
思想:
(1)令i=l,并令temp=kl;
(2)计算i的左孩子j=2i+1;
(3)若j<=n-1,则转(4),否则转(6);
(4)比较kj和kj+1,若kj+1>kj,则令j=j+1,否则j不变;
(5)比较temp和kj,若kj>temp,则令ki等于kj,并令i=j,j=2i+1,并转(3),否则转(6)
(6)令ki等于temp,结束。
-----------------------------------------Code---------------------------
voidHeapSort(SeqIAstR)
{//对R[1..n]进行堆排序,不妨用R[0]做暂存单元 intI; BuildHeap(R);//将R[1-n]建成初始堆for(i=n;i>1;i--)//对当前无序区R[1..i]进行堆排序,共做n-1趟。
{ R[0]=R[1];R[1]=R[i];R[i]=R[0];//将堆顶和堆中最后一个记录交换 Heapify(R,1,i-1); //将R[1..i-1]重新调整为堆,仅有R[1]可能违反堆性质 } }---------------------------------------Code--------------------------------------
堆排序的时间,主要由建立初始堆和反复重建堆这两部分的时间开销构成,它们均是通过调用Heapify实现的。
堆排序的最坏时间复杂度为O(nlgn)。
堆排序的平均性能较接近于最坏性能。
由于建初始堆所需的比较次数较多,所以堆排序不适宜于记录数较少的文件。
堆排序是就地排序,辅助空间为O
(1), 它是不稳定的排序方法。
堆排序与直接插入排序的区别:
直接选择排序中,为了从R[1..n]中选出关键字最小的记录,必须进行n-1次比较,然后在R[2..n]中选出关键字最小的记录,又需要做n-2次比较。
事实上,后面的n-2次比较中,有许多比较可能在前面的n-1次比较中已经做过,但由于前一趟排序时未保留这些比较结果,所以后一趟排序时又重复执行了这些比较操作。
堆排序可通过树形结构保存部分比较结果,可减少比较次数。
八、拓扑排序
例:
学生选修课排课先后顺序
拓扑排序:
把有向图中各顶点按照它们相互之间的优先关系排列成一个线性序列的过程。
方法:
在有向图中选一个没有前驱的顶点且输出
从图中删除该顶点和所有以它为尾的弧
重复上述两步,直至全部顶点均已输出(拓扑排序成功),或者当图中不存在无前驱的顶点(图中有回路)为止。
---------------------------------------Code--------------------------------------
voidTopologicalSort()/*输出拓扑排序函数。
若G无回路,则输出G的顶点的一个拓扑序列并返回OK,否则返回ERROR*/
{
intindegree[M];
inti,k,j;
charn;
intcount=0;
Stackthestack;
FindInDegree(G,indegree);//对各顶点求入度indegree[0....num]
InitStack(thestack);//初始化栈
for(i=0;i Console.WriteLine("结点"+G.vertices[i].data+"的入度为"+indegree[i]);
for(i=0;i {
if(indegree[i]==0)
Push(thestack.vertices[i]);
}
Console.Write("拓扑排序输出顺序为:
");
while(thestack.Peek()!
=null)
{
Pop(thestack.Peek());
j=locatevex(G,n);
if(j==-2)
{
Console.WriteLine("发生错误,程序结束。
");
exit();
}
Console.Write(G.vertices[j].data);
count++;
for(p=G.vertices[j].firstarc;p!
=NULL;p=p.nextarc)
{
k=p.adjvex;
if(!
(--indegree[k]))
Push(G.vertices[k]);
}
}
if(count Cosole.WriteLine("该图有环,出现错误,无法排序。
");
else
Console.WriteLine("排序成功。
");
}
----------------------------------------Code--------------------------------------
算法的时间复杂度O(n+e)。
九、锦标赛排序
锦标赛排序的算法思想与体育比赛类似。
首先将n个数据元素两两分组,分别按关键字进行比较,得到n/2个比较的优胜者(关键字小者),作为第一步比较的结果保留下来,
然后对这n/2个数据元素再两两分组,分别按关键字进行比较,…,如此重复,直到选出一个关键字最小的数据元素为止。
--------------------------------CodeinC---------------------------------------
#include
#include
#include
#include
#defineSIZE100000
#defineMAX1000000
structnode
{
longnum;//关键字
charstr[10];
intlastwin;//最后胜的对手
intkiller;//被击败的对手
longtimes;//比赛次数
}data[SIZE];
longCompareNum=0;
longExchangeNum=0;
longRead(charname[])//读取文件a.txt中的数据,并存放在数组data[]中;最后返回数据的个数
{
FILE*fp;
longi=1;
fp=fopen(name,"rw");
fscanf(fp,"%d%s",&data[i].num,data[i].str);
while(!
feof(fp))
{
i++;
fscanf(fp,"%d%s",&data[i].num,data[i].str);
}
return(i-1);
}
longCreate(longnum)//创建胜者树,返回冠军(最小数)在数组data[]中的下标
{
inti,j1,j2,max,time=1;
longmin;//记录当前冠军的下标
for(i=1;pow(2,i-1) ;
max=pow(2,i-1);//求叶子结点数目
for(i=1;i<=max;i++)//初始化叶子结点
{
data[i].killer=0;
data[i].lastwin=0;
data[i].times=0;
if(i>num)
data[i].num=MAX;
}
for(i=1;i<=max;i+=2)//第一轮比赛
{
++CompareNum;
if(data[i].num<=data[i+1].num)
{
data[i].lastwin=i+1;
data[i+1].killer=i;
++data[i].times;
++data[i+1].times;
min=i;
}
else
{
data[i+1].lastwin=i;
data[i].killer=i+1;
++data[i].times;
++data[i+1].times;
min=i+1;
}
}
j1=j2=0;//记录连续的两个未被淘汰的选手的下标
while(time<=(log(max)/log
(2)))//进行淘汰赛
{
for(i=1;i<=max;i++)
{
if(data[i].times==time&&data[i].killer==0)//找到一名选手
{
j2=i;//默认其为两选手中的后来的
if(j1==0)//如果第一位置是空的,则刚来的选手先来的
j1=j2;
else//否则刚来的选手是后来的,那么选手都已到场比赛开始
{
++CompareNum;
if(data[j1].num<=data[j2].num)//先来的选手获胜
{
data[j1].lastwin=j2;//最后赢的是j2
data[j2].killer=j1;//j2是被j1淘汰的
++data[j1].times;
++data[j2].times;//两选手场次均加1
min=j1;//最小数下标为j1
j1=j2=0;//将j1,j2置0
}
else//同理
{
data[j2].lastwin=j1;
data[j1].killer=j2;
++data[j1].times;