【数据结构】外部排序总结 + 构建败者树 Python 版

来源:互联网 发布:淘宝如何添加宝贝规格 编辑:程序博客网 时间:2024/06/16 22:24

外部排序又称大数据文件排序。我看了教材之后,依然对外部排序有些模棱两可的误解,搜了一些资料才发现问题出在哪里。

一、多路平衡归并排序
外排序的一个例子是外归并排序(External merge sort),它读入一些能放在内存内的数据量,在内存中排序后输出为一个顺串(即是内部数据有序的临时文件),处理完所有的数据后再进行归并。比如,要对 900 MB 的数据进行排序,但机器上只有 100 MB 的可用内存时,外归并排序按如下方法操作:

1、读入 100 MB 的数据至内存中,用某种常规方式(如快速排序、堆排序、归并排序等方法)在内存中完成排序。

2、将排序完成的数据写入磁盘。

3、重复步骤 1 和 2 直到所有的数据都存入了不同的 100 MB 的块(临时文件)中。在这个例子中,有 900 MB 数据,单个临时文件大小为 100 MB,所以会产生 9 个临时文件。

4、读入每个临时文件(顺串)的前 10 MB ( = 100 MB / (9 块 + 1))的数据放入内存中的输入缓冲区,最后的 10 MB 作为输出缓冲区。(实践中,将输入缓冲适当调小,而适当增大输出缓冲区能获得更好的效果。)

5、执行九路归并算法,将结果输出到输出缓冲区。一旦输出缓冲区满,将缓冲区中的数据写出至目标文件,清空缓冲区。直至所有数据归并完成。

也就是说,在执行归并的过程中,并没有完全占用完内存空间,一定留有一些空间用来存储归并结果的,课本里并没有讲清楚这一点。当归并结果构成一个物理块时,也就是数据量足够,可以IO读写,写入外存时,就写入外存。当多路归并其中一路已经归并完毕时,则从外存中调入该路剩余的一部分数据进入内存,继续排序,这样既可实现全局外部排序。终于清楚了。

多路归并可以实现比二路归并更好的时间复杂度,因为可以减少 外存读写次数,这是影响外排算法速度的最主要因素,但是外排路数多了之后,会造成归并时选择最小元素的时间复杂度呈多项式上升,归并的本质其实还是选择,我发现了。构建败者树可以实现将选择最小元素的时间复杂度控制在对数范围内,从而提高排序速度。败者树的理论我就不细述了,和内排算法的堆排序有异曲同工之妙,引入一篇blog胜者树和败者树,在选择排序堆排序中使用了胜者树,外排里是败者树。这篇blog 里用的是java,将外排的整个过程都模拟了一遍。我在这里就不全部模拟了,我就写一下败者树的代码。

二、置换、选择排序
置换选择排序的基本思想就是扩大初始归并段的长度,这样也可以减少排序时间。因为初始归并段增加长度的话,初始归并段的个数就减少了,内排的数量少了,也就减少了时间。
但是置换选择排序的一套算法下来,每个初始归并段的长度增加了,但是并不完全一致,根据统计经验,采用置换选择排序算法得到的初始归并段的平均长度为内存大小的2倍。具体的算法也很好理解,链接在这里,我就不赘述了,置换选择排序过程。可以编程实现,而且是比较简单的一些线性表操作。

三、最佳归并树
除了上述两种提高外排效率的方法外,采用一颗最佳的归并树也是必要的,这样的最终目的也就是为了减少和外存的交互读写时间。给出结论,对于k路归并,如果

(m-1) MOD (k-1)=0

其中m是初始归并段的个数,则不需要加虚设段,否则需要加虚设段。其本质含义就是说,希望初始归并段的个数能够和k相匹配,这样最节省归并时间。

败者树Python代码:

# -*- coding=utf-8 -*-import randomfrom sorting_algorithms import SortingAlgorithmsclass ExternalSortingAlgorithms(object):    '''    败者树外部排序的全部实现:    1、使用gen_k_data 函数产生一个 data,这个data中,k表示归并的路数,l表示每一路中包含数字的个数        e.g. data = gen_k_data(10, 200)             print "generate the data for loser tree: ", data    2、在 merging_soring 函数中,接收上一步产生的数据。并首先生成一个败者树,在这一步中,关键点就是创建败者树,以及调整败者树。        然后,每次产生一个 compare,用来存放当前需要比较的10个数值,取出最小的一个,再从对应的那一路中取出下一个数值放在compare里        对应的位置上。如果某一路都取完了,我们就给该路的compare值赋一个很大的值,比如说我在这里赋了10000,当compare里全部都是10000        说明已经排序完毕,返回result 就是我们的排序结果。        e. g. merging_sorting(data)    '''    def __init__(self):        pass    def gen_k_data(self, k, l):  # 生成k 路随机数,并排序,存放在列表中, k表示归并的路数,l表示每一路多少个数        if k < 0 or l < 0:            print "Wrong given k number or l number."        data = []        sort = SortingAlgorithms()        for n in xrange(0, k):            merge = []            small = random.randint(0, 100)            large = random.randint(800, 1000)            for _ in xrange(0, l):                merge.append(random.randint(small, large))  # small 和 large 用来控制取值范围的。            data.append(sort.heap_sort(merge))        return data    def merging_sorting(self, data):  # 生成一个归并序列,把每一路中的元素挨个排入compare数组中        k = len(data)        compare = []        for i in xrange(0, k):            compare.append(data[i][0])            #data[i].remove(data[i][0])        print compare        def adjust(loserTree, dataArray, n, s):  # 败者树的核心代码            t = (s + n) / 2            while t > 0:  # 从败者树的尾部开始进行比较                if dataArray[s] > dataArray[loserTree[t]]:  # 和败者结点比较                    s, loserTree[t] = loserTree[t], s  # 如果比某个败者结点大,说明该结点失败了,将s结点存入败者树,把败者树的现在的胜结点拿去和其父节点比较。                t /= 2            loserTree[0] = s        def createLoserTree(loserTree, dataArray, n):            for i in range(n):                loserTree.append(0)                dataArray.append(i-n)  # 这里是为了生成败者树用的,            for i in range(n):                adjust(loserTree, dataArray, n, n-1-i)        loserTree = []        dataArray = []        createLoserTree(loserTree, dataArray, k)        for i in xrange(k):            dataArray[i] = compare[i]  # 将数据替换成待排数据            adjust(loserTree, dataArray, k, i)  # 此步执行完毕,败者树才完全创建初始化完毕,正式开始排序归并        result = []        while True:            if data[loserTree[0]][0] > 9999:                break            result.append(data[loserTree[0]][0])  # 添加到 result 中,这是我们需要的结果            data[loserTree[0]].remove(data[loserTree[0]][0])  # 从data 中删除头一个元素            if data[loserTree[0]] == []:                data[loserTree[0]].append(10000)            compare[loserTree[0]] = data[loserTree[0]][0]  # 将下一个元素添加到 compare数组中            adjust(loserTree, compare, k, loserTree[0])        return result