推荐博客 : 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 ", 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); } }