外部排序专题

1.外部排序基本概念:前面介绍的排序都是内部排序,是直接在内存里面进行排序的,但是大多数情况下文件比较大,这时候我们就得把文件分割成一个个小块进行输入内存再排序。在排序过程中需要进行多次的内存与外存之间的交互,所以称之为外部排序。

外部排序操作:以例题作为讲解

例题1:假设文件有4500个记录,每块磁盘可以放75个记录,计算机中用于排序的内存区可以存放 450 个记录,试问:
1)可以建立多少个初始归并段?每个归并段有多少个记录?存放于多少个块中?
2)应采用几路归并,请写出每趟需要读写磁盘的块数

解答:
1) 文件中有4500个记录,内部排序区可容纳450个记录(其实排序过程是 依次 输入1-450,451-900… 进行排序,然后输出构成有序的初始归并段 ),则可建立的初始归并段为4500/450 = 10,每个初始归并段有450个记录,存放于 450/75 =6 个块中。
2)内存区可以容纳6个块,所以可以建立5个输入缓冲区。1个输出缓冲区,因此采用5路归并。
归并次数为 log(5)10 = 2,每次归并需要读写磁盘次数都为4500/75=60 次 ,每次归并需要读写磁盘总次数为为60 * 2 =120 次,做了两趟归并,读写总次数 2 * 120=240。
另外再来看看
2.外部排序总时间= 内部排序所需时间+外村信息读写时间+内部归并需要时间

内部排序所需时间 :就是第一次进行生成初始归并序列的时间,这其中就有一次读写时间,排序时间可以忽略。如上题内部排序所需时间为 120 相当于一次归并的时间
外存信息读写时间 :其实就是归并次数乘以每次读写时间,上题为 两次归并,每次归并读写磁盘次数为120 ,所以总时间为2 * 120 = 240次。
内部归并需要时间:就是在内存中进行序列段合并为一个序列段所需时间,这一时间其实主要在于数据进行比较的时间,例如上题,进行的是5路归并,那么在5个元素中选取最小数比较次数是4次,总共有4500个记录,最后一个不需要比较,因此每趟归并需要的比较次数为 (4500-1)*4=17996次,进行两次归并,17996 *2=35992次比较。但是比较时间相比于读写时间比较短所以可以忽略。

归并次数 S=log(k) r 。r为初始归并段,k为归并路数,证明很简单,略。若是不采用其他方法进行比较次数优化则S趟总共需要比较次数为 S( n-1 )( k-1) = [ log(k) r ] ( n-1 )( k-1)=[log2 r] (n-1)(k-1)/[log2k] ,式子中 (k-1)/log2k 随着路数k的增大而增大,这将抵消由于k增大而减少外存访问次数所得到的效益,那么能不能使用其他方法来减少比较次数呢?下面介绍败者树方法。
败者树:树中k个叶节点分别存放k个归并段在归并过程中当前参加比较的记录,内部节点用来记录左右子树的 “败者” 而胜利者继续向上比较直到到达根节点,如图所示。
在这里插入图片描述

因为 k 路归并的败者树深度为log2k , 因此k 个记录中选择最小关键字,最多需要 log2k次比较,所以排序总的比较次数为S(n-1)[log2k]=[log(k)r] (n-1) log2k=(n-1) log2r 。 可见使用败者树后内部归并的比较次数就与k无关了,因此只要内存空间允许,可以通过增大路数k达到减少 I/O 次数,提高外部排序速度。
值得说明的是,归并路数k并不是越大越好,归并路数k增大,相应的得增加输入缓冲区的块数,若是可使用的空间一定,势必要减少每个输入缓冲区的容量,这样也会使得内存、外存交换数据次数增加,因此仍然会导致时间增大。

还有什么办法可以减少排序时间呢?

3.置换选择排序(生成初始归并段)
只要增大初始归并段长度就可以减少初始归并段个数,达到减少归并时间的效果。

在这里插入图片描述
上述算法中WA选择最小数使用败者树实现。

综上所述:每一次归并所读取 I/O 的次数是一定的,总记录/每块磁盘容量,因此可以通过减少归并次数,达到减少读取次数减少排序时间问题,那么减少归并次数的方法有:增大归并路数 或者 增大初始序列 。
实现代码:


void Select_MiniMax(LoserTree ls,WorkArea wa,int q){
    int p, s, t;
// ls[t]为q的双亲节点,p作为中介
   
    for(t = (w+q)/2,p = ls[t]; t > 0;t = t/2,p = ls[t]){
        // 段号小者 或者 段号相等且关键字更小的为胜者
        if(wa[p].rnum < wa[q].rnum || (wa[p].rnum == wa[q].rnum && wa[p].key < wa[q].key)){
            s=q;
            q=ls[t]; //q指示新的胜利者
            ls[t]=s;
        }
    }
    ls[0] = q; // 最后的冠军
}
//输入w个记录到内存工作区wa,建得败者树ls,选出关键字最小的记录,并由s指示其在wa中的位置。
void Construct_Loser(LoserTree ls, WorkArea wa, FILE *fi){
    int i;
    for(i = 0; i < w; ++i){
        wa[i].rnum = wa[i].key = ls[i] = 0;
    }
    for(i = w - 1; i >= 0; --i){
        fread(&wa[i].rec, sizeof(RedType), 1, fi);// 输入一个记录
        wa[i].key = wa[i].rec.key; // 提取关键字
        wa[i].rnum = 1; // 其段号为"1"
        Select_MiniMax(ls,wa,i); // 调整败者树
    }
}

4.最佳归并树

文件经过置换选择排序后,得到的是长度不等的初始归并段,下面讨论如何组织长度不等的初始归并段的归并顺序使得IO次数最少。其实也就是m叉哈夫曼树类似,很简单,不展开叙述。其中有一点不同是,可能初始归并段个数并不能构成严格的k叉树,这时就要补充虚段了。

如何确定添加虚段的数目?
设度为0的节点有 N0 个,度为 k的节点个数有Nk个,则对于严格k叉树 N0=(k-1) Nk+1,由此可得Nk=(N0-1)/(k-1) 。

  1. 若是(N0-1)%(k-1) =0 , 则说明这N0个叶节点正好可以构成严格k叉树。
  2. 若是不等于0,则设需要添加 m个 长度为0 的虚段使得(N0+m-1)%(k-1)=0,即可。

以上就是外部排序的全部内容

  • 9
    点赞
  • 27
    收藏
    觉得还不错? 一键收藏
  • 7
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 7
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值