【C/C++】C和C++11之enum列舉的使用細節
阿新 • • 發佈:2020-12-08
目錄
Updating...
前置芝士
- 二叉搜尋樹(Binary Search Tree,BST)
普通平衡樹
模板:
普通平衡樹的基本操作可以參考上方模板
1. 替罪羊樹
眾所周知 每種平衡樹都有一種操作來維護平衡。
替罪羊樹是暴力維護平衡的(
替罪羊樹要存 左右兒子,節點值,子樹實際大小(前面的一般平衡樹都要用),子樹不實際大小(?)還有刪除標記(後面解釋)。
struct Node
{
int l,r,val;
int size,fact; // 實際大小:fact
bool exist; // exist : 是否存在
}tzy[N];
int cnt,root; // cnt : 節點數量 root : 根
// 建立新節點
inline void newnode(int &now,int val){now=++cnt; tzy[now].val=val; tzy[now].size=tzy[now].fact=1; tzy[now].exist=true;}
1.1. 插入
只要按 BST 插入方法插入即可,但是為了平衡還得在後面加一句判平衡。
void ins(int &now,int val)
{
if (!now){newnode(now,val); check(root,now); return ;} // check : 判平衡
++tzy[now].size; ++tzy[now].fact;
if (val<tzy[now].val) ins(tzy[now].l,val);
else ins(tzy[now].r,val);
}
1.2. 刪除
還記得前面節點裡有維護「刪除標記」嗎?
對,刪除就是把節點打上標記。
而「子樹不實際大小」就是樹中子樹的大小,
「子樹實際大小」就是樹中子樹去掉打了標記的大小。
同樣,為了平衡還得在後面加一句判平衡。
void del(int now,int val)
{
if (tzy[now].exist&&tzy[now].val==val){tzy[now].exist=false; tzy[now].fact--; check(root,now); return ;}
tzy[now].fact--;
if (val<tzy[now].val) del(tzy[now].l,val);
else del(tzy[now].r,val);
}
!.! 判斷樹是否平衡並調整樹
插入和刪除都提到了「判平衡」,但是「判平衡」怎麼寫呢?
我們從根向要操作的節點找,如果找到了需要重構的節點,就暴力重構它的子樹。
判斷是否平衡的條件是:
當前節點的左子樹或右子樹的大小大於當前節點的大小 \(\times \alpha\),其中 \(\alpha\) 是平衡因子,或
當前節點為根的子樹內被刪除的節點數量 \(>\) 樹大小(非實際)的 \(30\%\) 了 .
\(\alpha\) 必須取 \([0.5,1]\),一般取 \(\alpha\in[0.7,0.8]\),最平常的是取 \(\alpha=0.75\) .
inline bool imbalence(int now){return (max(tzy[tzy[now].l].size,tzy[tzy[now].r].size)>tzy[now].size*alpha)||
(tzy[now].size-tzy[now].fact>tzy[now].size*0.3); }
所以總體的 check
大概是這樣的:
void check(int &now,int end)
{
if (now==end) return;
if (imbalence(now)){rebuild(now); update(root,now); return ;}
if (tzy[end].val<tzy[now].val) check(tzy[now].l,end);
else check(tzy[now].r,end);
}
這裡 rebuild
是重構,update
是更新資料(size
)。
update
非常的好寫:
void update(int now,int end)
{
if (!now) return;
if (tzy[end].val<tzy[now].val) update(tzy[now].l,end);
else update(tzy[now].r,end);
tzy[now].size=tzy[tzy[now].l].size+tzy[tzy[now].r].size+1;
}
前面說過了,替罪羊樹的維護平衡(重構)是暴力,替罪羊樹的重構就是拉開(中序遍歷)再拎起來。
vector<int> v;
void ldr(int now) // 拉開
{
if (!now) return;
ldr(tzy[now].l);
if (tzy[now].exist) v.push_back(now);
ldr(tzy[now].r);
}
void lift(int l,int r,int &now) // 拎起來
{
if (l==r){now=v[l]; tzy[now].l=tzy[now].r=0; tzy[now].size=tzy[now].fact=1; return ;}
int m=(l+r)>>1;
while ((l<m)&&(tzy[v[m]].val==tzy[v[m-1]].val)) --m;
now=v[m];
if (l<m) lift(l,m-1,tzy[now].l);
else tzy[now].l=0;
lift(m+1,r,tzy[now].r);
tzy[now].size=tzy[tzy[now].l].size+tzy[tzy[now].r].size+1; // (
tzy[now].fact=tzy[tzy[now].l].fact+tzy[tzy[now].r].fact+1;
}
void rebuild(int &now)
{
v.clear(); ldr(now);
if (v.empty()){now=0; return ;}
lift(0,v.size()-1,now);
}
1.3. & 1.4. 查詢值的排名 / 查詢排名的值
這些就按照普通 BST 寫就行啦
int getrank(int val)
{
int now=root,rank=1;
while (now)
{
if (val<=tzy[now].val) now=tzy[now].l;
else {rank+=tzy[now].exist+tzy[tzy[now].l].fact; now=tzy[now].r;}
} return rank;
}
int getval(int rank)
{
int now=root;
while (now)
{
if (tzy[now].exist&&tzy[tzy[now].l].fact+tzy[now].exist==rank) break;
else if (tzy[tzy[now].l].fact>=rank) now=tzy[now].l;
else {rank-=tzy[tzy[now].l].fact+tzy[now].exist; now=tzy[now].r;} // 這個 rank-=... 有主席樹內味了
} return tzy[now].val;
}
1.5. & 1.6. 求前趨 / 求後繼
前趨和後繼有個小技巧:
前趨:getval(getrank(x)-1);
後繼:getval(getrank(x+1));
inline int pre(int x){return getval(getrank(x)-1);}
inline int nxt(int x){return getval(getrank(x+1));}
實際時空比較
普通平衡樹:
平衡樹 | 程式碼長度 | 時間 | 空間 |
---|---|---|---|
替罪羊樹 | 3.15KB | 352ms | 1.89MB |