第8章 排序.docx

上传人:b****5 文档编号:29525238 上传时间:2023-07-24 格式:DOCX 页数:47 大小:163.86KB
下载 相关 举报
第8章 排序.docx_第1页
第1页 / 共47页
第8章 排序.docx_第2页
第2页 / 共47页
第8章 排序.docx_第3页
第3页 / 共47页
第8章 排序.docx_第4页
第4页 / 共47页
第8章 排序.docx_第5页
第5页 / 共47页
点击查看更多>>
下载资源
资源描述

第8章 排序.docx

《第8章 排序.docx》由会员分享,可在线阅读,更多相关《第8章 排序.docx(47页珍藏版)》请在冰豆网上搜索。

第8章 排序.docx

第8章排序

第8章  排序

基本知识点

排序

关键字

记录

内部排序

外部排序

插入排序

交换排序

选择排序

归并排序

基数排序

静态链表

顺序表

8.1内容要点

排序(Sorting)是计算机程序设计中的一种重要操作,其功能是将一个数据元素集合或任意序列,重新排列成一个按关键字有序的序列。

在许多计算机应用领域(如计算机信息处理、数据库系统等)都要用到排序。

8.1.1排序的基本概念

假定含n个记录的序列为{R1,R2,…,Rn},其相应的关键字序列为{K1,K2,…,Kn},使序列{R1,R2,…,Rn}成为一个按关键字有序的序列{Rp1,Rp2,Rp3,…,Rpn}的一种操作称为排序。

其中,对应1,2,…,n的一种排列p1,p2,p3,…,pn,相对应的关键字序列{Kp1,Kp2,Kp3,…,Kpn}递增或递减有序。

如果待排序的记录数量不大,可直接在计算机主存储器中进行的排序称为内部排序;而在排序过程中需要对辅助存储器进行访问的排序则称为外部排序。

如果待排元素序列,对它按关键码进行排序,若相同关键码元素间的位置关系,排序前与排序后保持一致,则为稳定排序;而不能保持一致的排序方法则为不稳定排序。

在排序过程中通常需要进行两种基本操作:

比较关键字的大小和将记录从一个位置移动至另一个位置。

其中移动操作可通过记录的存储方式的改变来避免。

8.1.2 记录序列的存储方式

待排序的记录序列可以有以下三种存储方式:

⑴ 待排序的记录存放在地址连续的一组存储单元中,在序列中相邻的两个记录的存储位置也相邻,即记录之间的次序关系由存储位置决定,实现排序必须借助移动记录。

⑵ 待排序的记录存放在静态链表中,记录间的次序关系由指针指示,实现排序可不需移动记录,仅需修改指针即可。

⑶ 待排序记录本身存放在一组地址连续的存储单元中,另设一指示各个记录存储位置的地址向量,在排序过程中不移动记录本身,而只移动地址向量中这些记录的“地址”。

在第二种存储方式下实现的排序又称(链)表排序,在第二种存储方式下实现的排序又称地址排序。

我们主要实现在第一种存储方式下的各种排序。

8.2内部排序

  按排序的策略不同可将内部排序划分为插入排序、交换排序、选择排序、归并排序和基数排序五种类型。

  

8.2.1插入排序

1.直接插入排序

思想方法:

在插入第i个记录Ri时R1,R2,…,Ri-1已经排好序,这时将待排记录Ri的关键字Ki依次与关键字Ki-1,Ki-2,…,K1进行比较,从而找到应该插入的位置j,并将Rj,Rj+1,…,Ri-1顺序后移一个位置,然后将Ri插入到位置j,从而使前i个位置的所有记录保持有序。

2.折半插入排序

思想方法:

在直接插入排序法的基础上,通过待插入记录与已排好序的表居中的记录按关键字进行比较,将有序表一分为二,下次比较就只在其中一个有序子表中进行,如果还没有确定插入位置,再将子表又一分为二。

这样继续下去,直到要比较的子表中只有一个记录时,比较一次便确定了插入位置。

即折半插入排序的“查找”过程利用了“折半查找”。

3.表插入排序

