排序法

排序法
排序法

排序法

一、插入排序

将待排序的数据分成两个区域:有序区和无序区,每次将一个无序区中的数据按其大小插入到有序区中的适当位置,直到所有无序区中的数据都插入完成为止。

设待排序的数据有6个,依次为12、8、10,14,6,2.其排序过程如图所示:

循环变量I 12 8 10 14 6 2

I=1 10 14 6 2

I=2 14 6 2

I=3 [8 ] 14 6 2

I=4 2

I=5

I=6

程序如下:

Procedure sort_inset;

Var I,j:integer;

Begin

For i:=2 to n do

begin

J:=I;

A[0]:=a[i];

While (a[j-1]>a[0]) and (j-1>=1) do {利用while循环找插入位置}

Begin

A[j]:=a[j-1];

J:=j-1;

End;

A[j]:=a[0]; {将数据插入到序列中}

End;

End;

算法评价:

不难发现,随数据的初始状态不同,插入排序所耗时间有很大差异。当数据的初始状态为正序,在每一步排序中仅需进行一次数据比较,且不发生数据记录的移动,即最小比较次数为n-1次。此时算法的复杂度为O(n)。当数据初始状态为反序时,则数据记录的比较次数和移动次都取最大值,即最大比较次数(2+3+4+…+n)=(n-1)(n+2)/2,最大移动次数为(n+4)(n-1)/2,取上述最小值和最大值的平均值,作为插入排序时所需进行关键字间的比较次数和移动记录的次数,约为n2/4,由此,插入排序算法的时间复杂度为O(n2)。

插入排序法简便,且容易实现,当待排数据量n很小时,这是一种很好的排序方法,但当n变大时,插入排序的时间开销不断增大,插入排序适用于大部分数据已形成正序或反序,只需插入个别数据的情况。

二、希尔排序

希尔排序是插入排序的一种,又称“缩小增量排序”,它在时间效率上较直接插入排序方法有较大的改进。

它的基本思想是:将大量的插入排序划分成小组的少量数据的插入排序。具体操作为先取一个小于n 的整数d1作为第一个增量,把全部数据分成d1个组。所有距离为d1的倍数的数据放在同一个组中。先在各组内进行插入排序,然后,取第二个增量d2

该方法实质上是一种分组插入法。以一组具体的实例来说明希尔排序的过程。设待排序的数据有6个,依次为12,8,10,14,6,2,其排序过程如图所示:

初始假设12 8 10 14 6 2

1214

8 6

10 2

第一步排序结果:12 6 2 14 8 10

12 2 8

6 14 10

第二步排序结果: 2 6 8 10 12 14

第三步排序结果: 2 6 8 10 12 14 希尔排的一个特点是:子序列的构成不是简单地“逐段分割”,而是相隔某个“增量”的数据组成一个子序列,如上列中,第一步排序时的增量为3,第二步排序时的增量为2,由于前两步的插入排序中数据是和同一子序列中的前一个数据进行比较,因此字较小的数据通常不是一步一步地往前挪动,而是跳跃式地往前移,从而使得在进行最生一步增量为1的插入排序时,序列已基本有序,只要作少量的数据比较和移动即可完成排序,因此希尔排序的时间复杂度较直接插入排序的低。

希尔排序的程序如下:

Procedure shell_insert(p:integer);

Var x,I,j,k:integer;

Begin

For i:=1 to p do

Begin

J:=I;

While j<=n-p do

Begin

X:=a[j+p]; K:=j;

