第8章 排序.docx
- 文档编号:29525238
- 上传时间:2023-07-24
- 格式:DOCX
- 页数:47
- 大小:163.86KB
第8章 排序.docx
《第8章 排序.docx》由会员分享,可在线阅读,更多相关《第8章 排序.docx(47页珍藏版)》请在冰豆网上搜索。
第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]=
- 配套讲稿:
如PPT文件的首页显示word图标,表示该PPT已包含配套word讲稿。双击word图标可打开word文档。
- 特殊限制:
部分文档作品中含有的国旗、国徽等图片,仅作为作品整体效果示例展示,禁止商用。设计者仅对作品中独创性部分享有著作权。
- 关 键 词:
- 第8章 排序