直接插入排序、折半插入排序均要大量移动记录。

表插入排序,是通过链接指针,按关键字的大小,实现从小到大的链接过程。

为此需增设一个指针项。

操作方法与直接插入排序类似,即仍是将一个记录插入到已排好序的有序表中,不同的是直接插入排序要移动记录,而表插入排序是修改链接指针。

表插入排序的待排记录序列以静态链表作为存储结构。

表插入排序的结果是一个有序的链表,只能进行顺序查找,要实现随机查找,还需要对记录进行重排。

4.希尔排序

希尔排序又称缩小增量排序。

其思想方法是:

选择一个递减的步长序列t1,t2,…,tk,其中tk=1。

按步长序列个数k,对序列进行k趟排序。

每趟排序时,根据对应的步长ti,将待排序列分割成ti个子序列,分别对各子表进行直接插入排序。

仅当步长因子为1时,整个序列作为一个表来处理。

但此时由于前面的工作,整个待排记录序列已基本有序,再对全体记录进行一次直接插入排序即可完成排序工作。

希尔排序法中的步长因子可以有多种取发,但应使因子序列中没有除1以外的公因子,并且最后一个因子必须是1。

8.2.2交换排序

1.冒泡排序

思想方法:

对n个记录的表,从前至后依次两两比较、交换,重新安排存放顺序,得到一个关键字最大的记录r[n],称为第一趟冒泡;对余下的前n-1个记录的表,再从前至后依次两两比较、交换,重新安排存放顺序,得到一个关键字最大的记录r[n-1],第二趟冒泡结束;如此重复,进行n-1趟冒泡后,n个记录即成为按关键字由小到大排列有序的表。

2.快速排序

思想方法:

在待排记录序列中,任取其中一个记录(常选第一个记录),以该记录的关键字为界,经过一趟排序后,所有关键字比它小的记录都交换到它前面,而比它大的交换到它的后面;然后再分别对这前后两部分记录重复上述过程,直到整个序列有序。

快速排序被认为是平均性能最好的一种排序方法,但快速排序需要栈空间来实现递归。

8.2.3选择排序

选择排序主要是每一趟从待排序列中选取一个关键字最小的记录,也即第一趟从n个记录中选取关键字最小的记录作为第一个记录,第二趟从剩下的n-1个记录中选取关键字最小的记录作为第二个记录,直到整个序列的记录选完。

这样,由选取记录的顺序,便得到按关键字有序的序列。

1.简单选择排序

思想方法:

第一趟,从n个记录中找出关键码最小的记录与第一个记录交换;第二趟,从第二个记录开始的n-1个记录中再选出关键码最小的记录与第二个记录交换;如此,第i趟,则从第i个记录开始的n-i+1个记录中选出关键码最小的记录与第i个记录交换,直到整个序列按关键码有序。

2.堆排序

设有n个元素的序列k1,k2,…,kn,当且仅当满足下述关系之一时,称之为堆。

(i=1,2,…,[n/2])

若以一维数组存储一个堆,则堆对应一棵完全二叉树,且所有非终端结点的值均不大于

(或不小于)其子女的值。

因此,若序列{k1,k2,…,kn}是堆,则堆顶元素(根结点)的值是最小(或最大)的。

大顶堆、小顶维示意如图8.1所示。

 

图8.1两个堆示例

设有n个元素,将其按关键码排序。

首先将这n个元素按关键码建成堆,将堆顶元素输出,得到n个元素中关键码最小(或最大)的元素。

然后,再对剩下的n-1个元素建成堆,输出堆顶元素,得到n个元素中关键码次小(或次大)的元素。

如此反复,便得到一个按关键码有序的序列。

称这个过程为堆排序。

实现堆排序需解决两个问题:

1如何将n个元素的序列按关键码建成初始堆;

对初始序列建堆的过程,就是一个反复进行筛选的过程。

若将此序列看成一个n个结点的完全二叉树,则最后一个非终端结点是第[n/2]个元素,由此“筛选”只需从第[n/2]个元素开始,使该子树成为堆,之后向前依次对各结点为根的子树进行筛选,使之成为堆,直至根结点。

