划分树

推荐博客 : https://www.cnblogs.com/hchlqlz-oj-mrj/p/5744308.html

  划分树,类似于线段树,是一个完全二叉树,主要可以用来求解区间第K大元素,时间复杂度为 logn, 快排也可以实现这个操作,但是会改变原序列,就需要每次操作后在复原。

下面给出一棵划分树的建树图:

红色标记的点都进入了左区间。

划分树有两个重要的数组 , val[20][i] , 表示在第 k 层所有元素的分布情况,第 0 层表示初始序列。

num[20][i] 表示第 k 层第 i 个元素前由多少个元素进了区间的左侧,包括该元素本身。

建树的过程类似线段树

void build(int l, int r, int k){
    if (l == r) return;
    int mid = (l+r)>>1;
    int isame = mid-l+1;
    
    for(int i = l; i <= r; i++) {
        if (val[k][i] < sorted[mid]) isame--; // 有多少个同中间值相同的元素被放入左侧。
    }
    int ls = l, rs = mid+1;
    for(int i = l; i <= r; i++){
        if (i == l) num[k][i] = 0;
        else num[k][i] = num[k][i-1];
        
        if (val[k][i] < sorted[mid] || (isame > 0 && val[k][i] == sorted[mid])) {
            val[k+1][ls++] = val[k][i];
            num[k][i]++;
            if (val[k][i] == sorted[mid]) isame--;
        }
        else val[k+1][rs++] = val[k][i];
    }
    build(l, mid, k+1);
    build(mid+1, r, k+1);
}

 查询的过程

int ans = 0;
void query(int l, int r, int k, int ls, int rs, int kk){
    //printf("ls = %d r = %d\n", ls, rs);
    if (l == r) {ans = val[k][l]; return;}
    
    int ly;
    if (ls == l) ly = 0;
    else ly = num[k][ls-1]; // 区间左侧进入左区间的个数 
    int lsum = num[k][rs]-ly; // 整个区间进入左区间的个数
    int mid = (l+r) >> 1;
    if (kk <= lsum){
        int lt = l+ly;
        query(l, mid, k+1, lt, l+ly+lsum-1, kk);
    } 
    else{
        int rt = mid+1+ls-l-ly;
        query(mid+1, r, k+1, rt, rt+rs-ls+1-lsum-1, kk-lsum);
    }
}

 

posted @ 2018-04-02 19:40  楼主好菜啊  阅读(307)  评论(0编辑  收藏  举报