• k路归并(败者树,记录败者)


          败者树在外排序中用到,每加入一个数字时,调整树需要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值过大时,虽然归并次数减少,但读写外存次数会增加。

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


  • 相关阅读:
    RF手持配置问题
    S4系统编辑屏幕报错
    【SAP】日志表CDHDR和CDPOS
    VA01隐藏销售凭证流的金额
    ABAP MODIFY SCREEN
    解决SMARTFORMS 中table 控件单行跨页的问题
    golang json 性能分析
    【高性能】GO 高性能专题
    9千万次循环 从2分3秒 优化到 7.3秒的过程 GO语言
    IDE 插件开发 相关点 -------------- Vscode debug protocol JDWP DAP
  • 原文地址:https://www.cnblogs.com/riskyer/p/3323138.html
Copyright © 2020-2023  润新知