第十章 内部排序.docx
- 文档编号:6668945
- 上传时间:2023-01-08
- 格式:DOCX
- 页数:26
- 大小:447.89KB
第十章 内部排序.docx
《第十章 内部排序.docx》由会员分享,可在线阅读,更多相关《第十章 内部排序.docx(26页珍藏版)》请在冰豆网上搜索。
第十章内部排序
第十章内部排序
10-1概述
排序(Sorting):
将数据元素的任意序列,重新排列成按关键字有序的序列的过程。
其形式化描述如下:
假设有n个记录,其序列为
,其相应的关键字序列为
,则排序是确定
的一种排列
,使得关键字序列满足以下非递减(或非递增)关系:
,即使序列变成
。
稳定排序与非稳定排序:
当排序之前,如果
,在排序前
,而排序后,
仍然在
前,则称这种排序为稳定排序。
否则,被称为非稳定排序。
内部排序与外部排序:
排序过程中只涉及到随机存储器的排序过程,被称为内部排序。
如果涉及外部存储器交换,则称为外部排序。
注:
外部排序通常用于大数据记录集合的排序。
内部排序的方法分类:
(1)按排序过程的基本思想和技巧分为:
插入排序、交换排序、选择排序、归并排序和基数排序等;
(2)按内部排序所需的工作量分为:
1)简单排序(其时间复杂度为
);2)先进排序法(其时间复杂度为
);3)基数排序(其时间复杂度为
)。
排序过程中的主要操作:
1)比较两个关键字的大小;
2)将记录从一个位置移动到另外的位置;
待排序记录的存储方式:
(1)存储于连续的存储单元上,记录之间的关系由其地址关系决定(顺序存储结构);
(2)记录存放在静态链表中,记录之间的关系由链表指针指示(链表排序);(3)记录存储在连续的地址单元中,另有向量指示记录之间的关系,这样可以避免移动记录,但增加存储空间为代价(地址排序)。
对待排序记录数据类型的基本声明约定:
#defineMAXSIZE20
typedefintKeyType;
typedefstruct{
KeyTypekey;
InfoTypeotherinfo;
}ReType;
typedefstruct{
RedTyper[MAXSIZE+1];//r[0]闲置或作为哨兵记录
intlength;//顺序表的表长度
}SqList;
10-2插入排序
10-2-1直接插入排序(StraightInsertionSort)
排序类型:
简单排序。
基本思想:
将一条新的记录插入到一个有序表中,使新表依然有序。
主要技巧:
(1)第i趟直接插入排序的操作方法:
在含有i-1个记录的有序序列r[1]~r[i-1]中插入记录r[i],使得序列r[1]~r[i]有序;
(2)在r[0]处设置监视哨。
算法的主要步骤:
(1)在有序子表中查找插入点位置;
(2)将插入点以后的记录后移一个记录位置;(3)插入新记录。
算法描述:
voidInsertSort(SqList&L){//对顺序表L作直接插入排序
for(i=2;i if(LT(L.r[i].key,L.r[i-1].key)){//第i个记录比前一个小时,需要插入 L.r[0]=L.r[i];//设置哨兵 L.r[i]=L.r[i-1];//将L.r[i-2]~L.r[j]后移一个记录 for(j=i-2;LT(L.r[0].key,L.r[j].key);--j)L.r[j+1]=L.r[j]; L.r[j+1]=L.r[0];//在插入位置插入记录L.r[0]即原来的L.r[i] }//if//注: for()循环退出时执行了--j,所以L.r[j+1]=L.r[0] }//for }//InsertSort 排序实例(略): 效率分析: (1)空间复杂度: 辅助空间为一个单元,即L.r[0]; (2)时间复杂度: 比较次数 ,移动次数 整个复杂度 。 10-2-2其它插入排序 从直接插入算法可知: 算法的主要操作为比较和移动。 因此,算法的改进可以从这两个方面进行。 1.折半查找 基本思想: 利用子表的有序性进行折半查找,以减少比较次数。 主要技巧: 设置了low和high两个位置指示器来表征有序表的范围。 算法描述: voidBInsertSort(SqList&L){//对顺序表L作折半插入排序 for(i=2;i L.r[0]=L.r[i];//暂存待插入元素 low=l;high=i-1; while(low<=high){ m=(low+high)/2; if(LT(L.r[0].key,L.r[m].key))high=m-1; elselow=m+1; }//while for(j=i-1;j>=high+1;--j)L.r[j+1]=L.r[j]; L.r[high+1]=L.r[0];//在插入位置插入记录L.r[0]即原来的L.r[i] }//for }//BInsertSort 排序实例(略): 效率分析: 增加了两个位置指针low和high,使得比较次数由 变成了 ,但因移动次数为 ,所以整个算法的时间复杂度为 。 2.2-路插入排序 基本思想: 在折半查找的基础上,为减少移动记录的次数,增加n个记录大小的辅助存储空间d。 d用于存放有序子表,并将L.r[1]存入d[1],然后,对余下的元素的插入排序时,分别插入到d[1]的前面或后面。 主要技巧: 1)设置辅助存储空间d存放有序子表;2)将d看作一个循环向量。 为此,设置两个指针first和final,分别指向排序过程中有序序列的第一个记录和最后一个记录在d中的位置。 (1)L.r[1]d[1]; (2)ifL.r[i].key (3)否则,L.r[i]插入到d[1]之后的有序表中。 算法描述(略): (作为课外作业) 排序实例: [初始关键字]: 49,38,65,97,76,13,27,49 假定对n个元素进行插入排序,则排序过程中向量d的状态如下: i=1时,final=first=1: (49) i=2时,L.r[2].key=38 (49)(38) final=1,first=n i=3时,L.r[3].key=65) (4965)(38) final=2,first=n i=4时,L.r[4].key=97 (496597)(38) final=3,first=n i=5时,L.r[5].key=76 (49657697)(38) final=4,first=n i=6时,L.r[6].key=13 (49657697)(1338) final=4,first=n-1 i=7时,L.r[7].key=27 (49657697)(132738) final=4,first=n-2 i=8时,L.r[8].key=49 (4949657697)(132738) final=4,first=n-3 效率分析: 其移动记录次数约为 。 注: 当L.r[1]是最大/最小记录时,2-路排序完全失去其优越性,为什么? 3.表插入排序 目标: 希望在排序过程中完全不移动记录。 基本思想: 修改数据存储结构成如下的静态链表结构: #defineSIZE100//静态链表容量 typedefstruct{ RedTyperc; intnext; }SLNode; typedefstruct{ SLNoder[SIZE]; intlength; }SLinkListType; 假设数组下标为0的分量表示表头结点,其关键字取最大整数MAXINT。 则表插入算法过程可描述如下: 1)将静态数组下标为0和1的分两看作一个循环链表; 2)依次将2到n的分量按关键字值插入到该循环链表中去。 排序实例: (如图10-1所示) 效率分析: 表排序不移动记录,而改为修改指针(2n次),比较次数不变,因此,实践复杂度仍然为。 进一步改进: 由于是以链表形式存放的,以上算法不能进行折半查找,只能顺序查找。 重排算法的基本思想: 顺序扫描有序链表,将第i个链结点存入数组的第i个分量即可。 重排算法如下: (如教材P269-270) 10-2-3Shell排序 排序类型: 插入排序。 提出原因: 对直接排序,当待排序记录序列为正序时,其时间复杂度可提高到 。 因此,如果能使待排序序列基本有序,则可提高效率。 基本思想: 将整个待排序记录分割成若干个子序列,并分别对其进行直接插入排序,待整个序列中的记录“基本有序”时,在对全体进行一次直接插入排序。 如图10-2所示。 主要技巧: 子序列分割及其有序化;子序列分割大小的增量思想,直到分割大小为1以便完成最后的排序。 Shell排序算法描述: //对顺序表作一趟Shell插入排序,和简单直接插入排序的主要差别: //1)前后记录的增量是dk而不是1 //2)r[0]只是暂存单元,不是哨兵。 当j<=0时找到插入位置 voidShellInsert(SqList&L,intdk){ for(i=dk+1;i<=L.length;++i){ if(LT(L.r[i].key,L.r[i-dk].key)){//需要将L.r[i]插入到增量子表中时 L.r[0]=L.r[i];//暂存L.r[i] for(j=i-dk;j>0&<(L.r[0].key,L.r[j].key);j-=dk)L.r[j+dk]=L.r[j]; L.r[j+dk]=L.r[0]; }//if }//for }//ShellInsert //按增量序列dlta[0..t-1]对顺序表L作Shell排序 voidShellSort(SqList&L,intdlta[],intt){ for(k=0;k }//ShellSort 注: Shell排序中的增量问题还无确定的好方法。 目前为止的研究结果是: 1)当取 时,其时间复杂度为 ,其中,t为排序趟数, ;2)当N取某个特定范围内的值时,其比较和移动次数约为 ;3)当 时,可减少到 。 10-3快速排序 排序类型: 交换排序。 1.冒泡排序(BubbleSort) 基本思想: 对相邻元素进行两两比较,如果逆序,则交换其值,直到第n-1个记录和第n个记录处理完后,完成一趟“冒泡”过程。 经过最多不超过n-1趟,序列将变成正序序列。 算法描述: (略) 排序实例: (略) 效率分析: 如果为正序,则只进行一趟排序,如果为逆序则要进行n-1趟,其比较次数为 ,并作等数量级的移动,因此,其时间复杂度为 。 2.快速排序(QuickSort) 对冒泡法的一种改进。 基本思想: 通过一趟排序,将原序列分割成左右两个部分,其中左部分所有元素比右部分小(或大)。 继续直到整个表有序为止。 主要技巧: 1)支点(pivot)概念的引入; 一趟快速排序过程的算法描述: 以支点为中心,将所有关键字小于支点的记录置于支点之前,否则,置于支点之后。 即以支点L.r[i]为中心,将序列{L.r[s],…,L.r[t]}分割成两个子序列: {L.r[s],L.r[s+1],…,L.[i-1]}和{L.r[i+1],L.r[i+2],…,L.r[t]}。 算法实现描述: 算法10.6(a) //假设pivotkey为支点关键字,low和high分别记录向后或向前搜索大于pivotkey或小于pivotkey的记录,并进行交换的起始位置。 //结束排序的条件: low==high //交换顺序表L中L.r[low..high]的记录,使支点记录到达相应位置,并返回其所在 //的位置,此时,其前面的记录的关键字值均小于它,而后面的都大于它 intPartition(SqList&L,intlow,inthigh){ pivotkey=L.r[low].key;//将子表的第一个记录作为支点记录 while(low { while(low L.r[low]L.r[high];//交换,比支点小的记录到低端 while(low L.r[low]L.r[high];//交换,比支点大的记录到高端 } returnlow;//返回支点位置(low==high) }//Partition 改进算法(算法10.6(b)) //改进: 因为每次交换时对支点的移动是多余的。 只需将支点暂存于L.r[0]中 intPartition(SqList&L,intlow,inthigh){ L.r[0]=L.r[low];//将子表的第一个记录作为支点记录 pivotkey=L.r[low].key;//支点关键字 while(low { while(low L.r[low]=L.r[high];//移动比支点小的记录到低端 while(low L.r[high]=[low];//移动比支点大的记录到高端 } L.r[low]=L.r[0];//移动支点记录到相应位置 returnlow;//返回支点位置(low==high) }//Partition 排序实例: (以改进算法为例) 快速排序的递归算法实现描述: //算法10.7 //对顺序表L中的子序列L.r[low]…L.r[high]作快速排序 voidQSort(SqList&L,intlow,inthigh){ if(low pivotkey=Partition(L,low,high);//将L.r[low]..L.r[high]一分为二 QSort(L,Low,pivotloc-1);//对低端子表根据pivotloc进行递归排序 QSort(L,pivodloc+1,high);//对高端子表根据pivotloc进行递归排序 } }//QSort 效率分析(详细分析见教材P276-277): 快速排序的平均时间为 ,其中,n为待排序记录个数,k为常数。 结论: 所有同数量级的此类排序方法中,快速排序常数因子最小。 快速排序是已知排序算法中最好的内部排序方法。 10-4选择排序 基本思想: 第i趟从n-i+1(i=1,2,…,n-1)个记录中选一最小或最大者,作为有序序列中的第i个记录。 10-4-1简单选择排序 第i趟排序的基本操作: 从n-i+1个记录中,通过n-i次比较,选出最小的关键字记录,并和第i个记录交换。 其中, 。 算法过程: 令i从1到n-1,进行n-1趟选择操作。 算法实现描述(算法10.9): voidSelectSort(SqList&L){//对顺序表L作简单选择排序 for(i=1;i j=SelectMinKey(L,i); if(i! =j)L.r[i]L.r[j]; } }//SelectSort 算法复杂度: 移动次数最小为0,最大为3(n-1)次。 比较次数为n(n-1)/2。 。 改进思路: 其主要操作是比较,因此,可以考虑如何减少比较次数。 尽量利用前面比较过程中得到的信息。 改进方法: 锦标赛方法。 从图10-4(a)----(c)知: 8个队员进行比赛,决定冠军需7场比赛;亚军只需两场比赛;季军也只需两场比赛。 共11场比赛,而不是像简单选择算法共需7+6+5=18场比赛。 结论: 如果充分利用前面比较得到的信息,则可以减少比较的总次数。 这种排序方法被称为树形排序。 10-4-2树形选择排序 树形选择排序(TreeSelectionSort),又称为锦标赛排序(TournamentSort): 是一种按锦标赛思想进行的选择排序方法。 基本思想: 1)对n个记录两两比较,然后再在其中 个较小者之间两两比较,重复直到选出最小者为止。 用n个叶子结点的完全二叉树表示。 如图10-5(a)所示。 2)每个非终端结点等于其叶子结点中的最小关键字。 则,根结点即最小关键字记录。 3)输出最小者后,将叶子中最小者改为最大,然后从它开始,和其另一兄弟比较,修改到根的路径上的各个节点的关键字,则根变成次小者。 4)重复3)直到所有叶子结点被输出为止。 效率分析: 含有n个叶子结点的完全二叉树的深度为 ,则在树形选择排序中,除最小关键字要进行n-1次比较外,每选择一个次小关键字仅需进行 次比较,因此其时间复杂度为 。 缺点: 输出一个关键字后,找下一个次小关键字时,和最大关键字 的比较是不必要的。 空间复杂度: 需要n个辅助空间存放排序结果。 由此,J.Willioms提出了堆排序方法,即另一种树形的选择排序。 10-4-3堆排序 堆排序(HeapSort)的目标: 只需要一个记录大小的辅助存储空间,且每个待排序的记录也只占一个存储空间。 堆的定义: 设有n个元素的序列 ,当且仅当满足如下关系时,称其为堆。 或者 ,其中, 堆的存储结构: 以一维数组表示。 堆的性质: 1)第一个元素为根结点;2)任何一个非终端结点的后继结点的值将不大于其父结点的值或不小于其父结点的值;3)堆顶元素必然是序列中的最大或最小元素;4)一个堆对应一个完全二叉树结构。 例如: 图10-6(a)和10-6(b)所示分别为6个元素和8个元素的序列的堆结构。 其对应的一维数组的值分别为: {96,83,27,38,11,08}和{12,36,24,84,47,30,53,91}。 堆排序: 建立堆,输出堆,然后重建堆,直到得到原序列的有序序列为止的排序过程。 堆的筛选: 当堆顶元素被输出之后,用堆的最后一个元素作为新的堆顶元素,然后从堆顶至叶子的调整(成新堆)的过程。 方法: 将堆看作一个完全二叉树,则最后一个非终端结点是 ,因此,筛选从此处开始。 例如,对无序列{49,38,65,97,76,13,27,49},应从第4个元素97开始,直到第一个 元素为止,对每个元素检查其跟左、右子树之间的关系,如果满足堆的条件(即比左右子树结点的值小),则OK,否则,调整,使其满足。 (如图10-7所示) 输出堆顶元素后,调整堆的过程跟图10-7相似,其主要差别在于n个顶点变成n-1个顶点。 其堆的重建过程示意图如图10-8所示。 堆调整算法描述: typedefSqListHeapType;//用顺序结构表示 //假定H.r[s..m]中除H.r[s]外,所有结点的关键字值之间均满足堆的定义 //本函数对H.r[s]进行调整以重建堆,建堆的目标是大顶堆 //调整的元素范围为s..m voidHeapAdjust(HeapType&H,ints,intm){ rc=H.r[s]; for(j=2*s;j<=m;j*=2){//沿key较大的孩子结点向下筛选 if(j if(! LT(rc.key,H.r[j].key))break;//如果rc.key比H.r[j].key大,则已经到位 H.r[s]=H.r[j];s=j;//否则,H.r[j]上移到H.r[s]处,并记住s的最新位置s } H.r[s]=rc;//原来的H.r[s]放到其应当在的位置 }//HeapAdjust 堆排序算法实现过程描述: voidHeapSort(HeapType&H){//对顺序表H进行堆排序 for(i=H.length/2;i>0;i--)//将H.r[1..H.length]建成一个大顶堆 HeapAdjust(H,I,H.length); for(i=H.length;i>1;i--){ H.r[1]H.r[i];//将堆顶记录和当前子序列的最后一条记录H.r[i]交换 HeapAdjust(H,1,i-1); } }//HeapSort 时间效率分析: 堆排序的主要时间耗费在建立初始堆及筛选过程上,假设深度为k,则筛选算法的关键字比较次数<=2(k-1)次,建立n个元素、深度为h的堆共需进行比较<=4n次。 N个结点的完全二叉树深度为 ,调整堆的次数为n-1次,因此,总的比较次数不超过: 堆排序在最坏情况下,时间复杂度为 。 空间效率上: 堆排序只需要一个额外的辅助存储空间。 10-5归并排序 归并排序(MergingSort): 将两个或两个以上的有序表合并成一个新表的排序方法。 存储结构: 顺序结构或链表结构。 2-路归并的基本思想: 假设初始序列含有n个记录,每个记录被看作一个含有一个元素的有序子序列,子表长度为1。 则可两两归并,获得 个长度为2或1的有序子表。 如此重复,即可得到一个长度为n的有序序列。 实例: 如图10-8所示。 其核心思想是将相邻两个有序序列两两归并成一个新的序列。 算法实现描述: //将有序序列SR[i..m]和SR[m+1..n]归并为有序序列TR[i..n] voidMerge(RcdTypeSR[],RcdType&TR[],inti,inm,intn){ for(j=m+1,k=i;i<=m&&j<=n;++k){ if(LQ(SR[i].key,SR[j].key))TR[k]=SR[i++]; elseTR[k]=SR[j++]; } if(i<=m)TR[k..n]=SR[i..m]; if(j<=n)TR[k..n]=SR[j..n]; }//Merge 时间效率分析: 需要调用 次Merge算法,其中,h为初始子表的长度,n为待排序记录个数。 整个归并次数为 趟。 因此,其时间复杂度为 。 空间效率分析: 需要和待排序记录个数相同大小的辅助存储空间。 递归形式的2-路归并算法: //将SR[s..t]归并为TR1[s..t] voidMSort(RcdTypeSR[],RcdType&TR1[],ints,intt){ if(s==t)TR1[s]=SR[s]; else{ m=(s+t)/2;//将SR[s..t]平分为SR[s..m]和SR[m+1..t] MSort(SR,TR2,s,m);//递归地将SR[s..m]归并
- 配套讲稿:
如PPT文件的首页显示word图标,表示该PPT已包含配套word讲稿。双击word图标可打开word文档。
- 特殊限制:
部分文档作品中含有的国旗、国徽等图片,仅作为作品整体效果示例展示,禁止商用。设计者仅对作品中独创性部分享有著作权。
- 关 键 词:
- 第十章 内部排序 第十 内部 排序
![提示](https://static.bdocx.com/images/bang_tan.gif)