OSDN Git Service

Update scapegoat.md
authorSiej <42795424+Siej@users.noreply.github.com>
Wed, 29 Aug 2018 07:02:14 +0000 (15:02 +0800)
committerGitHub <noreply@github.com>
Wed, 29 Aug 2018 07:02:14 +0000 (15:02 +0800)
docs/ds/scapegoat.md

index a6ae3a7..1738e7e 100644 (file)
@@ -1,6 +1,4 @@
-# 替罪羊树\r
-\r
-**替罪羊树**是一种依靠重构操作维持平衡的重量平衡树。替罪羊树会在插入、删除操作时,检测途经的所有节点,若发现失衡,则将以该节点为根的子树重构。\r
+**替罪羊树**是一种依靠重构操作维持平衡的重量平衡树。替罪羊树会在插入、删除操作时,检测途经的节点,若发现失衡,则将以该节点为根的子树重构。\r
 \r
 我们在此实现一个可重的权值平衡树。\r
 \r
@@ -12,19 +10,25 @@ int cnt, // 树中元素总数
  wn[MAXN], // 本数据出现次数(为 0 代表已删除)\r
   s[MAXN], // 以本节点为根的子树大小\r
  sd[MAXN]; // 已删除节点不计的子树大小\r
\r
+void Calc(int k) {\r
+// 重新计算以 k 为根的子树大小\r
+ s[k] = s[lc[k]] + s[rc[k]] + wn[k];\r
+ sd[k] = sd[lc[k]] + sd[rc[k]] + wn[k];\r
+}\r
 ```\r
 \r
 ## 重构\r
 \r
-首先,如前所述,我们需要判定一个节点是否需要重构。为此我们引入一个比例常数 $\alpha$(取值在 $$(0.5,1)),如某节点的子节点大小占它本身大小的比例超过 $\alpha$,则重构。\r
+首先,如前所述,我们需要判定一个节点是否应重构。为此我们引入一个比例常数 $\alpha$(取值在 $(0.5,1)$,一般采用 $0.7$ 或 $0.8$),若某节点的子节点大小占它本身大小的比例超过 $\alpha$,则重构。\r
 \r
-另外由于我们采用惰性删除(删除只使用 `wn[i]--`),已删除节点过多也影响效率。因此如果未被删除的子树大小占子树总大小的比例低于 $\alpha$,则亦重构。\r
+另外由于我们采用惰性删除(删除只使用 `wn[k]--`),已删除节点过多也影响效率。因此若未被删除的子树大小占总大小的比例低于 $\alpha$,则亦重构。\r
 \r
 ``` cpp\r
 inline bool CanRbu(int k) {\r
 // 判断节点 k 是否需要重构\r
  return wn[k] && (alpha * s[k] <= (double)std::max(s[lc[k]], s[rc[k]])\r
-  || alpha * s[k] >= (double)sd[k]);\r
+  || (double)sd[k] <= alpha * s[k]);\r
 }\r
 ```\r
 \r
@@ -41,13 +45,12 @@ void Rbu_Flatten(int& ldc, int k) {
 }\r
 \r
 int Rbu_Build(int l, int r) {\r
-// 将数组内 [l, r) 区间重建成树\r
- int mid = l + r >> 1;\r
+// 将 ldr[] 数组内 [l, r) 区间重建成树,返回根节点\r
+ int mid = l + r >> 1; // 选取中间为根使其平衡\r
  if(l >= r) return 0;\r
  lc[ldr[mid]] = Rbu_Build(l, mid);\r
- rc[ldr[mid]] = Rbu_Build(mid + 1, r);\r
- if(l + 1 == r) s[ldr[mid]] = sd[ldr[mid]] = wn[ldr[mid]];\r
- else Calc(ldr[mid]);\r
+ rc[ldr[mid]] = Rbu_Build(mid + 1, r); // 建左右子树\r
+ Calc(ldr[mid]);\r
  return ldr[mid];\r
 }\r
 \r
@@ -61,9 +64,11 @@ void Rbu(int& k) {
 \r
 ## 基本操作\r
 \r
+几种操作的处理方式较为类似,都规定了**到达空结点**与**找到对应结点**的行为,之后按**小于向左、大于向右**的方式向下递归。\r
+\r
 ### 插入\r
 \r
-插入时,从根节点向下寻找插入元素位置。如没有相同元素则新建节点,如有则直接 `wn[k]++`。最后,途经的可重构节点进行重构。\r
+插入时,到达空结点则新建节点,找到对应结点则 `wn[k]++`。递归结束后,途经的节点可重构的要重构。\r
 \r
 ``` cpp\r
 void Ins(int& k, int p) {\r
@@ -81,7 +86,7 @@ void Ins(int& k, int p) {
 \r
 ### 删除\r
 \r
\83°æ\80§å\88 é\99¤ï¼\8cå\8f¯ä»¥é\87\87ç\94¨ `wn[k]--`ã\80\82å\9c¨æ\9c\80å\90\8e沿é\80\94可重构节点要重构。\r
\83°æ\80§å\88 é\99¤ï¼\8cå\88°è¾¾ç©ºç»\93ç\82¹å\88\99忽ç\95¥ï¼\8cæ\89¾å\88°å¯¹åº\94ç»\93ç\82¹å\88\99 `wn[k]--`ã\80\82é\80\92å½\92ç»\93æ\9d\9få\90\8eï¼\8c可重构节点要重构。\r
 \r
 ``` cpp\r
 void Del(int& k, int p) {\r
@@ -100,7 +105,9 @@ void Del(int& k, int p) {
 \r
 ### upper_bound\r
 \r
-返回权值严格大于某值的最小数的名次。每到一个节点,若查找值等于该节点权值,则返回该节点之后的名次。小于则向左走,大于则向右走。\r
+返回权值严格大于某值的最小名次。\r
+\r
+到达空结点则返回 1,因为只有该子树左边的数均小于查找数才会递归至此。找到对应结点,则返回该节点所占据的最后一个名次 + 1。\r
 \r
 ``` cpp\r
 int MyUprBd(int k, int p) {\r
@@ -110,9 +117,12 @@ int MyUprBd(int k, int p) {
  else if(p < w[k]) return MyUprBd(lc[k], p);\r
  else return sd[lc[k]] + wn[k] + MyUprBd(rc[k], p);\r
 }\r
\r
+```\r
+\r
+以下是反义函数,相当于采用 `std::greater<>` 比较,即返回权值严格小于某值的最大名次。查询一个数的排名可以用 `MyUprGrt(rt, x) + 1`。\r
+\r
+``` cpp\r
 int MyUprGrt(int k, int p) {\r
-// 反义的函数,相当于采用 std::greater<> 比较\r
  if(!k) return 0;\r
  else if(w[k] == p && wn[k]) return sd[lc[k]];\r
  else if(w[k] < p) return sd[lc[k]] + wn[k] + MyUprGrt(rc[k], p);\r
@@ -122,7 +132,7 @@ int MyUprGrt(int k, int p) {
 \r
 ### at\r
 \r
-ç»\99å®\9aå\90\8d次ï¼\8cè¿\94å\9b\9e该å\90\8d次ä¸\8aç\9a\84æ\9d\83å\80¼ã\80\82å°\86å\90\8d次ä¸\8eè\8a\82ç\82¹å·¦å­\90æ \91大å°\8få\8f\8aæ\9c¬è\8a\82ç\82¹é\87\8då¤\8d次æ\95°æ¯\94è¾\83ï¼\8cå\90\8cæ ·å°\8fäº\8eå\90\91å·¦ï¼\8c大äº\8eå\90\91å\8f³\r
+ç»\99å®\9aå\90\8d次ï¼\8cè¿\94å\9b\9e该å\90\8d次ä¸\8aç\9a\84æ\9d\83å\80¼ã\80\82å\88°è¾¾ç©ºç»\93ç\82¹è¯´æ\98\8eæ\97 æ­¤å\90\8d次ï¼\8cæ\89¾å\88°å¯¹åº\94ç»\93ç\82¹å\88\99è¿\94å\9b\9eå\85¶æ\9d\83å\80¼\r
 \r
 ``` cpp\r
 int MyAt(int k, int p) {\r
@@ -136,7 +146,7 @@ int MyAt(int k, int p) {
 \r
 ### 前驱后继\r
 \r
-以上两种功的组合\r
+以上两种功能结合即可\r
 \r
 ``` cpp\r
 inline int  MyPre(int k, int p) { return MyAt(k, MyUprGrt(k, p)); }\r