10.4
This presentation is the property of its rightful owner.
Sponsored Links
1 / 36

10.4 归并排序 基本思想 把 k(k ≥2)个有序子文件合并在一起,形成一个新的有序 文件。 同时归并 k 个有序子文件的排序过程称为 k- 路归并排序。 PowerPoint PPT Presentation


  • 69 Views
  • Uploaded on
  • Presentation posted in: General

10.4 归并排序 基本思想 把 k(k ≥2)个有序子文件合并在一起,形成一个新的有序 文件。 同时归并 k 个有序子文件的排序过程称为 k- 路归并排序。 2-路归并排序 ---归并2个有序子文件的排序。 例. 将有序文件 A 和 B 归并为有序文件 C。 A =(2,10,15,18,21,30) B =(5,20,35,40) 按从小至大的次序从 A 或 B 中依次取出2,5,10,15,...,40, 顺序归并到 C 中,得: C =(2,5,10,15,18,20,21,30,35,40).

Download Presentation

10.4 归并排序 基本思想 把 k(k ≥2)个有序子文件合并在一起,形成一个新的有序 文件。 同时归并 k 个有序子文件的排序过程称为 k- 路归并排序。

An Image/Link below is provided (as is) to download presentation

Download Policy: Content on the Website is provided to you AS IS for your information and personal use and may not be sold / licensed / shared on other websites without getting consent from its author.While downloading, if for some reason you are not able to download a presentation, the publisher may have deleted the file from their server.


- - - - - - - - - - - - - - - - - - - - - - - - - - E N D - - - - - - - - - - - - - - - - - - - - - - - - - -

Presentation Transcript


10 4 k k 2 k k

10.4 归并排序

基本思想

把k(k≥2)个有序子文件合并在一起,形成一个新的有序

文件。

同时归并k个有序子文件的排序过程称为k-路归并排序。

2-路归并排序---归并2个有序子文件的排序。

例. 将有序文件A和B归并为有序文件C。

A=(2,10,15,18,21,30)

B=(5,20,35,40)

按从小至大的次序从A或B中依次取出2,5,10,15,...,40,

顺序归并到C中,得:

C=(2,5,10,15,18,20,21,30,35,40)


10 4 k k 2 k k

一般地,2-路归并过程为:

假定文件r[low..high]中的相邻子文件(子表)

(r[low],r[low+1],...,r[mid])和(r[mid+1],...,r[high])

为有序子文件,其中:low≤mid<high 。

将这两个相邻有序子文件归并为有序文件y[low..high],即:

(y[low],y[low+1],...,y[high])

r[9..16]

y[9..16]

9

10

11

12

13

14

15

16

9

10

11

12

13

14

15

16

i→

k→

有序

子表

2-路归并

有序文件(表)

j→

有序

子表


10 4 k k 2 k k

 将两个有序子文件归并为有一个有序文件的算法

void merge(r,y,low,mid,high)

RecType r[],y[];int low,mid,high;

{ int k=i=low,j=mid+1;

while (i<=mid && j<=high)

{ if (r[i].key<=r[j].key)

{ y[k]=r[i];i++;}//归并前一个子文件的记录

else

   { y[k]=r[j];j++;} //归并后一个子文件的记录

   k++;}

while (j<=high) //归并后一个子文件余下的记录

{ y[k]=r[j]; j++; k++;}

while (i<=mid)  //归并前一个子文件余下的记录

{ y[k]=r[i]; i++; k++;}

} // merge


10 4 k k 2 k k

2-路归并排序

假定文件(r[1],r[2],...,r[n])中记录是随机排列的,进行

2-路归并排序,首先把它划分为长度均为1的n个有序子文件,

然后对它们逐步进行2-路归并排序。其步骤如下:

第1趟:从r[1..n]中的第1个和第2个有序子文件开始,调用

算法merge,每次归并两个相邻子文件,归并结果放到y[1..n]中。

在y中形成 n/2 个长度为2的有序子文件。若n为奇数,则y中最

后一个子文件的长度为1。

第2趟:把y[1..n]看作输入文件,将 n/2 个有序子文件两

两归并,归并结果回送到r[1..n]中,在r中形成 n/2/2个长度为4的有序子文件。若y中有奇数个子文件,则r中最后一个子文

件的长度为2。

......

共计经过 log2n 趟归并,最后得到n个记录的有序文件。


10 4 k k 2 k k

例1. 对8个记录作2路归并排序,共进行log28=3趟归并。

y[1..8]

y[1..8]

r[1..8]

r[1..8]

