败者树,图解败者树,算法执行调整每一步详细分析

多路归并-败者树。可以视为一棵安全二叉树的变形。每个叶子结点存放各归并段在归并过程中当前参加比较的记录,内部结点用来记忆叶子结点和对应的父结点比较的失败者,而胜者继续往上进行比较,一直到根结点,也就是0号节点。最终各节点就是一次遍历调整得到的最大值。
int arr[DataSourceNumber][2] = {
        {10,9},{9,8},{11,7},{8,6},{8,7}
};   //假定有这个数据集,5个数据来源,每个数据来源有两个数据
对应到现实问题中,数据来源可以是一次不能全部读入计算机内存的文件。

算法核心,调整败者树
void adjust(int index)  //败者树从叶子结点index开始向上调整
{
        //找到index对应的父结点
        int father = (index + DataSourceNumber)>>1;
        while(father>0) 
        {
                if( get(treeNode[father])>get(index) )  
//和父结点比较,败者的索引更新父结点
                {
                        int tmp = treeNode[father];
                        treeNode[father] = index;
                        index = tmp;  //向上继续比较,更新index
                }
                father /= 2;
        }
        treeNode[0] = index;
}
dxNblQAAAAASUVORK5CYII=
//这个树和堆排序一样,只是想象出来的,真正排序的时候不会构建这么一个树
//初始化的过程中,在所有分组中查看第一个数据,找出最大的一个数据所在的分组进行初始化树的非叶子结点。上图中最大的是11,在第二号分组。
//初始化第一步之后,开始从最后一个叶子结点调整败者树,最终treeNode[0]存放的就是最大的一个数,也就是最终胜出的那个数。
CRMAHD6v7wO4ForM9hGAAAAAElFTkSuQmCC   //从4号开始,父结点2(11)>4(8),对应的父结点改成8所在的数据源4,相应的index=2;继续比较,没有发生变化; 
CRMAnP4ffkXpIiXVk7QAAAAASUVORK5CYII=   //3号的8开始比较,当比较到父结点的父结点的时候,满足败者更新父结点,index = 2。继续比较,最后treeNode[0] = 2;
//2(11)调整没有变化.
QSXKJrPgxcIAAAAASUVORK5CYII= //1号的9调整之后,父结点更新变成1,index = 2;继续调整没有变化 ilLWDdSeXXIAAAAASUVORK5CYII=  //0号的10和父结点指向的3中的第一个元素比较,不满足调整,接着和父结点的父结点比较,2(11)>0(10)调整,此时index = 2,表示胜利的结点在2号数据源,最终treeNode[0] = 2。输出最大值11
obS+W7BDxPAAAAAElFTkSuQmCC  //输出11之后2号数据源剩下7,从该叶子结点开始重新调整 299A7tpeECYBd8E8oNs0KfeHNwYtijVRl0OqH3tllMqjhfsxZwezlIl5SsKu2p3AnLwkTAIz+PxEPcTzgz7WLAAAAAElFTkSuQmCC  //败者叶子结点,更新父结点,index = 1;继续和父结点的父结点1(0)比较
4BjSpW6tbIMAAAAAElFTkSuQmCC //0(10)>1(9),更新,index = 0;最终treeNode[0] = 0;输出第二大的值10,如下图。
D3ydkK02f6wVAAAAAElFTkSuQmCC  //往后继续从0(9)叶子结点开始调整,最终输出9  hTj7STAAw+v+J368tTDbGYwAAAABJRU5ErkJggg==  //9输出后,0号数据源没有数据了,可是下一步还要从这个打乱平衡的点继续调整,为了能够让算法继续进行下去,这里我们要进行特殊设置,该数据源如果没有数据了,就设置一个能表示的最小数据放在对应的点。
网上好多算法没有这个,其实败者树是用来处理海量数据的,所以一般不存在这样的问题,但是为了算法的鲁棒性和稳定性,讲道理还是要做设置的。
B+DCsSHNTxPiAAAAAElFTkSuQmCC此时index = 3;1(1)>3(8),更新,index = 1
ffq+vcnRzMwAAAAASUVORK5CYII=  //treeNode[0]=1,输出9
wP9vBqWzoNJVAAAAABJRU5ErkJggg== //后面的操作类似,继续从1(8)开始调整,最终输出8,就不在画图说明了 gOJQrzKevtFzRqaTa0x6Y+8zuywmLSKQNSHUf0EsLymUqO0pPMlHhgkARv8PAW8l48t0yLQAAAAASUVORK5CYII=  输出11,10,9,9,8

