败者树在外排序中用到,每加入一个数字时,调整树需要o(lgk),比较快。外排序过程主要分为两个阶段:(1)初始化各归并段写入硬盘,初识化的方法,可利用内排序方法还可以一种叫置换选择排序的方法(参考数据结构--李春葆)。


为什么需要败者树

     外排序过程考虑时间代价时,主要考虑访问磁盘的次数。那么基于两路归并排序的缺点在哪里呢?主要是访问磁盘的次数太多了?请看下图:

假设初始化归并段有m个,则二路归并需要访问硬盘的次数为log2(m)。按照这个方法,那是不是我们只要增加k就可以减少次数呢?答案是肯定的。就是说是k路归并的话,访问硬盘次数就是logk(m)。但是这里边存在一个矛盾:如果增大k,归并的时候比较次数增加了。那我们只要找到一种可以增大k,然后比较次数又比较少的方法就行了,这就是多路归并---败者树。看下面推导:

这里边logk(m)表示读取次数等于(log2(m)/log2(k)),比较次数(n-1),如果采用多路归并树的话比较次数log2(k),恰好与分母约掉,这样归并的比较次数与k无关了。

败者树调整策略

(1)输入每个归并段的第一个记录作为归并树的叶子节点。建立初始化归并树。

(2)两量相比较,父亲节点存储了两个节点比较的败者(节点较大的值);胜利者(较小者)可以参与更高层的比赛。这样树的顶端就是当次比较的冠军(最小者)。

(3)调整败者树,当我们把最小者输入到输出文件以后,需要从相遇的归并段取出一个记录补上去。补回来的时候,我们就需要调整归并树,我们只需要沿着当前节点的父亲节点一直比较到顶端。比较的规则是与父亲节点比较(父亲节点只是记录了一个败者索引,我们需要通过索引找到相应的值进行比较),比较小的(胜者)可以参与更高层的比较,即可以跟他爷爷比较,一直向上,直到根节点。比较大的(失败者)留在当前节点。

败者树编程(K路归并)

在实现利用败者树编程的时候,我们把败者树的叶节点和非叶点分开定义:

(1)叶节点存放在:b[k+1],其中b[0..k-1]存放记录,b[k]存放了一个比所有记录一个最小值,表示虚节点。

(2)败者节点存放:ls[k],ls[1...k-1]存放各次比较的败者数组索引。ls[0]存放了最后的冠军。

注意:这里每个叶节点都是连都非叶节点上的,这个叶节点就是我们的父节点,那我们怎么算出连到那个非叶节点上呢:通过t = (index + K)/2,得到我们父节点的索引t,这样我们在调整树的时候只需要比较b[ls[t]],然后一直比较就行了。

(1)败者树创建

首先,是创建归并树,程序开始将ls[0...k-1]=K,表示第K+1(虚设)个归并段的记录当前最小。然后,我们从k-1到0,每次加入一个记录进行一次调整,算法自顶向下,直到所有记录加进来,归并树也就建好了。

#include <iostream>

using namespace std;