第1趟

第2趟

第3趟


10 4 k k 2 k k

y[1..8]

r[1..8]

y[1..8]

r[1..8]

r[1..8]

1

2

3

4

5

6

7

8

9

10

11

1

2

3

4

5

6

7

8

9

10

11

1

2

3

4

5

6

7

8

9

10

11

1

2

3

4

5

6

7

8

9

10

11

1

2

3

4

5

6

7

8

9

10

11

例2. 对11个记录作2-路归并排序,进行log211=4趟归并。

第2趟

第1趟

第3趟

第4趟


10 4 k k 2 k k

一趟归并排序算法:

void mergepass(r,y,s) // s为子文件的长度

RecType r[],y[];int s;//将r中的子文件归并到y中

{ int i=1;

while(i+2*s-1<=n) //两两归并长度均为s的子文件

{ merge(r,y,i,i+s-1,i+2*s-1);

i=i+2*s;

}

if (i+s-1<n) //最后两个子长度为s和长度不足s的文件

merge(r,y,i,i+s-1,n);

else

while(i<=n) //复制最后一个子文件,长度≤s

{ y[i]=r[i];

i++;

}

}


10 4 k k 2 k k

调用算法mergepass,对文件r[1..n]归并排序的算法

void mergesort(RecType r[],int n)

{ RecType y[n+1];

int s=1; //子文件初始长度为1

while (s<n)

{ mergepass(r,y,s); //将r[1..n]归并到y[1..n]

s=2*s; //修改子文件长度

mergepass(y,r,s); //将y[1..n]归并到r[1..n]

s=2*s; //修改子文件长度

}

}

算法分析

● 对n个记录的文件进行归并排序,共需 log2n趟,每趟

所需比较关键字的次数不超过n, 共比较O(nlog2n)次。

●每趟移动n个记录, 共移动O(nlog2n)个记录。

● 归并排序需要一个大小为n的辅助空间y[1..n]。

● 归并排序是稳定的。


10 4 k k 2 k k

10.5 交换排序

10.5.1 冒泡排序

基本思想: 设待排序的文件为r[1..n]

第1趟(遍):从r[1]开始,依次比较两个相邻记录的关键字

r[i].key和r[i+1].key,若r[i].key>r[i+1].key,则交换记录

r[i]和r[i+1]的位置;否则,不交换。

(i=1,2,...n-1)

第1趟之后,n个关键字中最大的记录移到了r[n]的位置上。

第2趟:从r[1]开始,依次比较两个相邻记录的关键字

r[i].key和r[i+1].key,若r[i].key>r[i+1].key,则交换记录

r[i]和r[i+1]的位置;否则,不交换。

(i=1,2,...n-2)

第2趟之后,前n-1个关键字中最大的记录移到了r[n-1]的位

置上。

......

作完n-1趟,或者不需再交换记录时为止。


10 4 k k 2 k k

例: 第1趟 第2趟 第3趟 第4趟

┌─────────┐ ┌─┐ ┌─┐ ┌─┐

k1= 43 21 21 21 21 21 21 21 21 15 15 15

k2= 21 43 43 43 43 43 43 15 15 21 21 21

k3= 89 89 89 15 15 15 15 28 28 28 28 28

k4= 15 15 15 89 28 28 28 43 43 4343 43

k5= 28 28 28 28 89 43 43 4343 43 43 43

k6= 43 43 43 43 43 8989 8989 89 89 89

比较次数=5+4+3+2=14

交换记录的次数=3+2+1=6,移动记录次数=3*6=18


10 4 k k 2 k k

算法分析

● 最好情况: 待排序的文件已是有序文件,只需要进行1趟

排序,共计比较关键字的次数为

n-1

不交换记录。

●最坏情况: 要经过n-1趟排序,所需总的比较关键字的次

数为

(n-1)+(n-2)+...+1=n(n-1)/2

交换记录的次数最多为

(n-1)+(n-2)+...+1=n(n-1)/2

移动记录次数最多为

3n(n-1)/2 。

●只需要少量中间变量作为辅助空间。

● 算法是稳定的。


10 4 k k 2 k k

冒泡排序算法

// 对n个整数按递增次序作冒泡排序

Void bubble1(int a[],int n)

{ int i,j,temp;

for(i=0;i<n-1;i++) //作n-1趟排序

for(j=0;j<n-1-i;j++)

if (a[j]>a[j+1])

{ temp=a[j]; //交换记录

a[j]=a[j+1];

a[j+1]=temp;

}

for(i=0;i<n;i++)

printf("%d",a[i]); //输出排序后的元素

}


