From f1a5a986425948aeee9b53fb6ffc6e18605d191a Mon Sep 17 00:00:00 2001 From: Didnelpsun <2675350965@qq.com> Date: Mon, 4 Oct 2021 23:34:21 +0800 Subject: [PATCH] =?UTF-8?q?=E6=9B=B4=E6=96=B0=E6=8E=92=E5=BA=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Data-Structrue/10-sort-ex.md | 33 ++++++++++++++++++- Data-Structrue/10-sort.md | 64 +++++++++++++++++++++++++----------- 2 files changed, 76 insertions(+), 21 deletions(-) diff --git a/Data-Structrue/10-sort-ex.md b/Data-Structrue/10-sort-ex.md index 456cb10..be45b03 100644 --- a/Data-Structrue/10-sort-ex.md +++ b/Data-Structrue/10-sort-ex.md @@ -86,4 +86,35 @@ $D.$冒泡排序 解:$D$。选择排序和归并排序都与序列初始状态无关。 - \ No newline at end of file + + +## 外部排序 + +### 败者树 + +**例题** 设有$5$个初始归并段,每个归并段有$20$个记录,采用$5$路平衡归并排序,若不采用败者树,使用传统的顺序选出最小记录(简单选择排序)的方法,总的比较次数是(①),若采用败者树最小的方法,总的比较次数是(②)。 + +$A.20$ + +$B.300$ + +$C.396$ + +$D.500$ + +解:$C,B$。不采用败者树时,在$5$个记录中选出最小的需要做$4$次比较,共有$5\times20=100$个记录,需要估$99$次选择最小记录的操作,所以需要的比较次数为$4\times99=396$,故选$C$。 + +采用败者树时,$5$路归并意味着败者树的外结点有$5$个,败者树的高$h=\lceil\log_25\rceil=3$。每次在参加比较的记录中选择一个关键字最小的记录,比较次数不超过$h$,共$100$个记录需要的比较次数不超过$100\times3=300$,故选$B$。 + +**例题** 在做$m$路平衡归并排序的过程中,为实现输入/内部归并/输出的并行处理,需要设置(①)个输入缓冲区和(②)个输出缓冲区。 + +$A.2$ + +$B.m$ + +$C.2m-1$ + +$D.2m$ + +解:$D,A$。增加一个输出缓冲区,当一个输出缓冲区满时,通知通道进行输出,同时归并程序向第二个输出缓冲区填充数据,这就实现了内部归并和输出的并行。增加$m$个输入缓冲区,当$m$个缓冲区正在运行时,外部可以向正在工作的$m$个缓冲区对应的第二个缓冲区(也就是增加的$m$个缓冲区)写入数据,这就实现了输入和内部归并的并行。综上两点设置$2$个输出$2m$个输入缓冲区即可实现输入/内部归并/输出的并行处理。 + diff --git a/Data-Structrue/10-sort.md b/Data-Structrue/10-sort.md index 8ce099f..52be94f 100644 --- a/Data-Structrue/10-sort.md +++ b/Data-Structrue/10-sort.md @@ -359,12 +359,12 @@ $n$个元素二路归并排序,归并一共要$\log_2n$趟,每次归并时 外部排序就是针对数据元素太多,无法一次性全部读入内存进行排序而进行处理的在外部磁盘进行的排序处理方式。 -使用归并排序的方式,最少只用在内存分配三块大小的缓冲区即可堆任意一个大文件进行排序。然后对缓冲区里的数据进行内部排序。 +使用归并排序的方式,最少只用在内存分配三块大小的缓冲区(两个输入缓冲一个输出缓冲)即可堆任意一个大文件进行排序。然后对缓冲区里的数据进行内部排序。 外部排序的过程: -1. 生成初始归并段,需要读写并进行内部排序 -2. 重复读写,进行内部归并。 +1. 生成初始归并段(大小为输入缓冲区的总大小),需要读写并进行内部排序。 +2. 重复读写,进行内部归并排序。填满输出缓冲就可以输出。输入缓冲空就可以输入新数据。 外部排序时间开销=读写外存时间(最大的时间开销)+内部排序所需时间+内部归并所需时间。 @@ -372,44 +372,68 @@ $n$个元素二路归并排序,归并一共要$\log_2n$趟,每次归并时 优化方法就是使用更多路的多路归并,减少归并趟数。 -$k$路平衡归并:最多只能有$k$个段归并为一个;每一趟归并中,若有$m$个归并段参与归并,则经过这一趟处理得到$\lceil\dfrac{m}{k}\rceil$个新的归并段。 +$k$路平衡归并:最多只能有$k$个段归并为一个,需要一个输出缓冲区和$k$个输入缓冲区;每一趟归并中,若有$m$个归并段参与归并,则经过这一趟处理得到$\lceil\dfrac{m}{k}\rceil$个新的归并段。 -对$r$个初始归并段,使用$k$路归并,则归并树可以使用$k$叉树表示,若树高为$h$,则归并趟数为$h-1=\lceil\log_kr\rceil$。 +对$r$个初始归并段,使用$k$路归并,则归并树可以使用$k$叉树表示,若树高为$h$,则归并趟数最小为$h=\lceil\log_kr\rceil+1$。 但是多路归并会带来负面影响: 1. $k$路归并时,需要开辟$k$个输入缓冲区,内存开销增加。 2. 每挑选一个关键字需要对比关键字$(k-1)$次,内部归并时间增加。 -同时,若能增加初始归并段的长度,也可以减少初始归并段数量$r$从而进行优化。 +同时,若能增加初始归并段的长度$k$,也可以减少初始归并段数量$r$从而进行优化。 ### 败者树 用于通过过去归并的经历减少归并次数。败者树可以看作一棵多了一个单个的根的完全二叉树。$k$个叶结点分别是当前参加比较的元素,非叶子结点用来记忆左右子树中的失败者,而让胜者往上继续比较,一直到根结点。 -传统方法从$k$个归并段选出一个最大或最小元素需要对比关键字$k-1$次,而使用$k$路归并的败者树只需要对比关键字$\lceil\log_2k\rceil$次。 +如要用败者树排序$27,12,1,17,2,9,11,4$,格式:元素值$($归并段索引值$)$: + +```terminal + 1(3) + | + 2(5) + | + ---------------------- + | | + 12(2) 4(8) + | | + ------------ ------------ + | | | | + 27(1) 17(4) 9(6) 11(7) + | | | | + ------ ------ ------ ------ + | | | | | | | | +27 12 1 17 2 9 11 4 +``` + +传统方法从$k$个归并段选出一个最大或最小元素需要对比关键字$k-1$次,而使用$k$路归并的败者树只需要对比关键字$\lceil\log_2k\rceil$次(败者树层数)。 ### 置换选择排序 用于构建更长的初始归并段,从而减少归并次数。 -假设初始始待排文件为FI,初始归并段输出文件为FO,内存工作区为WA,FO和WA的初始状态为空,WA可容纳$w$个记录。置换选择算法的步骤如下: +假设初始始待排文件为$FI$,初始归并段输出文件为$FO$,内存工作区为$WA$,$FO$和$WA$的初始状态为空,$WA$可容纳$w$个记录。置换选择算法的步骤如下: -1. 从FI输入$w$个记录到工作区WA。. -2. 从WA中选出其中关键字取最小值的记录,记为MINIMAX记录。 -3. 将MINIMAX记录输出到FO中去。 -4. 若FI不空,则从FI输入下一个记录到WA中。 -5. 从WA中所有关键字比MINIMAX记录的关键字大的记录中选出最小关键字记录,作为新的MINIMAX记录。 -6. 重复步骤三到五,直至在WA中选不出新的MINIMAX记录为止,由此得到一个初始归并段,输出一个归并段的结束标志到FO中去。准备输出新的归并段。 -7. 重复步骤二到六,直至WA为空。由此得到全部初始归并段。 +1. 从$FI$输入$w$个记录到工作区$WA$。 +2. 从$WA$中选出其中关键字取最小值的记录,记为$MINIMAX$记录。 +3. 将$MINIMAX$记录输出到$FO$中去。 +4. 若$FI$不空,则从$FI$输入下一个记录到$WA$中。 +5. 从$WA$中所有关键字比$MINIMAX$记录的关键字大的记录中选出最小关键字记录,作为新的$MINIMAX$记录。 +6. 重复步骤三到五,直至在$WA$中选不出新的$MINIMAX$记录为止,由此得到一个初始归并段,输出一个归并段的结束标志到$FO$中去。准备输出新的归并段。 +7. 重复步骤二到六,直至$WA$为空。由此得到全部初始归并段。 -此时输出的初始归并段可以超过WA,且初始归并段长度是不一定相等的。 +此时输出的初始归并段可以超过$WA$,且初始归并段长度是不一定相等的。 + +如$FI$:$4,6,9,7,13,11,14,22,30,2,3,19,20,17,23,5,36,12,18,21,39$,$WA$长度为$3$,$FO$为$4,6,7,9,11,13,14,16,22,30$、$2,3,10,17,19,20,23,36$、$1,5,12,18,21,39$。 ### 最佳归并树 +因为每个归并段的长度不同,所以归并的次序比较重要。 + #### 最佳归并树的衡量 -每个初始归并段可以看作一个叶子结点,归并树的长度作为结点权值,则归并树的带权路径长度WPL等于读写磁盘的次数。从而归并过程中的磁盘IO次数=归并树的WPL×2。 +每个初始归并段可以看作一个叶子结点,归并树的长度作为结点权值,则归并树的带权路径长度$WPL$等于读写磁盘的次数。从而归并过程中的磁盘$I/O$次数=归并树的$WPL\times2$。 #### 最佳归并树的构造 @@ -417,12 +441,12 @@ $k$路平衡归并:最多只能有$k$个段归并为一个;每一趟归并 #### 添加虚段 -对于$k$叉归并来说,若初始归并段的数量无法构成严格的$k$叉归并树,则需要补充几个长度为$0$的虚拟段,再进行$k$叉哈夫曼树的构造。 +对于$k$叉归并来说,若初始归并段的数量无法构成严格的$k$叉归并树,则需要补充几个长度为$0$的虚拟段从而能保证严格$k$叉归并,再进行$k$叉哈夫曼树的构造。 那么添加多少虚段呢? -$k$叉的最佳归并树一定是一棵严格的$k$叉树,即树种只包含度为$k$和0的结点。 +$k$叉的最佳归并树一定是一棵严格的$k$叉树,即树中只包含度为$k$和$0$的结点。 -设度为$k$的结点有$n_k$个,度为0的结点有$n_0$个,归并树的总结点树为$n$,则初始归并段数量+虚段数量=$n_0$。 +设度为$k$的结点有$n_k$个,度为$0$的结点有$n_0$个,归并树的总结点树为$n$,则初始归并段数量+虚段数量=$n_0$。 所以$n=n_0+n_k$,$kn_k=n-1$,所以$n_0=(k-1)n_k+1$,所以$n_k=\dfrac{(n_0-1)}{(k-1)}$一定是可以整除的。如果不整除就要添加虚段。