//模拟一个小范围的败者树多路归并
//假设数据源来自5个数组,且这些数组都要是有序的(降序的)
#include<iostream>
#include<vector>
#include<limits>
using namespace std;
static const int DataSourceNumber = 5;  //数据源有五个数组
//定义数据源,一共5个,假设每个数组有两个元素
int arr[DataSourceNumber][2] = {
        {10,9},{9,8},{11,7},{8,6},{8,7}
};
//定义非叶子结点数组,非叶子结点的值表示该结点记录的是哪一个数组来源的值
int treeNode[DataSourceNumber];

//定义叶子结点数组,对应的数组小标表示是那个数据源,值表示数据源中的数据
int node[DataSourceNumber];

//定义数据源的偏移指向数据,因为每次从数据源中读取了一个数据,对应的索引就要偏移到下一位
int Iterator[DataSourceNumber] = {0};  //初始化全为0,因为每个数源都是要从第0个数据开始取

//定义获取叶子结点的值的函数
int get(int index)
{
        return node[index];
}

//定义设置叶子结点的值的函数
void set(int index)
{
        if(Iterator[index] == 2)
        {
                node[index] = numeric_limits<int>::min();
                return ;
        }
        node[index] = arr[index][Iterator[index]] ; //结点node[index]的值就是index个数据源的第Iterator[index]个值,
        //之后要设置Iterator[index]偏移到下一个,这样下次才能取出下一个值设置对应的node结点
        Iterator[index] += 1;
}
void adjust(int index)  //败者树从node结点index开始向上调整
{
        //找到index对应的父结点
        int father = (index + DataSourceNumber)>>1;
        while(father>0) 
        {
                if( get(treeNode[father])>get(index) )  
//和父结点比较,败者的索引更新父结点
                {
                        int tmp = treeNode[father];
                        treeNode[father] = index;
                        index = tmp;  //向上继续比较,更新index
                }
                father /= 2;
        }
        treeNode[0] = index;   //最终胜者
}


//设置摆着树的初始化工作
void init()
{ //败者树要找出最小的指定个数的数,先用数据源数组中首个元素最大的数据源索引来初始化非叶子结点
        //然后开始调整
        memset(Iterator,0,sizeof(Iterator));  //
        //1、设置node
        for(int idx=0;idx!=DataSourceNumber;++idx)
        {
                set(idx);
        }
        int MAX = 0;  //初始最大元素来自数组源0
        for(int idx=0;idx!=DataSourceNumber;++idx)
        {
                if(node[idx]>node[MAX])
                        MAX = idx;
        }
        //2、初始化treeNode
        for(int idx=0;idx!=DataSourceNumber;++idx)
        {
                treeNode[idx] = MAX;
        }
        //3、开始从最后一个非叶子结点调整
        for(int idx=DataSourceNumber-1;idx>=0;--idx)
        {
                adjust(idx);
        }
}
vector<int> merge(int cnt)  //要输出cnt多少个值
{
        vector<int> ans;
        while(cnt)
        {
                ans.push_back(node[treeNode[0]]);  //败者树的0号节点存放的就是当前最小的node值索引
                set(treeNode[0]);  //输出一个,要更新对应的叶子结点的值
                adjust(treeNode[0]);  //调整
                --cnt;
        }
        return ans;
}

int main()
{
        init();
        vector<int> ret = merge(5);
        for(auto elem:ret)
                cout<<elem<<" ";
        cout<<endl;


        init();
        vector<int> ret2 = merge(10);
        for(auto elem:ret2)
                cout<<elem<<" ";
        cout<<endl;
        system("pause");
}
8DPKkbY4F0ruEAAAAASUVORK5CYII=   //set函数没有这是min的操作,输出10个数据就是错的

转载于:https://www.cnblogs.com/meihao1203/p/9561256.html

  • 1
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值