#define  K  5 //表示5路归并
#define MIN INT_MIN; int b[K+1] = {17,5,10,39,15};
int ls[K] = {0};//记录败者的序号 void Adjust(int s)
{
for(int t=(s+K)/2; t>0; t=t/2){//t=(s+k),得到与之相连ls数组的索引
if(b[s] > b[ls[t]])//父亲节点
{
int temp = s; //s永远是指向这一轮比赛最小节点
s = ls[t];
ls[t]=temp;
}
}
ls[0] = s;//将最小节点的索引存储在ls[0]
} void CreateLoser()
{
b[K] = MIN;
int i;
for(i=0;i<K;i++)ls[i]=K;
for(i=K-1;i>=0;i--)Adjust(i); //加入一个基点,要进行调整 }
int main()
{
CreateLoser();
system("pause");
return 0;
}

图示一下创建树的过程:

(2)归并排序

读入数据,创建归并树,判断b[ls[0]]==MAX,等于表示所有记录都已输出。不等于,输出当前冠军,然后从相应归并段读入数据填上。注意,如果相应的归并段已经空了,则填上MAX。下面给出伪代码:

void K_Merge()
{
for(int i=0;i<K;i++){
input(i);//输入到b[i]
}
CreateLoser();
while(b[ls[0]]!=MAXKEY){//只要不是最大值
q = ls[0];//得到冠军的索引
output(b[q]);
intput(b[q]);
Adjust(q);
}
}

(3)整个代码(http://blog.csdn.net/tiantangrenjian/article/details/6838491

#include <iostream>
using namespace std; #define LEN 10 //最大归并段长
#define MINKEY -1 //默认全为正数
#define MAXKEY 100 //最大值,当一个段全部输出后的赋值 struct Array
{
int arr[LEN];
int num;
int pos;
}*A; int k,count;
int *LoserTree,*External; void Adjust(int s)
{
int t=(s+k)/2;
int temp;
while(t>0)
{
if(External[s] > External[LoserTree[t]])
{
temp = s;
s = LoserTree[t];
LoserTree[t]=temp;
}
t=t/2;
}
LoserTree[0]=s;
} void CreateLoserTree()
{
External[k]=MINKEY;
int i;
for(i=0;i<k;i++)LoserTree[i]=k;
for(i=k-1;i>=0;i--)Adjust(i);
} void K_Merge()
{
int i,p;
for(i=0;i<k;i++)
{
p = A[i].pos;
External[i]=A[i].arr[p];
//cout<<External[i]<<",";
A[i].pos++;
}
CreateLoserTree();
int NO = 0;
while(NO<count)
{
p=LoserTree[0];
cout<<External[p]<<",";
NO++;
if(A[p].pos>=A[p].num)External[p]=MAXKEY;
else
{
External[p]=A[p].arr[A[p].pos];
A[p].pos++;
}
Adjust(p);
}
cout<<endl;
} int main()
{
freopen("in.txt","r",stdin); int i,j;
count=0;
cin>>k;
A=(Array *)malloc(sizeof(Array)*k);
for(i=0;i<k;i++)
{
cin>>A[i].num;
count=count+A[i].num;
for(j=0;j<A[i].num;j++)
{
cin>>A[i].arr[j];
}
A[i].pos=0;
}
LoserTree=(int *)malloc(sizeof(int)*k);
External=(int *)malloc(sizeof(int)*(k+1)); K_Merge(); return 0;
}


注意点

归并路数k增大时,相应的需要增加输入缓冲区个数。如果可供应的内存不变,这将减少每个缓冲区的容量,使得内外存交换数据次数增大。所以k值过大时,虽然归并次数减少,但读写外存次数会增加。

另外了,考虑比较次数最小,可构造哈夫曼树。

k路归并(败者树,记录败者)的更多相关文章

  1. 多线程外排序解决大数据排序问题2(最小堆并行k路归并)

    转自:AIfred 事实证明外排序的效率主要依赖于磁盘,归并阶段采用K路归并可以显著减少IO量,最小堆并行k路归并,效率倍增. 二路归并的思路会导致非常多冗余的磁盘访问,两组两组合并确定的是当前的相对 ...

  2. 使用最小堆来完成k路归并 6.5-8

    感谢:http://blog.csdn.net/mishifangxiangdefeng/article/details/7668486 声明:供自己学习之便而收集整理 题目:请给出一个时间为O(nl ...

  3. 算法导论 6.5.9 堆实现K路归并问题

    问题: 设计一个时间复杂度为O(NlogK)的算法,它能够将K个有序链表合并为一个有序链表,这里的N为所有输入链表包含的总的元素个数 分析: 该问题为经典的利用堆完成K路归并的问题: 当K个序列满足一 ...

  4. Merge k Sorted Lists, k路归并

    import java.util.Arrays; import java.util.List; import java.util.PriorityQueue; /* class ListNode { ...

  5. HDU - 6041:I Curse Myself(Tarjan求环&K路归并)

    There is a connected undirected graph with weights on its edges. It is guaranteed that each edge app ...

  6. 【二叉堆】k路归并问题(BSOJ1941)

    Description 有n个函数,分别为F1,F2,...,Fn.定义Fi(x)=Ai*x^2+Bi*x+Ci(x∈N*).给定这些Ai.Bi和Ci,请求出所有函数的所有函数值中最小的m个(如有重复 ...

  7. Problem A: Assembly Required K路归并

    Problem A: Assembly Required Princess Lucy broke her old reading lamp, and needs a new one. The cast ...

  8. POJ-2442 Sequence K路归并问题

    题目链接:http://poj.org/problem?id=2442 问题一:K个有序表合成一个有序表,元素共有n个.用堆优化 问题二:两个序列的前n小的元素.堆优化. 这题就是问题二的扩展,每次处 ...

  9. POJ 2442(优先队列 k路归并 堆)

    Description Given m sequences, each contains n non-negative integer. Now we may select one number fr ...

随机推荐

  1. 基于Visual C++2013拆解世界五百强面试题--题17-程序结果分析1

    分析程序结果,分析过程我们就写在程序注释里面. 写出下列代码的输出内容 #include <stdio.h> int inc(int a) { return (++a); } int mu ...

  2. 运行于64操作系统上的C#客户端通过WCF访问Oracle数据库不兼容问题

    运行平台: Windows 7  64位操作系统 运行环境: IIS 7 编程语言:C# 数据库: 32位的Oracle 10g 运行原因:64位操作系统C#客户端程序通过WCF访问ORACLE数据库 ...

  3. C/C++ 基础教程

          自从做IOS后,就比较少用纯C++的方式写代码了,因为Obj-C的代码风格和C++的风格还是有一点区别的.怕自己忘记了C/C++的基础.整理了一些C/C++基础的网站,供大家学习C/C++ ...

  4. Threads and Anonymous Classes in JAVA

    As we all know,a thread is a separate process on your computer.you can run multiple threads all at t ...

  5. Tableau Server 8.0 升级到 8.3 过程记录

    一.使用账号(管理员权限),安装文件复制到服务器 二.检查维护状态 如果维护状态过期,更新到新版本会变成未授权. 先进Manage Product Keys刷新一下维护日期(其实不刷新也无所谓.到时候 ...

  6. NSOperationQueue和GCD的区别

    使用NSOperationQueue用来管理子类化的NSOperation对象,控制其线程并发数目.GCD和NSOperation都可以实现对线程的管理,区别是 NSOperation和NSOpera ...

  7. Cocos2d-x win7 + vs2010 配置图文详解(亲测)

    下载最新版的cocos2d-x.打开浏览器,输入cocos2d-x.org,然后选择Download,本教程写作时最新版本为cocos2d-1.01-x-0.9.1,具体下载位置如下图: 下载完之后, ...

  8. uml笔记

    把进度放在好了: 活动图与业务流程 对业务流程支持的主要图形就是活动图,活动图的主要目的在陈述活动与活动之间流程控制的转移.

  9. jquery 中多个存在依赖关系的ajax调用解决办法

    在使用ajax异步调用的时候,可能碰到同时调用多个ajax这种情况.而且多个ajax之间还存在依赖关系.这种情况怎么处理呢?   有两种办法: 一种是多个ajax嵌套调用,这时需要设置async为fa ...

  10. [转] HTC:html组件

    ~~~不时会看到 用htc实现 hover, border-raius等效果,可以用来弥补IE6-8的不足.那么htc到底是什么呢? 1.摘要 本文在实例的基础上讨论了HTC(HTML Compone ...