② 输出堆顶元素后,怎样调整剩余n-1个元素,使其按关键码成为一个新堆。

调整方法:

设有m个元素的堆,输出堆顶元素后剩下m-1个元素。

将堆底元素送入堆顶,堆被破坏,其原因仅是根结点不满足堆的性质。

将根结点与左、右子女中较小(或小大)的进行交换。

若与左子女交换,则左子树堆被破坏,且仅左子树的根结点不满足堆的性质;若与右子女交换,则右子树堆被破坏,且仅右子树的根结点不满足堆的性质。

继续对不满足堆性质的子树进行上述交换操作,直到叶子结点,堆被建成。

称这个自根结点到叶子结点的调整过程为筛选。

8.2.4归并排序

归并排序是又一类不同的排序方法,“归并”的含义是将两个或两个以上的有序表组合成一个新的有序表。

思想方法:

假设初始序列含有n个记录,则可看成是n个只含一个记录的有序子序列,然后两两归并,得到[n/2]个长度为2或1的有序子序列,再两两归并,如此重复,直到得到长度为n的有序序列为止,这种方法就称为2-路归并排序。

8.2.5基数排序

基数排序是一种借助于多关键字排序的思想,是将单关键字按基数分成“多关键字”进行排序的方法。

链式基数排序方法是:

将关键字拆分为若干项,每项作为一个“关键字”,则对单关键字的排序可按多关键字排序方法进行。

比如,关键字为4位的整数,可以每位看成是一个关键字,拆分成4项;又如,关键字由5个字符组成的字符串,可以每个字符作为一个关键字。

由于这样拆分后,每个关键字都在相同的范围内(对数字是0~9,字符是'a'~'z'),称这样的关键字可能出现的符号个数为“基”,记作RADIX。

上述取数字为关键字的“基”为10;取字符为关键字的“基”为26。

基于这一特性,用LSD(最低位优先)法排序较为方便。

基数排序:

从最低位关键字起,按关键字的不同值将序列中的记录“分配”到RADIX个队列中,然后再“收集”之。

如此重复d次即可。

链式基数排序是用RADIX个链队列作为分配队列,关键字相同的记录存入同一个链队列中,收集则是将各链队列按关键字大小顺序链接起来。

8.3排序实践

实践一直接插入排序的实现

1.实验目的

通过本实验,掌握直接插入排序算法的思想方法和实现该算法的基本技术。

2.实验内容

从键盘输入记录数,依次输入各记录元素数值,以这些数值的和为关键字进行排序。

最后在屏幕上输出排序后的记录。

3.实验要点及说明

设四个记录r[1]、r[2]、r[3]、r[4]的关键字依次为2、10、18、25,它们已经构成一个递增有序表,现向该表中插入新的关键字为9的记录,过程描述如图8.2所示。

r[1]r[2]r[3]r[4]r[5]存储单元

21018259将r[5]插入四个记录的有序表中,j=5

r[0]=r[j];i=j-1;将待插入的记录置于r[0]中,设置待插入位置

2101825□r[i+1]即r[5]为待插入位置

i=4,r[0]

入位置前移,直到r[0]≥vr[i]。

21018□25

i=3,r[0]

210□1825

i=2,r[0]

2□101825

i=1,r[0]≥r[i],r[i+1]=r[0];插入位置确定其记录号为i+1,将保存在r[0]的记录该位置

29101825向有序表中插入一个记录的过程结束

图8.2直接插入排序示例

实验中定义的直接插入排序函数,其形参为待排序的记录的首地址,主函数中输入记录数和各记录的非关键字数据,并求出关键字的值;输入结束后显示排序前的记录,然后调用直接插入排序函数,再显示排序后的结果。

4.参考程序

//头文件px1.h

//功能:

定长静态顺序表结构说明及初始数据输入、输出

#defineMAXSIZE20//顺序表的最大长度

#include

typedefintKeyType;//定义关键字类型为整数类型

typedefstruct

