平衡樹

JiCanDuck發表於2024-08-30

平衡樹真的噁心死了!!!!!!好煩啊,又臭又長。

有很多種平衡樹,替罪羊, treap,fhq, slpay。這裡就說 splay, 和 bst 和 替罪羊 了,因為其他我都不會(悲


先說二叉排序樹(二叉搜尋樹), 他的關係就是 左子樹所有節點 < 根節點 < 右子樹所有節點。也就是說,按照中序遍歷可以找到有序序列。

這個時候我們就可以增刪改(刪了再加入)查了!

查詢 透過搜尋,發現如果這個節點等於自己,cnt++, 小於自己往左搜尋,大於自己往右邊搜尋

比如說我們要增加,搜尋,如果發現沒有,就新增節點。

刪除, 找到位置(同上)cnt--。

這個時候就發現一個很嚴重的問題,它的複雜度是樹高,就有可能變成一條鏈,複雜度瞬間變成 \(O(n)\) (泰褲辣)。

平衡樹要來了!

平衡樹還有幾個功能

  1. 要求排名
  2. 按照排名查數
  3. 前驅後繼

替罪羊的思路就此誕生。 如果發現不平衡,就重構。

平衡的定義是非0節點佔全部的 M% 以內,且左節點的個數在 右節點的 M% 一內。

發現不平衡後就把它變成線性的,再按照貪心思路,每次折半重構。

M 在 70 ~ 75 為最佳

程式碼就不貼了,因為很醜。


接下來就是 splay 了

splay的主要思路就是操作的數都要旋轉到樹頂端

這個時候就要提到左旋和右旋了。其實就是傳統的左旋和右旋,還是講一下吧。

如圖是左旋,右旋就是反過來,將x往上面旋,只需要看x是左兒子,還是右兒子就可以了。

然而,無腦的旋轉會被善良的出題人卡掉。

所以,我們就要牽扯到祖宗——爸爸的爸爸叫什麼(

兩種情況,第一種,如下圖

先旋轉自己,再旋轉自己。

第二種,如下圖

先轉父親,再轉自己。

然後就是緊張刺激的寫程式碼環節了!

bool check(int x) {
  return a[a[x].fa].ch[0] != x;
}

寫一個函式判斷是做兒子(0)還是右兒子(1)。

void push_up(int rt) {
  a[rt].size = a[a[rt].ch[0]].size + a[a[rt].ch[1]].size + a[rt].cnt;
}

更新自己,記得加上自己的個數。

void Add(int x, int y, bool f) { 
  a[y].ch[f] = x;
  a[x].fa = y;
}

x接在y的(f ? "右兒子" : "左兒子")上。

正片,開始。


如果x是左兒子,就左旋,否則就是右旋,這樣就可以簡單輕鬆的完成自動的完成旋轉了(dig, zig害人啊)

void rotate(int x) {
  int y = a[x].fa, z = a[y].fa, d = check(x), w = a[x].ch[d ^ 1];
  Add(w, y, d);
  Add(x, z, check(y));
  Add(y, x, d ^ 1);
  push_up(y), push_up(x);
}

單次旋轉函式,將x向上旋轉。

void splay(int x, int p = 0) {
  for (int f = a[x].fa; f = a[x].fa, f != p; rotate(x)) {
    if (a[f].fa != p) {
      if (check(f) == check(x)) {
        rotate(f);
      } else {
        rotate(x);
      }
    }
  }
  if (p == 0) Rt = x;
}

多次旋轉函式,將 x 旋頂。

void find(int x) {
  int p = Rt;
  while (a[p].ch[x > a[p].v] && x != a[p].v) {
    p = a[p].ch[x > a[p].v];
  }
  splay(p);
}

查詢並旋轉到頂。

void insert(int x) {
  int p = Rt, fa = 0;
  while (p && x != a[p].v) {
    fa = p;
    p = a[p].ch[x > a[p].v];
  }
  if (p)
    a[p].cnt++;
  else {
    p = ++cnt;
    if (fa) a[fa].ch[x > a[fa].v] = p;
    a[p] = MakeSplay(x, fa);
  }
  splay(p);
}

找到,如果已經有了就cnt++,否則新增。

int pre_suc(int x, int f) {
  find(x);
  if (!f && a[Rt].v < x || f && a[Rt].v > x) return Rt;
  int p = a[Rt].ch[f];
  while (a[p].ch[f ^ 1]) {
    p = a[p].ch[f ^ 1];
  }
  return p;
}

前驅0, 後驅1。先旋轉到頂,然後查詢。

注意了,刪除不一樣

void remove(int x) {
  int last = pre_suc(x, 0), next = pre_suc(x, 1);
  splay(last), splay(next, last);
  int p = a[next].ch[0];
  if (a[p].cnt > 1) {
    a[p].cnt--;
    splay(p);
  } else {
    a[next].ch[0] = 0;
    push_up(next), push_up(last);
  }
}

先把x的前驅旋轉到頂,再將後繼旋到前驅的後面。那麼後繼的左兒子就是 x,而且 x 沒有兒子。

int rank1(int x) {
  int p = Rt;
  while (1) {
    if (a[p].ch[0] && a[a[p].ch[0]].size >= x) {
      p = a[p].ch[0];
    } else if (x > a[a[p].ch[0]].size + a[p].cnt) {
      x -= a[a[p].ch[0]].size + a[p].cnt;
      p = a[p].ch[1];
    } else {
      return p;
    }
  }
}

查排名為x的數,不多解釋。

總程式碼來咯。

等等等等,還要加入最大值和最小值哦,不然有可能會沒有前驅和後繼哦(親身

#include <iostream>

using namespace std;

const int kMaxN = 1e5 + 5;

int Rt, cnt;

struct Splay {
  int v, fa, cnt, size, ch[2];
} a[kMaxN];

Splay MakeSplay(int v, int fa) {
  Splay P;
  P.v = v, P.fa = fa, P.cnt = P.size = 1, P.ch[0] = P.ch[1] = 0;
  return P;
}

bool check(int x) {
  return a[a[x].fa].ch[0] != x;
}

void push_up(int rt) {
  a[rt].size = a[a[rt].ch[0]].size + a[a[rt].ch[1]].size + a[rt].cnt;
}

void Add(int x, int y, bool f) {
  a[y].ch[f] = x;
  a[x].fa = y;
}

void rotate(int x) {
  int y = a[x].fa, z = a[y].fa, d = check(x), w = a[x].ch[d ^ 1];
  Add(w, y, d);
  Add(x, z, check(y));
  Add(y, x, d ^ 1);
  push_up(y), push_up(x);
}

void splay(int x, int p = 0) {
  for (int f = a[x].fa; f = a[x].fa, f != p; rotate(x)) {
    if (a[f].fa != p) {
      if (check(f) == check(x)) {
        rotate(f);
      } else {
        rotate(x);
      }
    }
  }
  if (p == 0) Rt = x;
}

void find(int x) {
  int p = Rt;
  while (a[p].ch[x > a[p].v] && x != a[p].v) {
    p = a[p].ch[x > a[p].v];
  }
  splay(p);
}

void insert(int x) {
  int p = Rt, fa = 0;
  while (p && x != a[p].v) {
    fa = p;
    p = a[p].ch[x > a[p].v];
  }
  if (p)
    a[p].cnt++;
  else {
    p = ++cnt;
    if (fa) a[fa].ch[x > a[fa].v] = p;
    a[p] = MakeSplay(x, fa);
  }
  splay(p);
}

int pre_suc(int x, int f) {
  find(x);
  if (!f && a[Rt].v < x || f && a[Rt].v > x) return Rt;
  int p = a[Rt].ch[f];
  while (a[p].ch[f ^ 1]) {
    p = a[p].ch[f ^ 1];
  }
  return p;
}

void remove(int x) {
  int last = pre_suc(x, 0), next = pre_suc(x, 1);
  splay(last), splay(next, last);
  int p = a[next].ch[0];
  if (a[p].cnt > 1) {
    a[p].cnt--;
    splay(p);
  } else {
    a[next].ch[0] = 0;
    push_up(next), push_up(last);
  }
}

int rank1(int x) {
  int p = Rt;
  while (1) {
    if (a[p].ch[0] && a[a[p].ch[0]].size >= x) {
      p = a[p].ch[0];
    } else if (x > a[a[p].ch[0]].size + a[p].cnt) {
      x -= a[a[p].ch[0]].size + a[p].cnt;
      p = a[p].ch[1];
    } else {
      return p;
    }
  }
}

int main() {
  insert(0x3f3f3f3f);
  insert(-0x3f3f3f3f);
  int n, op, x;
  for (cin >> n; n; n--) {
    cin >> op >> x;
    switch (op) {
      case 1:
        insert(x);
        break;
      case 2:
        remove(x);
        break;
      case 3:
        find(x), cout << a[a[Rt].ch[0]].size << '\n';
        break;
      case 4:
        cout << a[rank1(x + 1)].v << '\n';
        break;
      case 5:
        cout << a[pre_suc(x, 0)].v << '\n';
        break;
      case 6:
        cout << a[pre_suc(x, 1)].v << '\n';
        break;
    }
  }
  return 0;
}

相關文章