While (k>0) and (x

Begin

A[k+p]:=a[k]; Dec(k,p);

End;

A[k+p]:=x;

Inc(j,p);

End;

End;

End;

算法评价:

(1)增量序列的选择

希尔排序的执行时间依赖于增量序列。好的增量序列的共同特征:

A.最后一个增量必须为1;

B.应该尽量避免序列中的值(尤其是相邻的值)互为倍数的情况

(2)希尔排序的时间性能优于直接插入排序

在希尔排序开始时增量较大,分组较多,每组的数据少,故各组内直接插入较快,后来增量dj逐渐缩小,分组数减少,而各组的数据增多,但由于已经按dj-1排过序,使数据较接近于有序状态,所以新的一步排序过程也较快。因此,希尔排序在较率上较直接插入排序有较大改进。

三、选择排序selection sort

基本思想:每一步从待排序的数据中选择最小(此处以正序为例,若为反序则正好相反)的数据,顺序放在已排好序的子序列的最后,直到全部数据排序完毕。

初始状态[ 12 8 10 14 6 2 ]

第一步排序 2 [ 8 10 14 6 12 ]

第二步排序 2 6 [ 10 14 8 12 ]

第三步排序 2 6 8 [ 14 10 12 ]

第四步排序 2 6 8 10 [ 14 12 ]

第五步排序 2 6 8 10 12 14

直接选择排序的程序如下:

Procedure sort_select;

Var I,j,k:integer;

Begin

For i:=1 to n-1 do

Begin

K:=I;

A[0]:=a[i];

For j:=i+1 to n do

If a[k]>a[j] then k:=j;

If k<>I then

Begin

A[i]:=a[k];

A[k]:=a[0];

End;

End;

End;

算法评价:

(1)数据比较次数

无论数据序列初始状态如何,在第i步排序中选出最小数据,需做n-i次比较。因此,总的比较次数为n(n-1)/2=O(n2);

(2)数据的移动次数:

当初始数据序列为正序时,移动次数为0.数据序列初态为反序时,每步排序均要执行交换操作,总的

移动次数取最大值3(n-1)。直接选择排序的平均时间复杂度为O(n2);

(3)稳定性分析:

直接选择排序是不稳定的。

四、冒泡排序

基本思想:

冒泡排序是将待排序的数据看成一个个重量不等的气泡,依据轻气泡不能在重气泡之下的原则,从下往上扫描,凡遇违反本原则的情况,就进行一次交换,使轻气泡“上浮”。如此反复,直到没有轻气泡在下,重气泡在上为止。

12 2 2 2 2 2

8 12 6 6 6 6

10 8 12 8 8 8

14 10 8 12 10 10

6 14 10 10 12 12

2 6 14 14 14 14

︼︼︼︼︼

初第第第第第

始一二三四五

数步步步步步

据排排排排排

序序序序序

后后后后后

冒泡排序的程序如下:

Procedure sort_bubble;

Var I,j:integer;

Begin

For i:=1 to n-1 do

For j:=n downto i+1 do

If a[j-1]>a[j] then

Begin

A[0]:=a[j-1];

A[j-1]:=a[j];

A[j]:=a[0];

End;

算法评价:

(1)算法的最好时间复杂度:

若数据的初始状态是正序的,一步扫描即可完成排序,所需的数据比较次数C和数据移动次数均达到最小值:Cmin=n-1 Mmin=0;冒泡排序的最好时间复杂度为O(n)。

(2)算法的最坏时间复杂度:

若数据是反序的,需要进行n-1步排序。每步排序要进行n-i次数据的比较(1<=i<=n-i),且每次比较都必须移动数据三次来达到交换数据位置。在这种情况下,比较和移动次数均达到最大值:

Cmax=n(n-1)/2=O(n2) Mmax=3n(n-1)/2=O(n2)

冒泡排序的最坏时间复杂度为O(n2)

(3)算法的平时间复杂度为O(n2)

虽然冒泡排序不一定要进行n-1步,但由于它的数据移动次数较多,故平均时间性能比直接插入排序要差得多。

(4)算法稳定性:冒泡排序是就地排序,且它是稳定的。

五、快速排序

快速排序是一种划分交换排序,它采用了一种分治的策略,通常称其为分治法。分治法的基本思想是将原问题分为若干规模更小但结构与原问题相似的子问题,递归地解这些子问题,然后将这些子问题的解组合为原问题的解。

快速排序的基本思想:

通过一步排序将待排序的数据分割成独立的两部分,其中一部分的数据比另一部分数据小,然后分别对这两部分数据继续进行划分、排序,直至整个序列有序。

基准元素可选择:第一个或最后一个或中间一个或随机一个

对于序列a[1..r],快速排序分三步:

(1)分解,将a[1..r]分解成两个非空子序列a[1..q]和a[q+1..r],使午a[1..q]中的数据小于a[q+1..r]中的数

(2)递归求解。通过递归调用快速排序对左、右子区间a[1..q]和a[q+1..r]快速排序。

(3)合并。由于分解是原地进行的,分解和递归求解后不需要做任何其他操作,a[1..r]的排序完。

下图展示了一次划分的过程及整个快速排序的过程。方框表示基准元素,它未参加真正的交换,只是在划分完成时才将它放入正确的位置上。

一次划分过程

初始序列12 8 10 14 6 2

选定基准元素8 10 14 6 2

I j

自j起向左扫描8 10 14 6 2

j

第一次交换后: 2 8 10 14 6

I

自i起向右扫描: 2 8 10 14 6

I

I向右扫描: 2 8 10 14 6

I

I向右扫描: 2 8 10 14 6

I

第二次交换后: 2 8 10 6 14

j

自j起向右扫描: 2 8 10 6 14

I j

第三次交换后: 2 8 10 6 14

各步排序之后的状态

初始假设[ 12 8 10 14 6 2 ]

一步排序后:[ 2 8 10 6 ] 12 [ 14 ]

二步排序后: 2 [ 8 10 6 ] 12 14

三步排序后: 2 [ 6 ] 8 [ 10 ] 12 14

最后的排序结果: 2 6 8 10 12 14

快速排序的程序如下:

Procedure sort_quick(l,r:integer);

Var I,j,x,y:integer;

Begin

I:=l;j:=r;

X:=a[(l+r)div 2];

Repeat

While a[i]

While x

If i<=j then

Begin

Y:=a[i];

A[i]:=a[j];

A[j]:=y;

Inc(i);

Dec(j);

End;

Until i>j;

If i

If l

End;

算法评价:

快速排序的时间主要耗费在划分操作上,对长度为r的区间进行划分,共需r-1次数据的比较。(1)最坏时间复杂度

最坏情况是每次划分选取的基准都是当前无序区中关键字最小(或最大)的记录,划分的结果是基准左边的子区间为空(或右边的子区间为空),而划分所得的另一个非空的子区间中记录数目,仅仅比划分前的无序区中记录个数少一个。

因此,快速排序必须做n-1次划分,第i次划分开始区间长度为n-i+1,所需的比较次数为n-i(1<=i<=n-1),故总的比较次数达到最大值Cmax=n(n-1)/2=O(n2);

(2)最好时间复杂度:

在最好情况下,每次划分所取的基准都是当前无序区的“中值”记录,划分的结果是基准的左、右两个无序子区间的长充大致相等。总的关键字比较次数为O(nlgn).

(3)平均时间复杂度:

尽管快速排序的最坏时间为O(n2),但就平均性而言,它是基于关键字比较的内部排序算法中速度最快者,快速排序亦因此而得名,它的平均时间复杂度为O(nlogn)。

(4)空间复杂度:

快速排序在系统内部需要一个栈来实现递归,若每次划分较为均匀,需栈空间为O (logn ),最坏情况下,所需的栈空间为O(n)。

六、 堆排序

1、 堆的定义

N 个元素的序列{k1,k2,…,kn},当且仅当满足如下关系时,称之为堆。 Ki<=K2i

Ki>=K2i

Ki<=K2i+1

Ki>=K2i+1

(=1,2,…,n div 2)

满足前一种关系称为小根堆,满足后一种关系称为大根堆。

大根堆和小根堆:

根结点(亦称为堆顶)的关键字是堆里所有结点关键字中最小者的堆称为小根堆。

根结点(亦称为堆顶)的关键字是堆里所有结点关键字中最大者的堆称为大根堆。如下图所示:

小根椎示例 大根椎示例

注意:堆中任一子树亦是堆

2、 堆的实质在存储

堆实质上是满足如下性质的完全二叉树,可用一维数组连续存储。

(1) 堆对应的完全二叉树中,所有非终端结点的值均不大于(或不小于)其左右儿子结点的值。

(2) 堆顶元素必为序列中n 个元素的最小值(或最大值)

(3) 在堆对应的完全二叉树中,若非终端结点的地址为k ,则它的父亲结的地址应为k/2,它的左

儿子结点的地址为2k ,右儿子结点的地址为2k+1.

3、 堆排序

若在输出堆顶是最小值(或最大值)后,使得剩2余n-1个元素的序列重又建成一个堆,则得到次小值。如此反复执行,便能得到一个有序序列,这个过程称为堆排序。

(1) 堆排序的特点:

堆排序是树型选择排序。其特点是:在排序过程中,将R[1..n]看成是一棵完全二叉树的顺序存储结构,利用完全二叉树中双亲结点和孩子结点之间的内在关系,在当前无序区中选择关键字最大(或最小)的记录。

(2) 堆排序与直接插入排序的区别:

直接选择排序中,为了从R[1..n]中选出关键字最小的记录,必须进行n-1次比较,然后在R[2..n]中选出关键字最小的记录,又需要做n-2次比较。事实上,后面的n-2次比较中,有许多比较可能在前面的n-1次比较中已经做过,但由于前一趟排序时未保存这些比较结果,所以后一趟排序时又重复执行了这些比较操作。

堆排序可通过树形结构保存部分比较结果,以减少比较次数。

(3) 实现堆排序需要解决的问题:

一是如何由一个无序序列建成一个初始堆;

10 15 36 25 30 70 70 36 30

15 10

25

二是如何在输出堆顶元素后,调整剩余元素成为一个新的堆。

解决办法:

用“筛选法”调整堆。每趟排序开始前R[1..i]是以R[1]为根的堆,在R[1]与R[i]交换后,新的无序区R[1..i-1]中只有R[1]的值发生了变化。故除R[1]可能违反堆性质外,其余任何结点为根的子树均是堆。因此,当被调整区间是R[low..hign]时,只须调整以R[low]为根的树即可。

具体实现:

R[low]是左、右子树均已是堆,这两棵子树的根R[2low]和R[2low+1]分别是各自子树中数据最小的结点。若R[low]不大于这两个孩子结点的数据,则R[low]未违反堆性质,以R[low]为根的树已是堆,无须调整;否则必须将R[low]和它的两个孩子结点中数据较小者进行交换,即R[low]与R[small](R[small=min(R[2low],R[2low+1])]交换。交换后双可能使结点R[small]违反堆性质。同样由于该结点的两棵子树仍然是堆,故可重复上述的调整过程,对以R[large]为根的树进行调整。此过程直至当前被调整的结点已满足堆性质,或者该结点已是叶子为止。上述过程就像过筛子一样,把较大的关键字逐层筛下去,而将较小的关键字逐层选上来。因此称为“筛选法”。

完整的堆排序程序:

Program heapsort;

Const n=100;

Var a:array[1..100] of integer;

I,x:integer;

Procedure adjust_down(I,m:integer);

Var x:integer;

Begin

While i*2<=m do

Begin

I:=i*2;

If (ia[i]) then inc(i);

If a[i]>a[I div 2] then

Begin

X:=a[I div 2];

A[I div 2]:=a[i];

A[i]:=x;

End

Else break;

End;

End;

Begin

Reandomize;

For i:=1 to n do a[i]:=random(100);

For i:=n div 2 downto 1 do adjust_down(I,n);

For i:=n downto 2 do

Begin

X:=a[i];

A[i]:=a[1];

A[1]:=x;

Adjust_down(1,i-1);

End;

For i:=1 to n do writeln(a[i]);

End.

算法评价:

堆排序的是时间主要由建立初始堆和反复重建堆这两部分的时间开销构成,堆排序的最坏时间复杂度为O(nlogn).堆排序的平均性能较接近于最坏性能。此外,堆排序仅需一个记录大小供交换用的辅助存储空间。由于建初始堆所需的比较次数较多,所以堆排序不适宜于记录数较少的文件。

七、基数排序(多关键字排序)

基数排序是和前面各类排序方法完全不同的一种排序方法。基数排序是借助多关键字排序的思想对单逻辑关键字进行排序的方法。什么是多关键字?每张扑克牌有两个“关键字”花色和面值,将扑克进行排序整理,通常是先按不同的花色分成四堆,再对每地堆按不同的面值调整顺序。

基数排序就是借助多关键字排序的思相,用“分配”和“收集”两种操作对单逻辑关键字进行排序的一种方法。如九个三位数分别是321,214,665,102,874,699,210,333,600。因为关键字是数值,且值都在0<=k<=999范围内,则可把每一个十位数字看成一个关键字,即可认为k由一个关键字(k1,k2,k3)组成,其中k1是百位数,k2是十位数,k3是个位数。

具体排序过程:

第一趟排序,以个位数关键字k3作为关键字分配,个位数是0的有210,600;个位数是1 的有321,……分配完成后,重新收集的结果为210,600,321,102,333,214,874,665,699;第二趟拓序,以十位数关键字k2作为关键字分配,十位数是0的有600,102;十位数是1 的有210,214,……三趟分配收集后的结果即为最终排序结果102,210,214,321,333,600,665,699,874。

基数排序程序:

Progam radixsort;

const n=8;

type link=^node;

node=record

data:integer;

next:link;

end;

var i,j,l,m,k:integer;

s:string;

p,p1:link;

a:array[1..n] of integer;

q,head:array[0..9] of link;

begin

writeln('Enter data:');

for i:=1 to n do read(a[i]);

for i:=5 downto 1 do

begin

for j:=0 to 9 do

begin new(head[j]);head[j]^.next:=nil;q[j]:=head[j] end;

for j:=1 to n do

begin

str(a[j],s);

for k:=1 to 5-length(s) do s:='0'+ s;

m:=ord(s[i])-48;

new(p); p^.data:=a[j]; p^.next:=nil;

q[m]^.next:=p; q[m]:=p;

end;

l:=0;

for j:=0 to 9 do

begin

p:=head[j];

while p^.next<>nil do

begin

l:=l+1;p1:=p;p:=p^.next; dispose(p1);a[l]:=p^.data;

end;

end;

end;

writeln('Sorted data:');

for i:= 1 to n do write(a[i]:6);

end.

八、各种内部排序主法的比较

选择排序主法需要考虑的因素如下:

(1)若n较小(n<=50),则可以采用直接插入乔序或选择排序。由于直接插入排序所需的记录移动操作较选择排序多,因而当记录本身信息量较大时,用直接选择排序较好。

(2)若文件的初始状态已经按关键字基本有序,则选用直接插入排序或冒泡排序为宜。

(3)若n较大,则应采用时间复杂度为O(nlogn)的排序方法,即快速排序、堆排序。快速排序是目前基于比较的排序法中被认为是最好的方法。

作业:

1、军方截获的信息由n(n<=1000)个数字组成,因为是敌国的高端秘密,所以一时不能破获。最原始的

想法就是对这n个数进行k次提问,每次提问只是对第i个数是多少感兴趣,现在要求编程完成k次提问。

输入:第一行n,以下n行是截获的数字,接着一行是k,接着是k行的提问数字

输出:k行依次对应提问的数字对应的输出数字

样例输入:样例输出:

5 7

121 121

1 121

126

121

7

34

2

4

3

相关主题
相关文档
最新文档