{

KeyTypekey;

intotherinfo[4];

charname[8];

}RedType;//定义记录结构类型

typedefstruct

{

RedTyper[MAXSIZE+1];

intlength;

}SqList;//定义顺序表结构类型

voidcreateSqlist(SqList&array)//建立待排序记录顺序表

{

inti,j,tt;

printf("\nInputthemumberofrecorde:

");

scanf("%d",&array.length);//输入记录数(顺序表长度)

for(i=1;i<=array.length;++i)

{

printf("\nInputtheinfomationofrecorde%d:

\n",i);

printf("inputthename:

");

scanf("%s",array.r[i].name);//输入第i个记录的姓名域的值

printf("\nInput4scores:

\n");

tt=0;

for(j=0;j<4;++j)

{

scanf("%d",&array.r[i].otherinfo[j]);//输入记录的其他非关键字的值

tt+=array.r[i].otherinfo[j];

}

array.r[i].key=tt;//计算关键字的值

}//输出未排序的顺序表

printf("\nbeforesorting:

\n");

printf("nametotalinf1inf2inf3inf4\n");

for(i=1;i<=array.length;++i)

{

printf("%-8s%-3d",array.r[i].name,array.r[i].key);

for(j=0;j<4;j++)

printf("%4d",array.r[i].otherinfo[j]);

printf("\n");

}

}

voidoutputSqlist(SqList&array)           //输出排序后的顺序表

{

inti,j;

printf("\nAftersorting:

\n");

printf("nametotalinf1inf2inf3inf4\n");

for(i=1;i<=array.length;++i)

{

printf("%-8s%-3d",array.r[i].name,array.r[i].key);

for(j=0;j<4;j++)

printf("%4d",array.r[i].otherinfo[j]);

printf("\n");

}

}

//头文件px1.h结束

//直接插入排序主程序开始

#include

voidInsertSort(SqList&L)

{//对顺序表L作直接插入的函数

inti,j;

for(i=2;i<=L.length;++i)

if(L.r[i].key

L.r[0]=L.r[i];//复制为哨兵

L.r[i]=L.r[i-1];

for(j=i-2;L.r[0].key

L.r[j+1]=L.r[j];//记录后移

L.r[j+1]=L.r[0];//插入到正确位置

}

}

voidmain()

{

Sqlistarray;

createSqlist(array);

InsertSort(array);//调用直接插入排序函数

outputSqlist(array)          

}

//直接插入排序主程序结束

5.思考题

采用直接插入排序法,实现将一个无序的单链表排列成一个降序的有序单链表。

实践二折半插入排序的实现

1.实验目的

通过本实验,掌握折半插入排序算法的思想方法和实现该算法的基本技术。

2.实验内容

从键盘输入记录数,依次输入各记录元素数值,以这些数值的和为关键字进行排序。

最后在屏幕上输出排序后的记录。

3.实验要点及说明

在实验中先定义记录数据类型,再定义一个采用折半插入排序算法的排序函数,其形参为待排序的记录的地址,主函数中输入记录数和各记录的非关键字数据,并求出关键字的值,输入结束后,先显示排序前的记录,然后调用折半插入排序函数对记录排序,最后显示排序后的记录。

4.参考程序

#include"px1.h"//参见:

实践一直接插入排序的实现

voidBinsertSort(SqList&L)//对顺序表L作折半插入的函数

{

inti,j,low,high,mid;

for(i=2;i

{

L.r[0]=L.r[i];

low=1;

high=i-1;

while(low<=high)

{

mid=(low+high)/2;

if(L.r[0].key

high=mid-1;

else

low=mid+1;

}

for(j=i-1;j>=high+1;--j)

L.r[j+1]=L.r[j];

L.r[high+1]=L.r[0];

}

}

voidmain()

{

Sqlistarray;

createSqlist(array);

BinsertSort(array);//调用折半插入排序函数

outputSqlist(array)          

}

5.思考题

折半插入排序较直接插入排序有何优点?

是否减少了时间复杂度?

实践三表插入排序的实现

1.实验目的

通过本实验,掌握表插入排序算法的思想方法和实现该算法的基本技术。

2.实验内容

从键盘输入记录数,依次输入各记录元素数值,以这些数值的和为关键字进行排序。

最后在屏幕上输出排序后的记录。

3.实验要点及说明

思想方法:

为插入方便,设数组下标为0的分量为表头结点,令表头结点记录的关键字取最大值MAXINT。

则表插入过程为:

先将静态链表中数组下标为“1”的分量和表头结点构成一个循环链表,然后依次将下标为2到n的分量按记录关键字非递减有序地插入到循环表中。

4.参考程序

//头文件px2.h静态链表类型说明

#defineSIZE100                      //静态链表容量

typedefintKeyType;                  //定义关键字类型为整型

structRedType                        //记录类型

{

KeyTypekey;                        //关键字项

InfoTypeotherinfo;           //其它数据项,具体类型在主程中定义

};

structSLNode                           //表结点类型

{

RedTyperc;                            //记录项

intnext;                             //指针项

};

structSLinkListType                      //静态链表类型

{

SLNoder[SIZE];                     //0号单元为表头结点

intlength;                          //链表当前长度

};

//头文件px2.h结束

//主程序表插入排序开始

#include

#include

#include

typedefintInfoType;//定义其它数据项的类型

voidTableInsert(SLinkListType&SL,RedTypeD[],intn)

{//由数组D建立n个元素的表插入排序的静态链表SL

inti,p,q;

SL.r[0].rc.key=INT_MAX;//表头结点记录的关键字取最大整数(非降序链表的表尾)

SL.r[0].next=0;//next域为0表示表尾(现为空表,初始化)

for(i=0;i

{

SL.r[i+1].rc=D[i];//将数组D的值赋给静态链表SL

q=0;

p=SL.r[0].next;

while(SL.r[p].rc.key<=SL.r[i+1].rc.key)//静态链表向后移

{

q=p;

p=SL.r[p].next;

}

SL.r[i+1].next=p;//将当前记录插入静态链表

SL.r[q].next=i+1;

}

SL.length=n;

}

voidArrange(SLinkListType&SL)

{//根据静态链表SL中各结点的指针值调整记录位置,

//使得SL中记录按关键字非递减有序顺序排列

inti,p,q;

SLNodet;

p=SL.r[0].next;//p指示第一个记录的当前位置

for(i=1;i

{//SL.r[1..i-1]中记录已按关键字有序排列,第i个记录在SL中的当前位置应不小于i

while(p

p=SL.r[p].next;//找到第i个记录,并用p指示其在SL中当前位置

q=SL.r[p].next;//q指示尚未调整的表尾

if(p!

=i)

{

t=SL.r[p];//交换记录,使第i个记录到位

SL.r[p]=SL.r[i];

SL.r[i]=t;

SL.r[i].next=p;//指向被移走的记录,使得以后可由while循环找回

}

p=q;//p指示尚未调整的表尾,为找第i+1个记录作准备

}

}

voidSort(SLinkListTypeL,intadr[])

{//求得adr[1..L.length],adr[i]为静态链表L的第i个最小记录的序号

inti=1,p=L.r[0].next;

while(p)

{adr[i++]=p;

p=L.r[p].next;

}

}

voidRearrange(SLinkListType&L,intadr[])

{//adr给出静态链表L的有序次序,即L.r[adr[i]]是第i小的记录。

//本算法按adr重排L.r,使其有序。

inti,j,k;

for(i=1;i

if(adr[i]!

=i)

{

j=i;

L.r[0]=L.r[i];//暂存记录L.r[i]

while(adr[j]!

=i)

{//调整L.r[adr[j]]的记录到位直到adr[j]=i为止

k=adr[j];

L.r[j]=L.r[k];

adr[j]=j;

j=k;//记录按序到位

}

L.r[j]=L.r[0];

adr[j]=

展开阅读全文
相关资源
猜你喜欢
相关搜索

当前位置:首页 > 高等教育 > 医学

copyright@ 2008-2022 冰豆网网站版权所有

经营许可证编号:鄂ICP备2022015515号-1