败者树与外部多路归并排序

败者树与外部多路归并排序

在处理大数据量的排序时,由于数据无法全部加载到内存,内部排序无法对整个数据集进行排序,需要到外部排序。

外部排序有一些特点,跟内存容量和读写文件有关:

1. 读写文件,需要考虑 IO 时间
2. 从无序到逐步有序的过程中,需要多个中间文件

外部排序有多种,常见的归并排序的如下:

输入为大文件 F ,排序过程分为分割和归并

分割:
1. 从 F 中读入内存能容纳的 k 个数
2. 对 k 个数排序
3. 排序结果输出的到文件 Gi
4. 重复 1-3 直到 F 结束
分割后得到 ceil(F/k) 个已排序的小文件,合并过程将多个小文件合并

归并:
1. 打开 m 个小文件(或后续中间结果文件)
2. 从 m 小文件中读当前数字,选最小的数输出到中间结果文件 Ri (假设为按从小到大排序)
3. 重复 2 直到 m 个小文件结束
4. 重复 1-3 直到所有文件(包间中间文件)已合并到最终文件

归并的过程中,以什么样的算法选数呢?

假如 m=2 (二路外部归并排序) ,那么没有什么好说的,直接比较两个数的大小就行了。但从整体上来说,由于每次只操作两个小文件,需要的中间结果文件数量多,IO 占用时间长。

如果提升 m ,进行多路排序,那么可减少中间文件的数量,但是对数的比较运算就复杂,每次取走一个数后,都要对 m 个文件流第一个数进行一次排序。

败者树是处理 m 路归并时,优化排序的数据结构,它是对排序树的一种变型,可减少因取走数调整的工作量。

败者树的基本思想是:左右孩子结点进行比较时,败者保存到父节点,而胜者则继续向上比较;根节点保存冠军。

                     L[0]:冠军b2
                       |
                    L[1]:败者b1
                  /            \
         L[2]:败者b0           L[3]:败者b3
         /     \               /      \
 L[4]:b[0]8  L[5]:b[1]7 L[6]:b[2]5  L[7]:b[3]9

对于 m 路归并排序,需要使用 m 个元素记录胜败情况,m 个叶子保存 m 个文件流第一个数。败者树的节点可用 union 表示为:

typedef union _Node {
    int pos;        // 胜者或败者位置
    InfoType *info;  // 文件流和其第一个数
}

InfoType 中需要记录文件流和对应的第一个数,需要支持的操作:

top() 取当前文件偏移的数,偏移位置不变
de()  取当前文件偏移的数,偏移位置 + 1

它使用数组存储,L[0]为冠军,L[m]...L[2m-1] 为叶子节点,每个节点 i 的分节点为 i/2 (有些写法会把败者树数组 L 和数据数组 B 分开,也是可以的) 。每次取走冠军L[0],需要从叶子结点向上更新。

调整的算法为:

// 调整败者树 l ,冠军为位置 s ,取出冠军后调整
adjust(LoserTree l, int s):
    p = s / 2   // 从叶子节点 l[s] 向根节点 l[0] 调整
    while p>0:
        //  l[s] 失败,调整父节点l[p],l[p]向上比较
        if l[s].info->top() > l[l[p]].info->top():
            tmp = l[p]  // 暂存 l[p]
            l[p] = s    // 父节点为败者
            s = tmp     // 存入 s 继续向上
        p = p / 2   // 继续向上调整
    l[0] = s        // 冠军

那么败者树的构建过程为:

create_lt(LoserTree l, int m, int data[]):
    for i in rang(m):
        l[i] = m+i;     // 胜者败者初始化
    for i in range(m, 2m-1):
        l[i] = InfoType(data[i-m])  // 数据初始化
    for i in range(2m-1, m, step=-1):
        adjust(l, i);   // 初始化调整,从 data[m-1]...data[0]

合并的算法为:

m_merge(LoserTree l, int m, int data[]):
    create_lt(l, m, data);
    // 一直取冠军直到文件结束
    while l[l[0]]->top() != EOF:
        output(l[l[0]]->de());  // 输出冠军,同时该文件流到下一位置
        adjust(l, l[0])

败者树将调整的过程的比较数量减少到 log(n) ,有效地加速外部排序的过程。

除了在外部归并排序中使用,还可以在置换选择排序中使用败者树,以在内存中获取当前小于已输出的最大的数的最小数,不过比较的时有双关键字:当前已输出的最大数、和同时比较数字。

转载于:https://www.cnblogs.com/fengyc/p/6805005.html

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值