• [算法]败者树


      胜者树和败者树都是完全二叉树,是树形选择排序的一种变型。每个叶子结点相当于一个选手,每个中间结点相当于一场比赛,每一层相当于一轮比赛。
      不同的是,胜者树的中间结点记录的是胜者的标号;而败者树的中间结点记录的败者的标号
      胜者树与败者树可以在log(n)的时间内找到最值。任何一个叶子结点的值改变后,利用中间结点的信息,还是能够快速地找到最值。在k路归并排序中经常用到。

      叶子节点相当于参赛选手,中间节点是比赛,比赛中败者记录在中间节点,胜者继续参加后面的比赛,直到根节点。根节点之上的一个节点用来记录最终胜者。

      败者树的建立:在参赛者数组b[]的最后添加一位,存放一个参赛选手的绝对最小值(选手都是正数的话,如-1)。所有中间节点都记录这个最小值的下标。然后依次调整(adjust())各个选手即可。

      败者树的调整:当改变一个选手的值,需要调整以维持败者树的形态。败者树只需调整选手的父节点即可。当子节点的值大于父节点,则子节点记录于父节点(小为胜利,记录败者),父节点继续与其父节点比赛;若子节点小于父节点,则直接使用子节点进行下一轮比赛。

    #define LEN 5//败者树容量,多路归并数目
    #define MIN -1//所有数据的可能最小值
    
    int ls[LEN+1];//败者树,ls[0]存放胜者,其余存放败者
    int buf[LEN+1];//存放多路归并的头元素值,多出来的一位放MIN
    
    void adjust(int s,int *buf){//s是需要调整的buf的下标
        int t=(s+LEN)/2;//得到s的在败者树上面的父节点
        while(t>0){//不断与父节点对比,直到败者树的根节点
            if(buf[s]>buf[ls[t]]){//如果当前节点s(胜者)大于父节点
                ls[t]^=s;//交换ls[t]和s
                s^=ls[t];//s记录胜者
                ls[t]^=s;//父节点记录败者
            }
            t/=2;//得到败者树的上一个父节点,继续与当前胜者s比较
        }
        ls[0]=s;//最终的胜者记录于ls[0]
    }
    
    void build(int *buf){
        buf[LEN]=MIN;//最后一位放MIN
        for(int i=0;i<LEN+1;++i)
            ls[i]=LEN;//所有败者树初始化为MIN的下标
        for(i=0;i<LEN;++i)
            adjust(i,buf);//依次调整即可完成初始化
    }
    
    int main()
    {
        //初始buf
        int tmp[5]={18,21,16,11,19};
        memcpy(buf,tmp,LEN*sizeof(int));
        build(buf);
        cout<<buf[ls[0]]<<endl;//输出11
    
        //取出11后,buf[3]=17
        int tmp1[5]={18,21,16,17,19};
        memcpy(buf,tmp1,LEN*sizeof(int));
        adjust(3,buf);
        cout<<buf[ls[0]]<<endl;//输出16
    
        return 0;
    }

     用最小堆也可以,两者都可以在log2(n)时间内找出最小值,数据大时败者树稍微快一点

    标题:班门弄斧,发一篇自己写的算法分析文章(续)

    发信站:水木社区(SatSep218:12:162006),站内

    昨晚睡觉之前,还在想堆和败者树到底哪一个更优的题目。终极通过心算得出答案(感爱好的人可以算一下,并不难):假如将N个数的分布看作是完全随机分布,则通过求和可以计算出,当从N个数中提取出一个新的数m时,假如该数比之前找到的n个最大的数中的最小的x大,则会替换x进进“堆”或者“败者树”。

    在这一次重新维护堆和败者树的时候,堆的期看比较次数是2logn-1,而败者树是logn。但是由于败者树内部节点存放的是叶子节点的下标,在进行比较的时候需要间接取值(先访问内部节点得到下标,在用此下标取到叶子节点中存放的数),所以比较时败者树访问内存次数是堆的两倍。

    而在交换次数上(堆是父节点和子节点交换,败者树是往上走的优越者和内部节点存放的败者进行交换,每次交换需要三次操纵),堆的交换次数期看值是1/2logn-1/4,而败者树是1/2logn(这里全都忽略了logn的取整题目)。

    我不能确定到底一次比较和一次内存访问到底哪一个更快,我猜想是内存访问更快,那样的话,败者树的比较性能会比堆更优。而在交换方面,到底那1/4的差会有多大影响,我也不好说了。

    看来除非做实验,很难分出堆和败者树的高下了。

  • 相关阅读:
    MYSQL学习笔记——数据类型
    MYSQL学习笔记——常用语句
    MYSQL学习笔记——基本语法
    Java虚拟机——类加载机制
    Java虚拟机——Class类文件结构
    Tmux
    nginx 更新提示端口占用的解决办法
    fcitx 无法启动
    E:无法修正错误,因为您要求某些软件包保持现状,就是它们破坏了软件包间的依赖关系
    清除浮动
  • 原文地址:https://www.cnblogs.com/iyjhabc/p/3141665.html
Copyright © 2020-2023  润新知