10 4 k k 2 k k

改进的冒泡排序算法

void bubblesort(RecType r[],int n)

{ int i,j,swap; RecType temp;

j=1; //置比较的趟数为1

do{ swap=0; //置交换标志为0

for (i=1;i<=n-j;i++)

{ if (r[i].key>r[i+1].key)

{ temp=r[i]; //交换记录`

r[i]=r[i+1];

r[i+1]=temp;

swap=1; //置交换标志为1

}

j++; //作下一趟排序

}

} while (j<n && swap);

} //未作完n-1趟,且标志为1


10 4 k k 2 k k

10.5.2 快速排序

基本思想:首先在r[1..n]中,确定一个r[i],经过比较和

移动,将r[i]放到"中间"某个位置上,使得r[i]左边所有记录

的关键字小于等于r[i].key,r[i]右边所有记录的关键字大于等

于r[i].key。以r[i]为界,将文件划分为左、右两个子文件。

用同样的方法分别对这两个子文件进行划分, 得到4个更小

的子文件。继续进行下去,使得每个子文件只有一个记录为止,

便得到原文件的有序文件。

例. 给定文件(20,05,37,08,63,12,59,15,44,08),选

用第1个元素20进行划分:

x

↑ ↑

i

j


10 4 k k 2 k k

x

↑ ↑

i

j

x

↑ ↑ ↑ ↑

j

i

x

↑ ↑ ↑ ↑

i

j

x

↑ ↑

i

j


10 4 k k 2 k k

x

↑ ↑ ↑ ↑

i

j

x

↑ ↑ ↑ ↑

i

j

x

i

j

x

左子文件

右子文件

i

j


10 4 k k 2 k k

快速排序

void quksort(r,low,high)

RecType r[];int low,high;

{ RecType x;int i,j;

if (low<high) //有两个以上记录

{ i=low;j=high;x=r[i]; //保存记录到变量x中

do{ while (i<j && r[j].key>x.key)

j--; //j向左端扫描

if (i<j) //i,j未相遇

{ r[i]=r[j]; i++;

while(i<j && r[i].key<x.key)

i++; //i向右端扫描

if (i<j)

{ r[j]=r[i];j--;

}

}

} while (i!=j); //i,j未相遇


10 4 k k 2 k k

r[i]=x; //划分结束

quksort(r,low,i-1); //递归处理左子文件

quksort(r,i+1,high); //递归处理右子文件

}

}

对文件r[1..n]快速排序

void quicksort(RecType r[],int n)

{ quksort(r,1,n);

}


10 4 k k 2 k k

算法分析

● 就平均速度而言,快速排序是已知内部排序方法中最好

的一种排序方法,其时间复杂度为O(nlog(n))。

●但是,在最坏情况下,快速排序所需的比较次数和冒泡

排序的比较次数相同,其时间复杂度为O(n2)。

●快速排序需要一个栈作辅助空间,用来实现递归处理左、

右子文件。在最坏情况下,递归深度为n,因此所需栈的空间大

小为O(n)数量级。

● 快速排序是不稳定的。


10 4 k k 2 k k

10.6 基数排序

基数排序的一般过程:

将任一关键字K看做一个d元组:K=(K1,K2, … ,Kd)

其中:K1是最高位, Kd是最低位,按组成关键字的每一位的值进行排序。

排序过程(以十进制正整数为例):

设文件有n个记录(R1,R2, … ,Rn),记录Ri的关键字为Ki,且Ki是不超d位的非负整数。

ⅰ:建立十个队列,编号分别为0,1,2, … ,9

ⅱ:重复执行d遍

⒈分配:若Ki【d】=j,则Ri放入第j号队列(j=0,1,…,9)

⒉收集:按0,1, … ,9的次序将各队列中的记录收集和排列起来。

⒊ d=d-1


10 4 k k 2 k k

队列

例:设有关键字序列:

分配


10 4 k k 2 k k

队列

队列

收集

分配


10 4 k k 2 k k

队列

收 集

问题:各队列的长度

如何确定?


10 4 k k 2 k k

A[ ]

例2:已知 n=12, d=2,r=10(12个2位的十进制整数,低位优先)

COUNT

[0..9]

各队列中的记录个数

分配

收集

B[ ]


10 4 k k 2 k k

0 1 2 3 4 5 6 7 8 9 10 11

a[ ]

分配

count[0..9]

收集

b[ ]

0 1 2 3 4 5 6 7 8 9 10 11


10 4 k k 2 k k

参考程序如下:

