數據結構系列(3)之 伸展樹

本文將主要講述 BBST 家族的另外一種相對奇特的樹,伸展樹;伸展樹的相較於其餘的 BBST,結構更加簡單,由於伸展樹不須要平衡因子、顏色等信息,他的節點就是 BST 的節點,同時他甚至沒有時刻維護全樹的平衡狀態,卻仍然能保持各項操做達到分攤 O(logn)html

1、結構概述

伸展樹的結構和二叉樹徹底相同,只是在實現上多了一步伸展;伸展樹蘊含的主要思想就是數據訪問的局部性,也就是說java

  • 剛剛訪問過的節點,極有可能很快會再次訪問;
  • 下一次要訪問的節點,極有可能就在剛被訪問過的節點附近;

這一現象在咱們生活正十分的常見,好比你的電腦,可能有幾百G的資料,可是常常用的可能只有百分之一;因此伸展樹的核心方法就是將剛剛操做過的節點移動到根位置,如圖所示:node

splay

2、單層伸展

根據以上的描述你可能很快會想到,要想把底部的元素伸展至樹根位置,只須要依次旋轉其父節點便可;這樣左的確能夠,可是在極端狀況下卻可能會使時間複雜度上升至 O(n);如圖所示:算法

splaysingle

圖中展現了二叉樹的極端狀況,即退化爲了列表,而後依次訪問末端元素,至全部元素都訪問一遍後,發現又回到了初始狀態,因此這種單層的伸展是萬萬不可取的;ide

3、雙層伸展

雙層伸展則是根據其父親節點和祖父節點的相對位置,進行相應的旋轉。並分紅如下分三類狀況:3d

1. zig-zig/zag-zag

splayzigzig

如圖所示,當祖孫三代左傾或者右傾時,先旋轉祖父節點再旋轉父節點;code


2. zig-zag/zag-zig

splayzigzag

如圖所示,當祖孫三代左傾、右傾交替時,先旋轉父節點,使其轉化爲同爲左傾或右傾,再旋轉祖父節點;htm


3. zig/zag

當節點的深度爲奇數時,則最後一次旋轉僅爲單層便可;blog


4、伸展算法

protected void splay(Node<T> node) {
  // move node up until its root
  while (node != root) {
    // Zig step
    Node<T> parent = node.parent;
    if (parent.equals(root)) {
      if (node.equals(parent.left)) {
        rotateRight(parent);
      } else if (node.equals(parent.right)) {
        rotateLeft(parent);
      }
      break;
    } else {
      Node<T> grandParent = parent.parent;
      boolean isLL = node.equals(parent.left) && parent.equals(grandParent.left);
      boolean isRR = node.equals(parent.right) && parent.equals(grandParent.right);
      boolean isRL = node.equals(parent.right) && parent.equals(grandParent.left);
      boolean isLR = node.equals(parent.left) && parent.equals(grandParent.right);
      // Zig zig step to the right
      if (isLL) {
        rotateRight(grandParent);
        rotateRight(parent);
      }
      // Zig zig step to the left
      else if (isRR) {
        rotateLeft(grandParent);
        rotateLeft(parent);
      }
      // Zig zag steps
      else if (isRL) {
        rotateLeft(parent);
        rotateRight(grandParent);
      } else if (isLR) {
        rotateRight(parent);
        rotateLeft(grandParent);
      }
    }
  }
}

注意:這裏仍然可使用以前在 AVL 樹 中講過的 3+4重構element

5、查找

1. 查找成功

splayget1

如圖所示,查找成功的時候只須要將目標節點伸展到樹根位置;


2. 查找失敗

splayget2

如圖所示,查找失敗的時候則須要將失敗的前一個節點(也就是最接近目標的節點),伸展至樹根位置;


3. 實現

@Override
public Node<T> search(T key) {
  if (key == null) return null;
  Node<T> u = super.binSearch(root, key);
  splay(u);
  return (key.compareTo(u.key) == 0) ? u : null;
}

// 查找最接近key的節點
public Node<T> binSearch(Node<T> v, T key) {
  Node<T> u = v;
  while (true) {
    int comp = key.compareTo(u.getKey());
    if (comp < 0)
      if (u.left != null)
        u = u.left;
      else
        return u;   // 失敗於左節點
    else if (comp > 0)
      if (u.right != null)
        u = u.right;
      else
        return u;  // 失敗於右節點
    else
      return u;    // 查找成功
  }
}


6、插入

如圖所示,插入也是同理,只需將最後插入的節點伸展至樹根位置便可;

splayinsert

實現

@Override
public Node insert(int element) {
  Node insertNode = super.insert(element);
  splay(insertNode);
  return insertNode;
}

7、刪除

1. 單節點刪除

splayremove1

如圖,通過一次查找後,目標節點已經移動至樹根位置,若此時樹根節點的左孩子或者右孩子爲空,則能夠直接刪除,而後令其後代代替;

2. 雙節點刪除

splayremove2

如圖,當根節點同時擁有兩個孩子的時候:

  • 先刪除根節點,元樹分割爲兩個樹
  • 令左子樹爲根,再查找一次目標節點,此時左子樹中最大的位置將伸展到樹根位置;同時他的右孩子必然爲空;
  • 最後將分割出來的右子樹接會樹中便可;

3. 實現

public Node<T> delete2(T key) {
  Node<T> node = search(key);
  if (key.compareTo(node.key) != 0) {
    return node;
  }

  // 查找成功,此時目標節點必然在樹根處
  if (root.left == null) {
    root = root.right;
    if (root != null) {
      root.parent = null;
    }
  } else if (root.right == null) {
    root = root.left;
    if (root != null) {
      root.parent = null;
    }
  } else {
    Node<T> t1 = root.left;
    Node<T> t2 = root.right;

    t1.parent = null;
    t2.parent = null;
    root.left = null;
    root.right = null;
    root = t1;

    // 查找必然失敗,可是左子樹中最大的節點已經伸展至樹根位置,且右子樹必然爲空;(無相同節點)
    search(key);
    root.right = t2;
    t2.parent = root;
  }
  return node;
}

同時這裏也能夠簡單實現,即便用二叉樹的刪除,最後在伸展一次:

@Override
public Node<T> delete(T key) {
  Node<T> deleteNode = super.delete(key);
  splay(deleteNode);
  return deleteNode;
}

總結

  • 無需記錄高度等信息,相對 AVL 樹的實現而言,更簡單一點,同時伸展樹的各項操做均爲 分攤 O(logn)

  • 不能杜絕單次最壞狀況,因此不能用於效率敏感的場合;

相關文章
相關標籤/搜索