排序算法.docx
《排序算法.docx》由会员分享,可在线阅读,更多相关《排序算法.docx(11页珍藏版)》请在冰豆网上搜索。
排序算法
排序算法
第一节排序及其基本概念
一、基本概念
1.什么是排序
排序是数据处理中经常使用的一种重要运算。
设文件由n个记录{R1,R2,……,Rn}组成,n个记录对应的关键字集合为{K1,K2,……,Kn}。
所谓排序就是将这n个记录按关键字大小递增或递减重新排列。
2.稳定性
当待排序记录的关键字均不相同时,排序结果是惟一的,否则排序结果不唯一。
如果文件中关键字相同的记录经过某种排序方法进行排序之后,仍能保持它们在排序之前的相对次序,则称这种排序方法是稳定的;否则,称这种排序方法是不稳定的。
3.排序的方式
由于文件大小不同使排序过程中涉及的存储器不同,可将排序分成内部排序和外部排序两类。
整个排序过程都在内存进行的排序,称为内部排序;反之,若排序过程中要进行数据的内、外存交换,则称之为外部排序。
内排序适用于记录个数不是很多的小文件,而外排序则适用于记录个数太多,不能一次性放人内存的大文件。
内排序是排序的基础,本讲主要介绍各种内部排序的方法。
按策略划分内部排序方法可以分为五类:
插入排序、选择排序、交换排序、归并排序和分配排序。
二、排序算法分析
1.排序算法的基本操作
几乎所有的排序都有两个基本的操作:
(1)关键字大小的比较。
(2)改变记录的位置。
具体处理方式依赖于记录的存储形式,对于顺序型记录,一般移动记录本身,而链式存储的记录则通过改变指向记录的指针实现重定位。
为了简化描述,在下面的讲解中,我们只考虑记录的关键字,则其存储结构也简化为数组或链表。
并约定排序结果为递增。
2.排序算法性能评价
排序的算法很多,不同的算法有不同的优缺点,没有哪种算法在任何情况下都是最好的。
评价一种排序算法好坏的标准主要有两条:
(1)执行时间和所需的辅助空间,即时间复杂度和空间复杂度;
(2)算法本身的复杂程度,比如算法是否易读、是否易于实现。
第二节插入排序
插入排序的基本思想是:
每次将一个待排序的记录,按其关键字大小插入到前面已经排好序的记录集中,使记录依然有序,直到所有待排序记录全部插入完成。
一、直接插入排序
1.直接插入排序的思想
假设待排序数据存放在数组A[1..n]中,则A[1]可看作是一个有序序列,让i从2开始,依次将A插入到有序序列A[1..i-1]中,A[n]插入完毕则整个过程结束,A[1..n]成为有序序列。
2.排序过程示例 (用【】表示有序序列)
待排序数据:
【25】 54 8 54 21 1 97 2 73 15 (n=10)
i=2:
【25 54】 8 54 21 1 97 2 73 15
i=3:
【8 25 54】 54 21 1 97 2 73 15
i=4:
【8 25 54 54】 21 1 97 2 73 15
i=5:
【8 21 25 54 54】 1 97 2 73 15
i=6:
【1 8 21 25 54 54】 97 2 73 15
i=7:
【1 8 21 25 54 54 97】 2 73 15
i=8:
【1 2 8 21 25 54 54 97】 73 15
i=9:
【1 2 8 21 25 54 54 73 97】 15
i=10:
【1 2 8 15 21 25 54 54 73 97】 排序结束
3.算法实现
可在数组中增加元素A[0]作为关键值存储器和循环控制开关。
第i趟排序,即A的插入过程为:
①保存A→A[0]
②
③如果A[j]<=A[0](即待排序的A),则A[0]→A[j+1],完成插入;
否则,将A[j]后移一个位置:
A[j]→A[j+1];;继续执行③
对于上面的数据实例,i从2依次变化到10的过程中,j值分别为{1,0,3,1,0,6,1,7,3}
4.程序代码
procedureinsertsort(n:
integer);
vari,j:
integer;
begin
fori:
=2tondo
begin
a[0]:
=a;
j:
=i-1;
whilea[j]>a[0]do {决定运算次数和移动次数}
begin
a[j+1]:
=a[j];
j:
=j-1;
end;
a[j+1]:
=a[0];
end;
end;
5.算法分析
(1)稳定性:
稳定
(2)时间复杂度:
①原始数据正序,总比较次数:
n-1
②原始数据逆序,总比较次数:
③原始数据无序,第i趟平均比较次数=,总次数为:
④可见,原始数据越趋向正序,比较次数和移动次数越少。
(3)空间复杂度:
仅需一个单元A[O]
二、希尔排序
1.基本思想:
任取一个小于n的整数S1作为增量,把所有元素分成S1个组。
所有间距为S1的元素放在同一个组中。
第一组:
{A[1],A[S1+1],A[2*S1+1],……}
第二组:
{A[2],A[S1+2],A[2*S1+2],……}
第三组:
{A[3],A[S1+3],A[2*S1+3],……}
……
第s1组:
{A[S1],A[2*S1],A[3*S1],……}
先在各组内进行直接插人排序;然后,取第二个增量S2(2.排序过程示例
待排序数据:
序号12345678910
原始数据1289573296375457957
S1=5组别①②③④⑤①②③④⑤
排序结果1254532573789577996
S2=3组别①②③①②③①②③①
排序结果1254532573789577996
S3=2组别①②①②①②①②①②
排序结果5321237575479578996
S4=1组别①①①①①①①①①①
排序结果5123237545757798996
3.算法实现
由于在分组内部使用的是直接插入排序,因此排序过程只要在直接插入排序的算法上对j的步长进行修改就可以了。
4.程序代码
procedureshell(n:
integer);
vars,k,i,j:
integer;
begin
s:
=n;
repeat
s:
=round(s/2); {设置增量S递减}
fork:
=1tosdo {对S组数据分别排序}
begin
i:
=k+s; {第二个待排序数据}
whilei<=ndo {当i>n时,本组数据排序结束}
begin
a[0]:
=a;
j:
=i-s; {约定步长为S}
while(a[j]>a[0])and(j>=k)do
begin
a[j+s]:
=a[j];
j:
=j-s;
end;
a[j+s]:
=a[0];
i:
=i+s;
end;
end;
untils=1;
end;
5.算法分析
(1)稳定性:
不稳定
(2)时间复杂度:
①Shell排序的执行时间依赖于增量序列。
如实例所示,选择增量系列为5-2-1的比较次数<增量系列为5-3-2-1的比较次数。
②在直接插入排序中,数据越趋向于有序,比较和移动次数越少,Shell排序的目的则是增加这种有序趋势。
虽然看起来重复次数较多,需要多次选择增量,但开始时,增量较大,分组较多,但由于各组的数据个数少,则比较次数累计值也小,当增量趋向1时,组内数据增多,而所有数据已经基本接近有序状态,因此,Shell的时间性能优于直接插入排序。
(3)空间复杂度:
仅需一个单元A[O]
第三节交换排序
交换排序的基本思想是:
两两比较待排序的数据,发现两个数据的次序相反则进行交换,直到没有反序的数据为止。
一、冒泡排序
1.冒泡排序的思想
最多进行n-1趟排序,每趟排序时,从底部向上扫描,一旦发现两个相邻的元素不符合规则,则交换。
我们发现,第一趟排序后,最小值在A[1],第二趟排序后,较小值在A[2],第n-1趟排序完成后,所有元素完全按顺序排列。
2.排序过程示例
待排序数据:
53 33 19 53 3 63 82 20 19 39
第一趟排序:
3 53 33 19 53 19 63 82 20 39
第二趟排序:
3 19 53 33 19 53 20 63 82 39
第三趟排序:
3 19 19 53 33 20 53 39 63 82
第四趟排序:
3 19 19 20 53 33 39 53 63 82
第五趟排序:
没有反序数据,排序结束。
3.程序代码
procedureBubble(n:
integer);;
vartemp,i,j:
integer;
change:
boolean;
begin
fori:
=1ton-1do
begin
change:
=false;
forj:
=n-1downtoido
ifa[j]>a[j+1]then
begin
change:
=true;
temp:
=a[j];a[j]:
=a[j+1];a[j+1]:
=temp;
end;
ifnot(change)thenexit;
end;
end;
4.算法分析
(1)稳定性:
稳定
(2)时间复杂度:
①原始数据正序,需一趟排序,比较次数n-1=O(n)
②原始数据反序,需n-1趟排序,比较次数
③一般情况下,虽然不一定需要n-1趟排序,但由于每次数据位置的改变需要3次移动操作,因此,总时间复杂度高于直接插入排序。
(3)空间复杂度:
仅需一个中间变量
5.改进
可以在每趟排序时,记住最后一次发生交换发生的位置,则该位置之前的记录均已有序。
下一趟排序扫描到该位置结束。
利用这种方式,可以减少一部分比较次数。
二、快速排序
1.快速排序的思想
在A[1..n]中任取一个数据元素作为比较的“基准”(不妨记为X),将数据区划分为左右两个部分:
A[1..i-1]和A[i+1..n],且A[1..i-1]≤X≤A[i+1..n](1≤i≤n),当A[1..i-1]和A[i+1..n]非空时,分别对它们进行上述的划分过程,直至所有数据元素均已排序为止。
2.算法实现
可以使用递归函数实现这一算法。
假定待排序序列的下标范围为low~high。
借用两个整型变量i、j作为指针,约定初值分别为low、high。
排序过程:
①选定基准X(不妨用A[low])
②j向前扫描,直到A[j]保证了A[low..i-1]≤X
③i向后扫描,直到A>X,交换A与A[j],j-1。
保证了X≤A[j..high]
④继续执行②、③,直到i=j。
这时,X恰好在A位置上。
⑤对序列A[low..i-1]及A[i+1..high]按照上述规律继续划分,直到序列为空。
仔细分析算法,我们发现,在排序中,我们总是用基准X与另一数据交换,因此,一趟排序结束后,X就能确切定位其最终位置。
3.排序过程示例
待排序数据:
67 67 14 52 299 90 54 87 71
X=67 i j
扫描j i j
交换 54 67 14 52 299 90 67 87 71
扫描i ij
交换 54 67 14 52 299 67 90 87 71
j=i,结束 ij
第一趟排序后:
54 67 14 52 29 9 [67] 90 87 71
第二趟排序后:
9 29 14 52 [54] 67[67] 71 87 [90]
第三趟排序后:
[9] 29 14 52 [54 67 67 71]87[90]
第四趟排序后:
[9] 14[29]52 [54 67 67 71 87 90]
第五趟排序后:
[9 14 29 52 54 67 67 71 87 90]
4.程序代码
procedurequicksort(low,high:
integer);
vartemp,x,i,j:
integer;
begin
iflowbegin
x:
=a[low];i:
=low;j:
=high;
whilei begin
while(j>i)and(a[j]>=x)doj:
=j-1;{j左移}
temp:
=a;a:
=a[j];a[j]:
=temp;
i:
=i-1;
while(j>i)and(a<=x)doi:
=i+1; {i右移}
temp:
=a;a:
=a[j];a[j]:
=temp;
j:
=j-1;
end;
quicksort(low,i-1);
quicksort(i+1,high);
end;
end;
5.算法分析
(1)稳定性:
不稳定
(2)时间复杂度:
每趟排序所需的比较次数为待排序区间的长度-1,排序趟数越多,占用时间越多。
①最坏情况:
每次划分选取的基准恰好都是当前序列中的最小(或最大)值,划分的结果A[low..i-1]为空区间或A[i+1..high]是空区间,且非空区间长度达到最大值。
这种情况下,必须进行n-1趟快速排序,第i次趟去见长度为n-i+1,总的比较次数达到最大值:
n(n-1)/2=O(n2)
②最好情况:
每次划分所取的基准都是当前序列中的“中值”,划分后的两个新区间长度大致相等。
共需lgn趟快速排序,总的关键字比较次数:
O(nlgn)
③基准的选择决定了算法性能。
经常采用选取low和high之间一个随机位置作为基准的方式改善性能。
(3)空间复杂度:
快速排序在系统内部需要一个栈来实现递归,最坏情况下为O(n),最佳情况下为O(lgn)。
第四节选择排序
选择排序的基本思想是:
每一趟从待排序的数据中选出最小元素,顺序放在已排好序的数据最后,直到全部数据排序完毕。
一、直接选择排序
1.过程模拟
待排序数据92286284621656873366
第一趟排序16286284629256873366
第二趟排序16286284629256873366
第三趟排序16283384629256876266
第四趟排序16283356629284876266
第五趟排序16283356629284876266
第六趟排序16283356626284879266
第七趟排序16283356626266879284
第八趟排序16283356626266849287
第九趟排序16283356626266848792
2.程序代码
procedureselect(n:
integer);
vartemp,k,i,j:
integer;
begin
fori:
=1ton-1do
begin
k:
=i;
forj:
=i+1tondo
ifa[j]=j;
ifk<>ithen
begin
a[0]:
=a;a:
=a[k];a[k]:
=a[0];
end;
end;
end;
3.算法分析
(1)稳定性:
不稳定
(2)时间复杂度:
O(n2)
(3)空间复杂度:
仅需一个中间单元A[0]
二、堆排序
1.堆排序思想
堆排序是一种树形选择排序,在排序过程中,将A[1..n]看成是完全二叉树的顺序存储结构,利用完全二叉树中双亲结点和孩子结点之间的内在关系来选择最小的元素。
2.堆的定义:
n个元素的序列K1,K2,K3,…Kn称为堆,当且仅当该序列满足特性:
Ki≤K2i,Ki≤K2i+1(1≤i≤n/2)
堆实质上是满足如下性质的完全二叉树:
树中任一非叶子结点的关键字均大于等于其孩子结点的关键字。
例如序列{1,35,14,60,61,45,15,81}就是一个堆,它对应的完全二叉树如下图1所示。
这种堆中根结点(称为堆顶)的关键字最小,我们把它称为小根堆。
反之,若完全二叉树中任一非叶子结点的关键字均大于等于其孩子的关键字,则称之为大根堆。
图4_1最小堆 图4_2 最大堆
3.堆排序过程(以最大堆为例)
(1)调整堆
假定待排序数组A为{20,12,35,15,10,80,30,17,2,1}(n=10),初始完全树状态为:
图4_3 图4_4
结点(20)、(35)、(12)等,值小于其孩子结点,因此,该树不属最大堆。
为了将该树转化为最大堆,从后往前查找,自第一个具有孩子的结点开始,根据完全二叉树性质,这个元素在数组中的位置为i=[n/2],如果以这个结点为根的子树已是最大堆,则此时不需调整,否则必须调整子树使之成为堆。
随后,继续检查以i-1、i-2等结点为根的子树,直到检查到整个二叉树的根结点(i=1),并将其调整为堆为止。
调整方法:
由于A的左、右子树均已是堆,因此A[2i]和A[2i+1]分别是各自子树中关键字最大的结点。
若A不小于A[2i]和A[2i+1],则A没有违反堆性质,那么以A为根的子树已是堆,无须调整;否则必须将A和A[2i]与A[2i+1]中较大者(不妨设为A[j])进行交换。
交换后又可能使结点A[j]违反堆性质,同样由于该结点的两棵子树仍然是堆,故可重复上述的调整过程,直到当前被调整的结点已满足堆性质,或者该结点已是叶子结点为止。
以上图为例,经过调整后,最大堆为:
{80,17,35,12,10,20,30,15,2,1}。
如图4_4所示。
此堆作为排序的初始无序区。
(2)选择、交换、调整
①将建成的最大堆作为初始无序区。
②将堆顶元素(根)A[1]和A[n]交换,由此得到新的无序区A[1..n-1]和有序区A[n],且满足A[1..n-1]≤A[n]
③将A[1..n-1]调整为堆。
④再次将A[1]和无序区最后一个数据A[n-1]交换,由此得到新的无序区A[1..n-2]和有序区A[n-1..n],且仍满足关系A[1..n-2]≤A[n-1..n],同样要将A[1..n-2]调整为堆。
直到无序区只有一个元素A[1]为止。
说明:
如果需要生成降序序列,则利用最小堆进行操作。
4.程序代码
procedureeditheap(i:
integer;s:
integer);{堆的调整}
varj:
integer;
begin
if2*i<=sthen {如果该结点为叶子,则不再继续调整}
begin
a[0]:
=a;j:
=i;
ifa[2*i]>a[0]thenbegina[0]:
=a[2*i];j:
=2*i;end;
if(2*i+1<=s)and(a[2*i+1]>a[0])then
begina[0]:
=a[2*i+1];j:
=2*i+1;end;{获取最大值}
ifj<>ithen