外部排序时间开销=读写外存的时间+内部排序所需时间+内部归并所需 时间
    归并趟数S=⌈logkr

    归并路数k增加,归并趟数S减少,读写磁盘总次数减少

    使用k路平衡归并策略,选出一个最小元素需要对比关键字(k-1)次, 导致内部归并所需时间增加,因此可以使用败者树进行优化,减小比较次数

    8路平衡归并,从八个归并段中选出一个最小元素需要对比关键字7次
    败者树算法 - 图1

    作用:在外部排序方法中,为了减少I/O次数,而需要将二路平衡归并改为多路平衡归并,但是按照原有的归并算法,将二路归并改为多路归并将增加其内部排序的时间。为了是内部排序不受到归并数目的影响,从而引入了败者树的概念。
    概念:败者树是对树形选择排序的一种变化,它是一颗完全二叉树。每个叶子节点存放各个归并段在当前位置需要参加归并的记录,其内部节点用来记录左右子数中的“失败者”,从而让胜利者继续比较,一直到跟节点。根据需求可以将左右子树中大的(小的)定义为失败者,小的(大的)为胜利者,则根节点指向的数为最小数(最大数)。

    下面以大的为失败者,小的为胜利者解释。
    败者树算法 - 图2

    如上图所示:有b0、b1、b2、b3、b4五个归并路数,它们的值分别是10、9、20、6、12.

    • b3和b4比较,b3为胜利者,于是将失败者b4的路号4存入b3、b4的父节点中;
    • 将胜利者b3继续与b0相比,b3对应的是6,b4对应的是10,于是b3为胜利者,b0为失败者,将失败者b0的路号0存入到上一层父节点中;
    • 再看右边b1和b2的比较,b1对应的是9,b2对应的是20,于是b1为胜利者,b2为失败者,将失败者b2的路号2存入到b1、b2的父节点中;
    • 然后将左边的胜者b3与右边的胜者b1比较,b3对应的是6,b1对应的是9,则b3为胜者,b1为败者,将失败者b1的路号1存入到上一层父节点中;
    • 最后在将胜利者的路号写入ls[0]中

    python 源码:

    1. #!/usr/bin/env python
    2. # -*- coding: utf-8 -*-
    3. """
    4. 参考资料:
    5. http://sshpark.com.cn/2018/11/22/%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84%EF%BC%9A%E8%B4%A5%E8%80%85%E6%A0%91/
    6. https://www.it610.com/article/1305779568971386880.htm
    7. """
    8. K = 4 # 4路归并
    9. buf = [] # 存放多路归并的头元素,多出来的一位放最小值
    10. ls = [] # ls[0]保存的是胜者的下标,其余保存败者下标号
    11. def adjust(index):
    12. """定义调整函数 s是buf数组的下标"""
    13. f_index = (index + K) // 2 # 取s位的父节点
    14. # 如果当前节点不是根节点则进入下次循环,否则跳出
    15. while f_index > 0:
    16. # 比较当前节点值与父节点对应的值,如果父节点胜出则记录失败者并将当前节点换成胜利者(当前规则:大者为败,小者为胜)
    17. if buf[index] > buf[ls[f_index]]:
    18. l = ls[f_index]
    19. ls[f_index] = index
    20. index = l
    21. f_index = f_index // 2
    22. # 记录最终胜利者到ls[0]
    23. ls[0] = index
    24. def build():
    25. """定义败者树构建函数"""
    26. # 添加最小值-1到buf尾部
    27. buf.append(-1)
    28. # 将败者树中间节点全部初始化为最小值所在位置即len(buf)-1
    29. for i in range(len(buf)):
    30. ls.append(len(buf) - 1)
    31. # 依次调整buf列表中所有位置
    32. for i in range(K):
    33. adjust(i)
    34. def get_min():
    35. return buf[ls[0]]
    36. if __name__ == '__main__':
    37. buf = [12, 7, 6, 8]
    38. build()
    39. print(get_min())
    40. buf[2] = 11
    41. adjust(2)
    42. print(get_min())
    43. pass

    参考资料: