今天看了一些有关外排序的内容,把一些自己觉得重要的东西写在这里和大家分享,如有不当之处还请不吝指教。
1、 把外存中的数据划分为若干段读入内存再进行排序并输出
2、 外排序的时间构成:产生初始顺串的内排序时间 + 归并顺串的时间 + 初始化顺串和归并顺串的I/O时间
3、 置换选择排序:建立一个最小堆,大小为M,从外存里不断读入数据并输出数据。每次先输出堆顶的元素r,再读入外存的数据s,如果s >= r,就把堆顶的元素设置成s并且调整堆结构;否则就把堆的末尾元素放到堆顶,s放到堆的末尾,把堆的大小减少1并且调整堆结构。这样一来,对于任意一个外存文件,至少输出一个长度为M的序列。平均生成的序列长度为2M
4、 归并的消耗:k路归并,m个串,高度为,所以可以增加k,减少m(增加串的长度),这样子就可以减少外存的I/O时间;同时注意到归并的顺序对消耗也有影响,本质是Huffman树的优化,注意k路归并的方法。
5、 多路归并树分析:赢者树+败者树,目的是提高在k个顺串中找出最小的那个“头元素”的效率。叶节点用L[1,…,n]表示,内部结点用B[1,…,n-1]表示。数组B中存放的实际是L的索引。就内部结点来看,最底层最右边的索引编号是,其中s是深度;所以最底层的内部结点一共有n – 1 – (2^s – 1) = n – 2^s个。最底层的外部节点数是2 * (n – 2 ^ s)个
6、 L[i]和B[p]之间的关系(p是i的父节点),记LowExt为最底层的外部节点数目;
若i <= LowExt, p = (i + offset) / 2,其中offset是最底层外部节点上面的所有节点数目
若I > LowExt, p = (i – LowExt + n – 1) / 2
7、 赢者树的重构需要通过沿着到根节点的路径向上,当前结点和兄弟结点比较,把优胜者放到父节点,再把当前节点设置为父节点;败者树的重构不需要和兄弟结点比较,只要一直和父节点比较,把败者放到父节点,胜者放到B[0],再不断向上
为了更加清楚的理解这件事情,我们贴出败者树的重构代码:
#L和B依上定义
def winner(L, b, c):
if L[b] < L[c]:
return b
return c
def loser(L, b, c):
if L[b] < L[c]:
return c
return b
def Replay(i): #更新i处的外部结点值并进行重构
if i <= LowExt:
p = (i + offset) // 2
else:
p = (i - LowExt + n - 1) // 2
B[0] = winner(L, i, B[p])
B[p] = loser(L, i, B[p])
while p // 2 >= 1: #沿路径向上比赛
temp = winner(L, B[p // 2], B[0])
B[p // 2] = loser(L, B[p // 2], B[0]
B[0] = temp
p //= 2
8、 原始方法找k个顺串的最小值的复杂度是O(k),生成长度为n的序列的总复杂度为O(kn);如今初始化一个败者树或者赢者树的复杂度是O(k),每次更新一个外部结点的复杂度为O(logk),生成一个长度为n的序列的总复杂度为O(k + nlogk)
9、外排序中不一定要用败方树/赢者树,用堆也是可以实现的,留给大家思考