平衡树-Treap基础内容
平衡樹-Treap
2021年8月6日
什么是平衡樹?
平衡樹是指任意節點左右子樹高度差都小于等于1的二叉樹。
平衡樹干什么?
平衡樹對序列的排序,尋找元素的位置有很方便的操作
算法原理
建樹
Treap通過隨機賦予節點一個權值進行建樹,由此大概率避免了建樹成鏈的情況。
【1】、初始化樹
初始化時,需要首先新建key值為正負無窮的兩個節點,作為樹的兩端。隨后依據隨機賦予的權值進行平衡操作。
void built() {get_node(-INF), get_node(INF);root = 1; tre[1].r = 2;pushup(root);if (tre[1].val < tre[2].val)lez(root); }【2】、新增節點
新增節點時,將其權值賦予一個隨機數,隨后初始化當前節點數目以及子節點數目。
int get_node(int key){tre[++idx].key=key;tre[idx].val=rand();tre[idx].cnt=tre[idx].size=1;return idx; }【3】、更新點值
對于每次操作之后,樹的形態發生變化,其節點內容也要發生變化,因此需要更新其子節點數目。
void pushup(int p) {//更新值tre[p].size = tre[tre[p].l].size + tre[tre[p].r].size + tre[p].cnt; }【4】、加入新值
加入新節點,首先對比其key值,依據大小進行左右遍歷.
因加入新節點時,可能有大權值在下的情況,因此需要對節點進行旋轉維護。
void insert(int& p, int key) {if (!p)p = get_node(key);else if (key == tre[p].key)tre[p].cnt++;else if (key > tre[p].key) {insert(tre[p].r, key);if (tre[tre[p].r].val > tre[p].val)lez(p);}else {insert(tre[p].l, key);if (tre[tre[p].l].val > tre[p].val)riz(p);}pushup(p); }【5】、刪除值
依然根據key值進行查找,查找到的節點若有多個相同值,則自減1,否則刪除節點。
刪除節點時,若無任何子樹,則直接刪除,否則,將其旋轉至最底層,然后刪除。
旋轉規則:
當其右子樹為空或左子樹權值更大時,右旋此節點(保證權值大的節點在上面),否則左旋此節點。因其上約束條件約定一定有子樹,因此若不滿足右旋條件,一定是左子樹為空或右子樹權值更大。
void remove(int& p, int key) {if (!p)return;if (tre[p].key == key) {if (tre[p].cnt > 1)tre[p].cnt--;else if (tre[p].l || tre[p].r)if (!tre[p].r || tre[tre[p].l].val > tre[tre[p].r].val) {riz(p); remove(tre[p].r, key);}else { lez(p); remove(tre[p].l, key); }else p = 0;}else if (tre[p].key > key) remove(tre[p].l, key);else remove(tre[p].r, key);pushup(p); }【6】、左旋,右旋
左旋:
首先將節點的右子節點更新為右子節點的左子節點,再將右子樹的左子節點指向原根節點,隨后將根節點更新為右子節點,更新新的左子樹與根節點的size值,左旋完成。
右旋:
首先將節點的左子節點更新為左子節點的右子節點,再將左子樹的右子節點指向原根節點,隨后將根節點更新為左子節點,更新新的右子樹與根節點的size值,左旋完成。
左右旋的操作完全相反。
void lez(int& p) {//左旋int q = tre[p].r;tre[p].r = tre[q].l; tre[q].l = p; p = q;pushup(tre[p].l); pushup(p); }void riz(int& p) {//右旋int q = tre[p].l;tre[p].l = tre[q].r; tre[q].r = p; p = q;pushup(tre[p].r); pushup(p); }為何如此旋轉?
以右旋為例,如圖所示:
若要將左子節點當成根節點,必須將其右子樹拿開之后才能使原來的根節點成為新的右子樹,此時相當于原根節點少了一個左子樹,原左子節點的右子節點沒有了父節點。因左子樹的所有值都比根節點小,因此左子節點的右子節點一定比根節點小,因此可以將原根節點當成原左子節點的右子節點的父節點。
以上操作之后,就可旋轉為:
由此不會改變樹的中序遍歷順序。
查詢
【1】、根據值尋找排名
注:此排名代表有多少比key小的數(加上其本身1個數)
若查詢到相等的節點,則返回其左子樹的值加上它本身。
若值比當前節點key值小,需向左查詢,此時因為當前節點key更大,因此不記錄排名,直接返回左子樹的值即可。
若值比當前值更大,就需要向右子樹查找,但是當前節點以及其左子樹是要記錄到排名中,因此返回左子樹的大小,節點本身數量以及右子樹查詢到的值。
int getrk(int p, int key) {if (!p)return 0;if (tre[p].key == key)return tre[tre[p].l].size + 1;if (tre[p].key > key)return getrk(tre[p].l, key);else return tre[tre[p].l].size + tre[p].cnt + getrk(tre[p].r, key); }【2】、根據排名找值
若查詢到的節點的左子樹大小要比所求排名大,則需向左搜索。
若查詢到的節點的左子樹大小更小但是加上其節點本身大于等于所需排名,則結果就是當前節點的值。
否則,向右子樹查找,每次所需排名需減去當前點及其左子樹的大小。
int getkr(int p, int rank) {if (!p)return INF;if (tre[tre[p].l].size >= rank)return getkr(tre[p].l, rank);if (tre[tre[p].l].size + tre[p].cnt >= rank)return tre[p].key;return getkr(tre[p].r, rank - tre[tre[p].l].size - tre[p].cnt); }【3】、查詢序列中嚴格比某值小的最大值
首先找到某值的左子節點,之后向右子樹遍歷,知道找到最大值。
int minxk(int p, int key) {if (!p)return -INF;if (tre[p].key >= key)return minxk(tre[p].l, key);else return max(tre[p].key, minxk(tre[p].r, key)); }【4】、查詢序列中嚴格比某值大的最小值
與【3】相反
int maxnk(int p, int key) {if (!p)return INF;if (tre[p].key <= key)return maxnk(tre[p].r, key);else return min(tre[p].key, maxnk(tre[p].l, key)); }模板例題
ACwing \253. 普通平衡樹
您需要寫一種數據結構(可參考題目標題),來維護一些數,其中需要提供以下操作:
注意: 數據保證查詢的結果一定存在。
輸入格式
第一行為 n,表示操作的個數。
接下來 n 行每行有兩個數 opt 和 xx,opt 表示操作的序號(1≤opt≤6)。
輸出格式
對于操作 3,4,5,6 每行輸出一個數,表示對應答案。
數據范圍
1≤n≤100000,所有數均在 ?1e7到 1e7 內。
輸入樣例:
8 1 10 1 20 1 30 3 20 4 2 2 10 5 25 6 -1輸出樣例:
2 20 20 20代碼
#include<iostream> #include<algorithm> #include<stdio.h> #include<string> #include<string.h>using namespace std;const int N = 100005, INF = 0x3f3f3f3f; int n;struct P {int l, r;int key;int cnt, size, val; }tre[N];int root, idx;void pushup(int p) {//更新值tre[p].size = tre[tre[p].l].size + tre[tre[p].r].size + tre[p].cnt; }int get_node(int key) {//建立節點tre[++idx].key = key;tre[idx].val = rand();tre[idx].cnt = tre[idx].size = 1;return idx; }void lez(int& p) {//左旋int q = tre[p].r;tre[p].r = tre[q].l; tre[q].l = p; p = q;pushup(tre[p].l); pushup(p); }void riz(int& p) {//右旋int q = tre[p].l;tre[p].l = tre[q].r; tre[q].r = p; p = q;pushup(tre[p].r); pushup(p); }void built() {//建樹get_node(-INF), get_node(INF);root = 1; tre[1].r = 2;pushup(root);if (tre[1].val < tre[2].val)lez(root); }void insert(int& p, int key) {//加點if (!p)p = get_node(key);else if (key == tre[p].key)tre[p].cnt++;else if (key > tre[p].key) {insert(tre[p].r, key);if (tre[tre[p].r].val > tre[p].val)lez(p);}else {insert(tre[p].l, key);if (tre[tre[p].l].val > tre[p].val)riz(p);}pushup(p); }void remove(int& p, int key) {//刪點if (!p)return;if (tre[p].key == key) {if (tre[p].cnt > 1)tre[p].cnt--;else if (tre[p].l || tre[p].r) {if (!tre[p].r || tre[tre[p].l].val > tre[tre[p].r].val) {riz(p); remove(tre[p].r, key);}else {lez(p); remove(tre[p].l, key);}}else p = 0;}else if (tre[p].key > key) {remove(tre[p].l, key);}else remove(tre[p].r, key);pushup(p); }int getrk(int p, int key) {//找排名if (!p)return 0;if (tre[p].key == key)return tre[tre[p].l].size + 1;if (tre[p].key > key)return getrk(tre[p].l, key);else return tre[tre[p].l].size + tre[p].cnt + getrk(tre[p].r, key); }int getkr(int p, int rank) {//找值if (!p)return INF;if (tre[tre[p].l].size >= rank)return getkr(tre[p].l, rank);if (tre[tre[p].l].size + tre[p].cnt >= rank)return tre[p].key;return getkr(tre[p].r, rank - tre[tre[p].l].size - tre[p].cnt); }int minxk(int p, int key) {//求前驅if (!p)return -INF;if (tre[p].key >= key)return minxk(tre[p].l, key);else return max(tre[p].key, minxk(tre[p].r, key)); }int maxnk(int p, int key) {//求后繼if (!p)return INF;if (tre[p].key <= key)return maxnk(tre[p].r, key);else return min(tre[p].key, maxnk(tre[p].l, key)); }int main() {built();cin >> n;while (n--) {int op, x;cin >> op >> x;if (op == 1)insert(root, x);else if (op == 2)remove(root, x);else if (op == 3)cout << getrk(root, x) - 1 << endl;else if (op == 4)cout << getkr(root, x + 1) << endl;else if (op == 5)cout << minxk(root, x) << endl;else cout << maxnk(root, x) << endl;}return 0; } 創作挑戰賽新人創作獎勵來咯,堅持創作打卡瓜分現金大獎總結
以上是生活随笔為你收集整理的平衡树-Treap基础内容的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 冰箱密封条几年一换(有哪些冰箱值得入手)
- 下一篇: AC自动机(写的很乱,仅记录留作自己复习