数据结构与算法 代码整理:外排序法
外排序原理:
外排序就是能够处理极大量数据的排序算法。通常来说,外排序处理的数据不能一次性装入内存,只能放在读写较慢的外存储器(eg.硬盘)上,通常采用“排序-归并”策略。
算法性能分析:
参考:http://blog.chinaunix.net/uid-25324849-id-2182916.html
多路归并的实现:
败者树是树形选择排序的一种变形。若在双亲结点中记下刚进行完比赛中的败者,而让胜者去参加高一级的比赛,便可得到一颗“败者树”。
如下图a中,在败者树中,根节点ls[2]的双亲结点ls[1]为冠军,指示各归并段中的最小关键字元素在第4段,
结点ls[5]指示败者为b5,胜者为b4,b4将在下一次比赛ls[1]中和b1比较;结点ls[1]指示败者为b1,胜者为b4,与另一场比赛的胜者b2进行比较。
在选得最小关键字元素后,只需修改对应叶子结点的值(b4),使其同一个归并段的下一个元素作为该胜出叶子结点的新的关键字,然后从该节点向上与双亲结点所指的关键字比较,败者留在该双亲,胜者继续向上直至树根的双亲,如图b所示。
算法思想用python实现如下:
1 # 外排序——多路归并排序法 2 # 败者树算法 3 4 # 模拟磁盘中已排序区域,并在文件最后添加一个极大数作为判断结束的哨兵值 5 # 注意,由于从列表后面删除元素要快于从前面删除,所以示例中的区域采用倒序排列,把第一个关键字放在最后 6 B = {1:[999, 16,15,10], 7 2:[999, 20,18,9 ], 8 3:[999, 40,22,20], 9 4:[999, 25,15,6 ], 10 5:[999, 48,37,12]} 11 k = len(B) # k路归并 12 merge_result = [] # 目标归并段 13 14 15 # k 路归并处理程序 16 # 利用败者树ls将编号从1到k的k个输入归并段元素归并到输出端 17 # b[1]到b[k]为败者树上的k个叶子结点,分别存放k个输入归并段中当前元素的关键字 18 b = [0]*(k+1) # 起点从1开始,第0位空着 19 ls = [0]*(k+1) # 由于败者树是完全二叉树(不含叶子),则该树的结点数即叶子个数(归并路数),可采用顺序储存结构 20 21 22 def K_Merge(): 23 for i in range(1,k+1): # 分别从k个输入归并中读入该段中的第一个关键字 24 input(i) # 保存到叶子节点中 25 CreaterLoserTree() # 根据当前关键字b建立败者树ls 26 while b[ls[1]]!=999: # 当冠军值非最大的哨兵值(存在未被归并的元素)时,归并继续 27 q = ls[1] # q指示当前最小关键字所在的归并段 28 output(q) # 将编号为 q 的段中最前面关键字写入输出归并段 29 input(q) # 从编号为 q 的段中读入下一个关键字 30 AdjustLoserTree(q) # 根据当前冠军,调整败者树,选择新的最小关键字 31 # output(1) # 将喊最大关键字的元素写入输出段(假设选择最大关键字作为哨兵的话,不过这里不需要) 32 33 # 建立败者树ls 34 # 已知从b[1]到b[k]为完全二叉树ls的叶子结点存有k个关键字,沿从叶子到根的k条路径将ls调整为败者树 35 def CreaterLoserTree(): 36 # 假设败者树上存放的都是最小键值(所在的序号),则可以对每个叶子节点进行测试 37 # 使当前结点的键值上升为新的败者,以替换掉原来树节点的位置 38 39 minval = b[1] 40 for i in range(2,k): # 先从第一批叶子结点中找到拥有最小的键值所在的段 41 if b[i]<minval: 42 minval = b[i]; minidx = i 43 44 for i in range(1,k+1): 45 ls[i] = minidx # 设置败者初值,为最小键值所在的段序号 46 47 for i in range(k,0,-1): 48 AdjustLoserTree(i) # 依次从b[1],b[2],...,b[k]出发调整败者树 49 50 # 选择最小关键字后,从叶到根调整败者树,选出下一个最小关键字 51 # 沿从叶子节点b[s],到根节点ls[0]的路径调整败者树 52 def AdjustLoserTree(s): 53 t = (s+k+1)/2 # ls[t]是b[s]的父结点 (+1是为了四舍五入) 54 while t > 1: 55 if b[s] > b[ls[t]]: # 败者树的结点中存放的是比较后较大的关键字所在的段序号 56 s,ls[t] = ls[t],s # 当b[s]被击败后,将s指示向新的胜利者,并将败者留着当前的内结点中 57 t = (t+1)/2 # 上移到上一个父结点 58 ls[1] = s 59 60 61 62 # 模拟外存读入函数,从i所指向的区域读取第一个关键字 63 def input(i): 64 b[i] = B[i][-1] 65 # 模拟输出归并函数,从i所指向的区域中读取第一个关键字,并输出到归并段 66 def output(i): 67 merge_result.append(B[i].pop()) 68 69 70 if __name__=="__main__": 71 pass 72 K_Merge() 73 print merge_result 74 #>>> [6, 9, 10, 12, 15, 15, 16, 18, 20, 20, 22, 25, 37, 40, 48]