本文將主要講述 BBST 家族的另外一種相對奇特的樹,伸展樹;伸展樹的相較於其餘的 BBST,結構更加簡單,由於伸展樹不須要平衡因子、顏色等信息,他的節點就是 BST 的節點,同時他甚至沒有時刻維護全樹的平衡狀態,卻仍然能保持各項操做達到分攤 O(logn)
;html
伸展樹的結構和二叉樹徹底相同,只是在實現上多了一步伸展;伸展樹蘊含的主要思想就是數據訪問的局部性,也就是說java
這一現象在咱們生活正十分的常見,好比你的電腦,可能有幾百G的資料,可是常常用的可能只有百分之一;因此伸展樹的核心方法就是將剛剛操做過的節點移動到根位置,如圖所示:node
根據以上的描述你可能很快會想到,要想把底部的元素伸展至樹根位置,只須要依次旋轉其父節點便可;這樣左的確能夠,可是在極端狀況下卻可能會使時間複雜度上升至 O(n)
;如圖所示:算法
圖中展現了二叉樹的極端狀況,即退化爲了列表,而後依次訪問末端元素,至全部元素都訪問一遍後,發現又回到了初始狀態,因此這種單層的伸展是萬萬不可取的;ide
而雙層伸展則是根據其父親節點和祖父節點的相對位置,進行相應的旋轉。並分紅如下分三類狀況:3d
如圖所示,當祖孫三代左傾或者右傾時,先旋轉祖父節點再旋轉父節點;code
如圖所示,當祖孫三代左傾、右傾交替時,先旋轉父節點,使其轉化爲同爲左傾或右傾,再旋轉祖父節點;htm
當節點的深度爲奇數時,則最後一次旋轉僅爲單層便可;blog
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
如圖所示,查找成功的時候只須要將目標節點伸展到樹根位置;
如圖所示,查找失敗的時候則須要將失敗的前一個節點(也就是最接近目標的節點),伸展至樹根位置;
@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; // 查找成功 } }
如圖所示,插入也是同理,只需將最後插入的節點伸展至樹根位置便可;
實現
@Override public Node insert(int element) { Node insertNode = super.insert(element); splay(insertNode); return insertNode; }
如圖,通過一次查找後,目標節點已經移動至樹根位置,若此時樹根節點的左孩子或者右孩子爲空,則能夠直接刪除,而後令其後代代替;
如圖,當根節點同時擁有兩個孩子的時候:
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)
;
不能杜絕單次最壞狀況,因此不能用於效率敏感的場合;