for (i=0,d=1;i<k; i++,d*=r)

{

for ( j=0; j<r; j++) count[j]=0; //初始化

for( j=0; j<n; j++) count[ a[j] / d % r]++;

//统计各队列中的记录个数

for ( j=1; j<r; j++) count[j]=count[j-1]+count[j]; //分配

for( j=n-1; j>=0; j--) b[--count[a[j] / d % r]]=a[j]; //收集

for( j=0; j<n; j++) a[j]=b[j];

}

注: d — 1为个位,10为十位,…

k — 整数的最大位数;

r — 数值的基数(如八进制数,十进制数等);

n —关键字个数。


10 4 k k 2 k k

  • 内排序小结

    内排序方法

    ●插入排序:直接插入排序;

    ●选择排序:简单选择/选择排序;堆排序

    ●归并排序

    2-路归并排序

    k-路归并排序

    ● 交换排序

    冒泡排序: 快速排序

    ● 基数排序

    最低位优先法


10 4 k k 2 k k

.什么是排序,什么是排序的稳定性

排序算法分析

(1)时间复杂度

● 对n个记录排序,所需比较关键字的次数;

最好情况;最坏情况;平均情况

● 对n个记录排序,所需移动记录的次数;

最好情况;最坏情况;平均情况

(2)空间复杂度

排序过程中,除文件中的记录所占的空间外,

所需的辅助存储空间的大小。


10 4 k k 2 k k

第11章 外部排序

  • 外部排序思想

    设磁盘有4500个记录的文件,磁盘的读∕写单位是250个记录的数据块,内存只能提供1500个记录的空间,试对此文件进行排序。

    解:ⅰ⑴ 从磁盘文件输入三个数据块,计750个记录至内存,排序后再写入磁盘;

    ⑵ 按⑴同样的方法得如下归并段:

R1 R2 R3 R4 R5 R6

1~750 751~1500 1501 ~2250 2251 ~3000 3001 ~ 3750 3751 ~ 4500


10 4 k k 2 k k

R1 R2

磁盘(1500个记录)

部分有序

ⅱ ⑴归并R1,R2

BF1

BF2

内存区

3块计750个记录

BF3

磁盘(1500个记录)

整体有序

R12


10 4 k k 2 k k

R1 R2 R3 R4 R5 R6

ⅱ ⑵ 按⑴同样得方法得:R34,R56,R1234,R123456

R12

R34

R56

1遍

1500

1500

1500

2/3遍

R1234

3000

1遍

R123456

4500个记录整体有序


10 4 k k 2 k k

建立初始归并段要把全部记录(4500)读写一遍,完成全部记录的归并要把(4500)个记录读写8/3遍。下面分析三路归并的情况:

R1 R2 R3 R4 R5 R6

完成全部记录的归并要把(4500)个记录读写2遍。


10 4 k k 2 k k

结论:M个初始归并段进行K路归并时,归并的趟数为:S = log(K(M))

证明:设M为K的整幂次,即M=K**s (s为整数)

第一趟归并得 k**(s-1)个为归并段;

第二趟归并得 k**(s-2)个为归并段;

第s趟归并得 k**s-s=1个归并段;

由m=k**s ,有log(k(m))=s

当m不是k的整幂次时,归并趟数是

log(k(m))的上界函数log(K(M))证毕

上式说明:要s ,只有K 或m


10 4 k k 2 k k

权值愈小的结点,离根愈近?

  • 最佳归并树

    设有8个初始归并段,其长度(以外存物理块为单位)分别为2,3,6,9,12,17,18,24,求其3路最佳归并树,并求其WPL和对外存的访问次数。

    解1:

2

3

6

9

11

12

17

18

24

32

59

0

91

WPL=(2+3+6)*3+(9+12) *2+(17+18+24)*2

=193

读写次数=WPL*2

=386


10 4 k k 2 k k

0

2

3

权值愈小的结点,离根愈远!

  • 解2:∵ k-(m-1) mod (k-1)-1 =3-(8-1) mod (3-1)-1=1

    ∴ 虚设一个长度为“0”的归并段。

5

6

9

12

17

18

20

24

47

91

WPL=(0+2+3)*3+(6+9+12+17+18)*2+24*1=163

读写次数=WPL*2=326


10 4 k k 2 k k

如何判定虚设段的个数?

令 M ——初始归并段的个数

K ——归并的路数

若(M-1) MOD (K-1)= 0,则不加虚设段;

否则需加 P个虚设段。

P = K-(M-1) MOD (K-1) -1


  • Login