資料結構與演算法-高階搜尋樹
高階搜尋樹
伸展樹
區域性性
-
剛剛被訪問過的元素,極可能在不久之後再次被訪問到
-
將被訪問的下一元素,極有可能就處於不久之前被訪問過的某個元素的附近
因此,只需將剛被訪問的節點,及時地“轉移”至樹根(附近),即可加速後續的操作
逐層伸展
簡易伸展
隨著節點E的逐層上升,兩側子樹的結構也不斷地調整,故這一過程也稱作伸展, 而採用這一調整策略的二叉搜尋樹也因此得名
最壞情況
如此分攤下來,每次訪問平均需要W(n)時間。很遺憾,這一效率不僅遠遠低於AVL樹,而且甚至與原始的二叉搜尋樹的最壞情況相當。且經過以上連續的5次訪問之後,全樹的結構將會復原
雙層伸展
將逐層伸展改為雙層伸展。 具體地,每次都從當前節點v向上追溯兩層(而不是僅一層),並根據其父親p以及祖父g的相對 位置,進行相應的旋轉。
zig/zag
zig/zig
單旋
每經過一次雙層調整操作,節點v都會上升兩層。若v的初始深度depth(v) 為偶數,則最終v將上升至樹根。若depth(v)為奇數,則當v上升至深度為1時,不妨最後再相應 地做一次zig或zag單旋操作。無論如何,經過depth(v)次旋轉後,v最終總能成為樹根。
最壞情況
伸展樹的實現
package com.atguigu.self; /** * @anthor shkstart * @create 2020-08-07 19:54 */ public class Splay<Integer> extends BST<Integer>{ //伸展演算法 protected BinNode<Integer> splay(BinNode<Integer> v){ //v為因最近訪問而需伸展的節點位置 if (v == null) return null; BinNode<Integer> p = null; BinNode<Integer> g = null; while ((p == v.parent) && (g == p.parent)){ //自下而上,反覆對v做雙層伸展 BinNode<Integer> gg = g.parent; //每輪之後v都以原曾祖父(great-grand parent)為父 if (IsLChild(v)){ if (IsLChild(p)){ //zig-zig attachAsLChild(g,p.rc); attachAsLChild(p,v.rc); attachAsRChild(p,g); attachAsRChild(v,p); } else { //zig-zag attachAsLChild(p,v.rc); attachAsRChild(g,v.lc); attachAsLChild(v,g); attachAsRChild(v,p); } } else if (IsRChild(p)){ //zag-zag attachAsRChild(g,p.lc); attachAsRChild(g,v.lc); attachAsLChild(p,g); attachAsLChild(v,p); } else { //zag-zig attachAsRChild(p,v.lc); attachAsLChild(g,v.lc); attachAsRChild(v,g); attachAsLChild(v,p); } if (gg == null) { //若*v原先的曾祖父*gg不存在,則*v現在應為樹根 v.parent = null; } else { //否則,*gg此後應該以*v作為左或右孩子 if (g == gg.lc){ attachAsLChild(gg,v); } else { attachAsRChild(gg,v); } } updateHeight(g); updateHeight(p); updateHeight(v); } //雙層伸展結束時,必有g == NULL,但p可能非空 if (p == v.parent){ //若p果真非空,則額外再做一次單旋 if (IsLChild(v)){ attachAsLChild(p,v.lc); attachAsRChild(v,p); } else { attachAsRChild(p,v.lc); attachAsLChild(v,p); } updateHeight(p); updateHeight(v); } v.parent = null; return v; } //調整之後新樹根應為被伸展的節點,故返回該節點的位置以便上層函式更新樹根 //查詢演算法 public BinNode<Integer> search(Integer e){ BinNode<Integer> p = searchIn(_root,e,_hot = null); _root = splay((p != null) ? p : _hot ); return _root; } //插入演算法 public BinNode<Integer> insert(Integer e){ if (_root == null){ _size++; return _root = new BinNode<Integer>(e); } //處理原樹為空的情況 if (e == search(e).data) return _root; //確認目標節點不存在 _size++; BinNode<Integer> t = _root; //建立新節點。以下調整<=7個指標以完成區域性重構 if ((int)_root.data < (int)e){ //插入新根,以t和t->rc為左、右孩子 t.parent = _root = new BinNode<Integer>(e,null,t,t.rc); if (HasRChild(t)){ t.rc.parent = _root; t.rc = null; } } else { //插入新根,以t->lc和t為左、右孩子 t.parent = _root = new BinNode<Integer>(e,null,t.lc,t); if (HasLChild(t)){ t.lc.parent = _root; t.lc = null; } } updateHeightAbove(t); //更新t及其祖先(實際上只有_root一個)的高度 return _root; //新節點必然置於樹根,返回之 } //刪除演算法 public Boolean remove(Integer e){ //從伸展樹中刪除關鍵碼e if ((_root == null) || (e != search(e).data)) return false; //若樹空或目標不存在,則無法刪除 BinNode<Integer> w = _root; //assert: 經search()後節點e已被伸展至樹根 if (!HasLChild(_root)){ //若無左子樹,則直接刪除 _root = _root.rc; if (_root != null) _root.parent = null; } else if (!HasRChild(_root)){ //若無右子樹,也直接刪除 _root = _root.lc; if (_root != null) _root.parent = null; } else { //若左右子樹同時存在,則 BinNode<Integer> lTree = _root.lc; lTree.parent = null; _root.lc = null; //暫時將左子樹切除 _root = _root.rc; _root.parent = null; //只保留右子樹 search(w.data); //以原樹根為目標,做一次(必定失敗的)查詢 _root.lc = lTree; lTree.parent = _root; // assert: 至此,右子樹中最小節點必伸展至根,且(因無雷同節點)其左子樹必空,於是只需將原左子樹接回原位即可 } if (_root != null){ updateHeight(_root); //此後,若樹非空,則樹根的高度需要更新 } return true; //返回成功標誌,若目標節點存在且被刪除,返回true;否則返回false } public void attachAsLChild(BinNode<Integer> p,BinNode<Integer> lc){ p.lc = lc; if (lc != null){ lc.parent = p; } } public void attachAsRChild(BinNode<Integer> p,BinNode<Integer> lc){ p.rc = rc; if (rc != null){ rc.parent = p; } } }
伸展演算法
//伸展演算法 protected BinNode<Integer> splay(BinNode<Integer> v){ //v為因最近訪問而需伸展的節點位置 if (v == null) return null; BinNode<Integer> p = null; BinNode<Integer> g = null; while ((p == v.parent) && (g == p.parent)){ //自下而上,反覆對v做雙層伸展 BinNode<Integer> gg = g.parent; //每輪之後v都以原曾祖父(great-grand parent)為父 if (IsLChild(v)){ if (IsLChild(p)){ //zig-zig attachAsLChild(g,p.rc); attachAsLChild(p,v.rc); attachAsRChild(p,g); attachAsRChild(v,p); } else { //zig-zag attachAsLChild(p,v.rc); attachAsRChild(g,v.lc); attachAsLChild(v,g); attachAsRChild(v,p); } } else if (IsRChild(p)){ //zag-zag attachAsRChild(g,p.lc); attachAsRChild(g,v.lc); attachAsLChild(p,g); attachAsLChild(v,p); } else { //zag-zig attachAsRChild(p,v.lc); attachAsLChild(g,v.lc); attachAsRChild(v,g); attachAsLChild(v,p); } if (gg == null) { //若*v原先的曾祖父*gg不存在,則*v現在應為樹根 v.parent = null; } else { //否則,*gg此後應該以*v作為左或右孩子 if (g == gg.lc){ attachAsLChild(gg,v); } else { attachAsRChild(gg,v); } } updateHeight(g); updateHeight(p); updateHeight(v); } //雙層伸展結束時,必有g == NULL,但p可能非空 if (p == v.parent){ //若p果真非空,則額外再做一次單旋 if (IsLChild(v)){ attachAsLChild(p,v.lc); attachAsRChild(v,p); } else { attachAsRChild(p,v.lc); attachAsLChild(v,p); } updateHeight(p); updateHeight(v); } v.parent = null; return v; } //調整之後新樹根應為被伸展的節點,故返回該節點的位置以便上層函式更新樹根
查詢演算法
//查詢演算法
public BinNode<Integer> search(Integer e){
BinNode<Integer> p = searchIn(_root,e,_hot = null);
_root = splay((p != null) ? p : _hot );
return _root;
}
插入演算法
//插入演算法
public BinNode<Integer> insert(Integer e){
if (_root == null){
_size++;
return _root = new BinNode<Integer>(e);
}
//處理原樹為空的情況
if (e == search(e).data) return _root;
//確認目標節點不存在
_size++;
BinNode<Integer> t = _root;
//建立新節點。以下調整<=7個指標以完成區域性重構
if ((int)_root.data < (int)e){
//插入新根,以t和t->rc為左、右孩子
t.parent = _root = new BinNode<Integer>(e,null,t,t.rc);
if (HasRChild(t)){
t.rc.parent = _root;
t.rc = null;
}
} else {
//插入新根,以t->lc和t為左、右孩子
t.parent = _root = new BinNode<Integer>(e,null,t.lc,t);
if (HasLChild(t)){
t.lc.parent = _root;
t.lc = null;
}
}
updateHeightAbove(t);
//更新t及其祖先(實際上只有_root一個)的高度
return _root;
//新節點必然置於樹根,返回之
}
刪除演算法
//刪除演算法
public Boolean remove(Integer e){
//從伸展樹中刪除關鍵碼e
if ((_root == null) || (e != search(e).data)) return false;
//若樹空或目標不存在,則無法刪除
BinNode<Integer> w = _root;
//assert: 經search()後節點e已被伸展至樹根
if (!HasLChild(_root)){
//若無左子樹,則直接刪除
_root = _root.rc;
if (_root != null) _root.parent = null;
} else if (!HasRChild(_root)){
//若無右子樹,也直接刪除
_root = _root.lc;
if (_root != null) _root.parent = null;
} else {
//若左右子樹同時存在,則
BinNode<Integer> lTree = _root.lc;
lTree.parent = null;
_root.lc = null;
//暫時將左子樹切除
_root = _root.rc;
_root.parent = null;
//只保留右子樹
search(w.data);
//以原樹根為目標,做一次(必定失敗的)查詢
_root.lc = lTree;
lTree.parent = _root;
// assert: 至此,右子樹中最小節點必伸展至根,且(因無雷同節點)其左子樹必空,於是只需將原左子樹接回原位即可
}
if (_root != null){
updateHeight(_root);
//此後,若樹非空,則樹根的高度需要更新
}
return true;
//返回成功標誌,若目標節點存在且被刪除,返回true;否則返回false
}
評價
B樹
當資料規模大到記憶體已不足以容納時,常規平衡二叉搜尋樹的效率將大打折扣。其原因在於,查詢過程對外存的訪問次數過多。
通過時間成本相對極低的多次記憶體操作,來替代時間成本相對極高的單次外存操作。相應地,需要將通常的二叉搜尋樹,改造為多路搜尋樹
多路平衡查詢
由於各節點的分支數介於[m/2]至m之間,故m階B-樹也稱作([m/2], m)-樹
例如,圖8.12(a)即為一棵由9個內部節點、15個外部節點以及14個關鍵碼組成的4階B-樹,其高度h = 3,其中每個節點包含13個關鍵碼,擁有24個分支。
介面
public class BTNode<T> {
BTNode<T> parent;
//父節點
Vec<T> key;
//關鍵碼向量
Vec<BTNode<T>> child;
//孩子向量(其長度總比key多一)
public BTNode() {
// 建構函式(注意:BTNode只能作為根節點建立,而且初始時有0個關鍵碼和1個空孩子指標)
parent = null;
child.add(0,null);
}
public BTNode(Integer e,BTNode<T> lc,BTNode<T> rc ) {
parent = null;
//作為根節點,而且初始時
key.add(0, (T) e);
//只有一個關鍵碼,以及
child.add(0,lc);
//兩個孩子
child.add(1,rc);
if (lc != null){
lc.parent = this;
}
if (rc != null){
rc.parent = this;
}
}
}
public class BTree<T> extends BTNode<T>{
protected int _size;
//存放的關鍵碼總數
protected int _order;
//B-樹的階次,至少為3——建立時指定,一般不能修改
BTNode<T> _root;
//根節點
BTNode<T> _hot;
//BTree::search()最後訪問的非空(除非樹空)的節點位置
public BTree(int _size, int _order) {
this._size = _size;
this._order = _order;
_root = new BTNode<T>();
}
public BTree() {
_root = new BTNode<T>();
}
public int get_size() {
return _size;
}
public void set_size(int _size) {
this._size = _size;
}
public int get_order() {
return _order;
}
public void set_order(int _order) {
this._order = _order;
}
public BTNode<T> get_root() {
return _root;
}
public void set_root(BTNode<T> _root) {
this._root = _root;
}
public Boolean empty(){
//判空
return _root == null;
}
}
查詢
從根節點開始,通過關鍵碼的比較不斷深入至下一層,直到某一關鍵碼命中(查詢成功),或者到達某一外部節點(查詢失敗)
//查詢演算法
public BTNode<T> search(Integer e){
//在B-樹中查詢關鍵碼e
BTNode<T> v = _root;
//從根節點出發
_hot = null;
while (v != null){
//逐層查詢
int r = v.key.search(e);
//在當前節點中,找到不大於e的最大關鍵碼
if ((0 <= r) && (e == v.key.elementAt(r))) return v;
//成功:在當前節點中命中目標關鍵碼
_hot = v;
v = (BTNode<T>) v.child.elementAt(r +1);
//否則,轉入對應子樹(_hot指向其父)——需做I/O,最費時間
}
//這裡在向量內是二分查詢,但對通常的_order可直接順序查詢
return null;
//失敗:最終抵達外部節點
}
效果:儘管沒有漸進意義上的改進,但相對而言極其耗時的I/O操作的次數,卻已大致縮減為原先的1/log2m。
插入
//插入演算法
public Boolean insert(Integer e){
//將關鍵碼e插入B樹中
BTNode<T> v = search(e);
//確認目標節點不存在
if (v != null) return false;
int r = _hot.key.search( e);
//在節點_hot的有序關鍵碼向量中查詢合適的插入位置
_hot.key.add(r+1, (T) e);
//將新關鍵碼插至對應的位置
_hot.child.add(r+2,null);
//建立一個空子樹指標
_size++;
//更新全樹規模
solveOverflow(_hot);
//如有必要,需做分裂
return true;
}
上溢
//分裂演算法
protected void solveOverflow(BTNode<T> v){
if (_order >= v.child.size()) return;
//遞迴基:當前節點並未上溢
int s = _order/2;
//軸點(此時應有_order = key.size() = child.size() - 1)
BTNode<T> u = new BTNode<T>();
//注意:新節點已有一個空孩子
for (int j = 0;j < _order - s - 1;j++){
//v右側_order-s-1個孩子及關鍵碼分裂為右側節點u
u.child.add(j,v.child.remove(s + 1));
//逐個移動效率低
u.key.add(j,v.key.remove(s + 1));
//此策略可改進
}
u.child.set(_order - s -1,v.child.remove(s+1));
//移動v最靠右的孩子
if (u.child.elementAt(0) != null){
//若u的孩子們非空,則
for (int j = 0;j < _order - s;j++){
//令它們的父節點統一
u.child.elementAt(j).parent = u;
//指向u
}
}
BTNode<T> p = v.parent;
//v當前的父節點p
if (p == null){
//若p空則建立之
_root = p = new BTNode<T>();
p.child.set(0,v);
v.parent = p;
}
int r = 1 + p.key.search((Integer) v.key.elementAt(0));
//p中指向u的指標的秩
p.key.add(r,v.key.remove(s));
//軸點關鍵碼上升
p.child.add(r + 1,u);
u.parent = p;
//新節點u與父節點p互聯
solveOverflow(p);
//上升一層,如有必要則繼續分裂——至多遞迴O(logn)層
}
刪除
c++
template <typename T> //關鍵碼刪除後若節點下溢,則做節點旋轉或合併處理
void BTree<T>::solveUnderflow ( BTNodePosi(T) v ) {
if ( ( _order + 1 ) / 2 <= v->child.size() ) return;
//遞迴基:當前節點並未下溢
BTNodePosi(T) p = v->parent;
if ( !p ) {
//遞迴基:已到根節點,沒有孩子的下限
if ( !v->key.size() && v->child[0] ) {
//但倘若作為樹根的v已不含關鍵碼,卻有(唯一的)非空孩子,則
_root = v->child[0];
_root->parent = NULL;
//這個節點可被跳過
v->child[0] = NULL;
release ( v );
//並因不再有用而被銷燬
}
//整樹高度降低一層
return;
}
Rank r = 0;
while ( p->child[r] != v ) r++;
//確定v是p的第r個孩子——此時v可能不含關鍵碼,故不能通過關鍵碼查詢
//另外,在實現了孩子指標的判等器之後,也可直接呼叫Vector::find()定位
// 情況1:向左兄弟借關鍵碼
if ( 0 < r ) {
//若v不是p的第一個孩子,則
BTNodePosi(T) ls = p->child[r - 1];
//左兄弟必存在
if ( ( _order + 1 ) / 2 < ls->child.size() ) {
//若該兄弟足夠“胖”,則
v->key.insert ( 0, p->key[r - 1] );
//p借出一個關鍵碼給v(作為最小關鍵碼)
p->key[r - 1] = ls->key.remove ( ls->key.size() - 1 );
//ls的最大關鍵碼轉入p
v->child.insert ( 0, ls->child.remove ( ls->child.size() - 1 ) );
//同時ls的最右側孩子過繼給v
if ( v->child[0] ) v->child[0]->parent = v;
//作為v的最左側孩子
return;
//至此,通過右旋已完成當前層(以及所有層)的下溢處理
}
}
//至此,左兄弟要麼為空,要麼太“瘦”
// 情況2:向右兄弟借關鍵碼
if ( p->child.size() - 1 > r ) {
//若v不是p的最後一個孩子,則
BTNodePosi(T) rs = p->child[r + 1];
//右兄弟必存在
if ( ( _order + 1 ) / 2 < rs->child.size() ) {
//若該兄弟足夠“胖”,則
v->key.insert ( v->key.size(), p->key[r] );
//p借出一個關鍵碼給v(作為最大關鍵碼)
p->key[r] = rs->key.remove ( 0 );
//ls的最小關鍵碼轉入p
v->child.insert ( v->child.size(), rs->child.remove ( 0 ) );
//同時rs的最左側孩子過繼給v
if ( v->child[v->child.size() - 1] ) //作為v的最右側孩子
v->child[v->child.size() - 1]->parent = v;
return;
//至此,通過左旋已完成當前層(以及所有層)的下溢處理
}
}
//至此,右兄弟要麼為空,要麼太“瘦”
// 情況3:左、右兄弟要麼為空(但不可能同時),要麼都太“瘦”——合併
if ( 0 < r ) {
//與左兄弟合併
BTNodePosi(T) ls = p->child[r - 1];
//左兄弟必存在
ls->key.insert ( ls->key.size(), p->key.remove ( r - 1 ) );
p->child.remove ( r );
//p的第r - 1個關鍵碼轉入ls,v不再是p的第r個孩子
ls->child.insert ( ls->child.size(), v->child.remove ( 0 ) );
if ( ls->child[ls->child.size() - 1] ) //v的最左側孩子過繼給ls做最右側孩子
ls->child[ls->child.size() - 1]->parent = ls;
while ( !v->key.empty() ) {
//v剩餘的關鍵碼和孩子,依次轉入ls
ls->key.insert ( ls->key.size(), v->key.remove ( 0 ) );
ls->child.insert ( ls->child.size(), v->child.remove ( 0 ) );
if ( ls->child[ls->child.size() - 1] ) ls->child[ls->child.size() - 1]->parent = ls;
}
release ( v );
//釋放v
} else {
//與右兄弟合併
BTNodePosi(T) rs = p->child[r + 1];
//右兄度必存在
rs->key.insert ( 0, p->key.remove ( r ) );
p->child.remove ( r );
//p的第r個關鍵碼轉入rs,v不再是p的第r個孩子
rs->child.insert ( 0, v->child.remove ( v->child.size() - 1 ) );
if ( rs->child[0] ) rs->child[0]->parent = rs;
//v的最左側孩子過繼給ls做最右側孩子
while ( !v->key.empty() ) {
//v剩餘的關鍵碼和孩子,依次轉入rs
rs->key.insert ( 0, v->key.remove ( v->key.size() - 1 ) );
rs->child.insert ( 0, v->child.remove ( v->child.size() - 1 ) );
if ( rs->child[0] ) rs->child[0]->parent = rs;
}
release ( v );
//釋放v
}
solveUnderflow ( p );
//上升一層,如有必要則繼續分裂——至多遞迴O(logn)層
return;
}
java
//刪除演算法
public Boolean remove(Integer e){
BTNode<T> v = search(e);
if (v == null) return false;
int r = v.key.search(e);
if (v.child.elementAt(0) != null){
BTNode<T> u = v.child.elementAt(r + 1);
while (u.child.elementAt(0) != null) u = u.child.elementAt(0);
v.key.setElementAt(u.key.elementAt(0),r);
v = u;
r = 0;
}
v.key.remove(r);
v.child.remove(r + 1);
_size--;
solveUnderflow(v);
return true;
}
//合併演算法
protected void solveUnderflow(BTNode<T> v){
if((_order + 1) / 2 <= v.child.size()) return;
BTNode<T> p = v.parent;
if ( p == null){
if ((v.key.size() == 0) && (v.child.elementAt(0) != null)){
_root = v.child.elementAt(0);
_root.parent = null;
v.child.setElementAt(null,0);
}
return;
}
int r = 0;
while (p.child.elementAt(r) != v) r++;
if (0 < r){
BTNode<T> ls = p.child.elementAt(r - 1);
if ((_order + 1) / 2 < ls.child.size()){
v.key.add(0,p.key.elementAt(r-1));
p.key.setElementAt(ls.key.remove(ls.key.size() - 1),r-1);
v.child.add(0,ls.child.remove(ls.child.size() - 1));
if (v.child.get(0) != null) v.child.get(0).parent = v;
return;
}
}
if (r < (p.child.size() - 1)){
BTNode<T> rs = p.child.elementAt(r + 1);
if ((_order + 1) / 2 < rs.child.size()){
v.key.add(v.key.size(),p.key.elementAt(r));
p.key.setElementAt(rs.key.remove(0),r);
v.child.add(v.child.size(),rs.child.remove(0));
if (v.child.get(v.child.size() - 1) != null) v.child.get(v.child.size() - 1).parent = v;
return;
}
}
if (0 < r){
BTNode<T> ls = p.child.get(r - 1);
ls.key.add(ls.key.size(),p.key.remove(r - 1));
p.child.remove(r);
ls.child.add(ls.child.size(),v.child.remove(0));
if (ls.child.get(ls.child.size() - 1) != null){
ls.child.get(ls.child.size() - 1).parent = ls;
}
while (!v.key.isEmpty()){
ls.key.add(ls.key.size(),v.key.remove(0));
ls.child.add(ls.child.size(),v.child.remove(0));
if (ls.child.get(ls.child.size() - 1) != null){
ls.child.get(ls.child.size() - 1).parent = ls;
}
}
} else {
BTNode<T> rs = p.child.get(r+1);
rs.key.add(0,p.key.remove(r));
p.child.remove(r);
rs.child.add(0,v.child.remove(v.child.size() - 1));
if (rs.child.get(0) != null) rs.child.get(0).parent = rs;
while (!v.key.isEmpty()){
rs.key.add(0,v.key.remove(v.key.size() - 1));
rs.child.add(0,v.child.remove(v.child.size() - 1));
if (rs.child.get(0) != null) rs.child.get(0).parent = rs;
}
}
solveUnderflow(p);
return;
}
紅黑樹
紅黑樹可保證:
在每次插入或刪除操作之後的重平衡過程中,全樹拓撲結構的更新僅涉及常數個節點。儘管最壞
情況下需對多達(logn)個節點重染色,但就分攤意義而言僅為O(1)個
定義
由紅、黑兩色節點組成的二叉搜尋樹若滿足以下條件,即為紅黑樹
(1) 樹根始終為黑色
(2) 外部節點均為黑色
(3) 其餘節點若為紅色,則其孩子節點必為黑色
(4) 從任一外部節點到根節點的沿途,黑節點的數目相等
介面
package com.atguigu.self;
/**
* @anthor shkstart
* @create 2020-08-10 8:46
*/
/*
裡直接沿用了二叉搜尋樹標準的查詢演算法search(),並根據紅黑樹的重平衡規則
與演算法,重寫了insert()和remove()介面;新加的兩個內部功能介面solveDoubleRed()和
solveDoubleBlack(),分別用於在節點插入或刪除之後恢復全樹平衡。
*/
public class RedBlack<T> extends BST<T>{
public BinNode<T> insert(T e){
//插入(重寫)
BinNode<T> x = search(e);
if (x != null) return x;
//確訃目標節點丌存在(留意對_hot的設定)
x = new BinNode<T>(e,_hot,null,null,-1);
//建立紅節點x:以_hot為父,黑高度-1
_size++;
solveDoubleRed(x);
return x;
//經雙紅修正後,即可迒回
}
//無論e是否存在於原樹中,返回時總有x->data == e
public Boolean remove(T e){
//從紅黑樹中刪除關鍵碼e
BinNode<T> x = search(e);
if (x == null) return false;
//確認目標節點存在(留意對_hot的設定)
BinNode<T> r = removeAt(x,_hot);
//實施刪除,_hot某一孩子剛被初除,且被r所指節點(可能是NULL)接替。以下檢查是否失衡,幵做必要調整
if (0 > --_size) return true;
if (_hot == null){
//若剛被刪除的是根節點,則將其置黑,幵更新黑高度
_root.color = RBColor.RB_BLACK;
updateHeight(_root);
return true;
}
if (BlackHeightUpdated(_hot)) return true;
//若所有祖先的黑深度依然平衡,則無需調整
if (IsRed(r)){
//否則,若r為紅,則叧需令其轉黑
r.color = RBColor.RB_BLACK;
r.height++;
return true;
}
solveDoubleBlack(r);
//經雙黑調整後返回
return true;
}
protected void solveDoubleRed(BinNode<T> x){
//雙紅修正
if (IsRoot(x)){
_root.color = RBColor.RB_BLACK;
_root.height++;
return;
}
BinNode<T> p = x.parent;
if (IsBlack(p)) return;
BinNode<T> g = p.parent;
BinNode<T> u = uncle(x);
/**
* 這等效於按中序遍歷次序,對節點x、p和g及其四棵子樹,做一次區域性“3 + 4”重構。
*/
if (IsBlack(u)){
if (IsLChild(x) == IsLChild(p)){
p.color = RBColor.RB_BLACK;
} else {
x.color = RBColor.RB_BLACK;
}
g.color = RBColor.RB_RED;
BinNode<T> gg = g.parent;
BinNode<T> r = g;
if (IsRoot(g)){
r = _root = rotateAt(x);
} else {
if (IsLChild(g)){
r = g.parent.lc = rotateAt(x);
} else {
r = g.parent.rc = rotateAt(x);
}
}
r.parent = gg;
} else {
/**
* 紅黑樹的角度來看,只需將紅節點p和u轉為黑色,黑節點g轉
* 為紅色,x保持紅色。從圖(c')B-樹的角度來看,等效於上溢節點的一次分裂。
*/
p.color = RBColor.RB_BLACK;
p.height++;
u.color = RBColor.RB_BLACK;
u.height++;
if (!IsRoot(g)){
g.color = RBColor.RB_RED;
}
solveDoubleRed(g);
}
}
/**
* 分為三大類共四種情冴:
* BB-1 :2次顏色翻轉,2次黑高度更新,1~2次旋轉,丌再逑弻
* BB-2R:2次顏色翻轉,2次黑高度更新,0次旋轉,丌再逑弻
* BB-2B:1次顏色翻轉,1次黑高度更新,0次旋轉,需要逑弻
* BB-3 :2次顏色翻轉,2次黑高度更新,1次旋轉,轉為BB-1戒BB2R
* @param r
*/
protected void solveDoubleBlack(BinNode<T> r){
//雙黑修正
BinNode<T> p = (r != null) ? r.parent : _hot;
if (p == null) return;
BinNode<T> s = (r == p.lc) ? p.rc:p.lc;
if (IsBlack(s)){
/**
* 下溢節點從父節點借出一個關鍵碼(p),
* 然後父節點從向下溢節點的兄弟節點借出一個關鍵碼(s),調整後的效果如圖(b')。
*/
BinNode<T> t = null;
if (HasLChild(s) && IsRed(s.lc)) t = s.lc; else if (HasRChild(s) && IsRed(s.rc)) t = s.rc;
if (t != null){
RBColor oldColor = p.color;
BinNode<T> b = p;
if (IsRoot(p)){
b = _root = rotateAt(t);
} else {
if (IsLChild(p)){
b = p.parent.lc = rotateAt(t);
} else {
b = p.parent.rc = rotateAt(t);
}
}
b.color = oldColor;
updateHeight(b);
} else {
s.color = RBColor.RB_RED;
s.height--;
if (IsRed(p)){
/**
* 將關鍵碼p取出並下降一層,然後以之為“粘合劑”,
* 將原左、右孩子合併為一個節點。從紅黑樹角度看,這
* 一過程可如圖(b)所示等效地理解為:s和p顏色互換。
*/
p.color = RBColor.RB_BLACK;
} else {
/**
* 將下溢節點與其兄弟合併。從紅黑樹的角 度來看,這一過程可如圖(b)所示等
* 效地理解為:節點s由黑轉紅。
*/
p.height--;
solveDoubleBlack(p);
}
}
} else {
/**
* 令關鍵碼s與p互換顏色,即可得到一棵與之完全等價
* 的B-樹。而從紅黑樹的角度來看,這一轉換對應於以節點p為軸做一
* 次旋轉,並交換節點s與p的顏色,接下來可以套用此前所介紹其它情況的處置方法,繼
* 續並最終完成雙黑修正
*/
s.color = RBColor.RB_BLACK;
p.color = RBColor.RB_RED;
BinNode<T> t = IsLChild(s) ? s.lc : s.rc;
_hot = p;
if (IsRoot(p)){
_root = rotateAt(t);
} else {
if (IsLChild(p)){
p.parent.lc = rotateAt(t);
} else {
p.parent.rc = rotateAt(t);
}
}
solveDoubleBlack(r);
}
}
/**
* 節點黑高度需要更新的情況共分三種:或者左、右孩子的黑高度不等;或者作為紅節點,黑高度與其孩
* 子不相等;或者作為黑節點,黑高度不等於孩子的黑高度加一。
* @param x
* @return
*/
@Override
public int updateHeight(BinNode<T> x){
//更新紅黑樹節點高度
x.height = Math.max(stature(x.lc),stature(x.rc));
//孩子一般黑高度相等,除非出現雙黑
return IsBlack(x) ? x.height++: x.height;
//若當前節點為黑,則計入黑深度
}
/*
檢查節點的顏色以及判定是否需要更新(黑)高度記錄,如此可大大簡化相關演算法的描述
*/
Boolean IsBlack(BinNode<T> p){
//外部節點也看作黑節點
return ((p == null) || (RBColor.RB_BLACK == p.color));
}
Boolean IsRed(BinNode<T> p){
//非黑即紅
return !IsBlack(p);
}
Boolean BlackHeightUpdated(BinNode<T> x){
//RedBlack高度更新條件,紅不變高度,黑增高一個
return (stature(x.lc) == stature(x.rc)) &&
(x.height == (IsRed(x) ? stature(x.lc) : stature(x.lc) + 1));
}
}
插入
雙紅修正RR-1
雙紅修正RR-2
統計
public BinNode<T> insert(T e){
//插入(重寫)
BinNode<T> x = search(e);
if (x != null) return x;
//確訃目標節點丌存在(留意對_hot的設定)
x = new BinNode<T>(e,_hot,null,null,-1);
//建立紅節點x:以_hot為父,黑高度-1
_size++;
solveDoubleRed(x);
return x;
//經雙紅修正後,即可迒回
}
//無論e是否存在於原樹中,返回時總有x->data == e
protected void solveDoubleRed(BinNode<T> x){
//雙紅修正
if (IsRoot(x)){
_root.color = RBColor.RB_BLACK;
_root.height++;
return;
}
BinNode<T> p = x.parent;
if (IsBlack(p)) return;
BinNode<T> g = p.parent;
BinNode<T> u = uncle(x);
/**
* 這等效於按中序遍歷次序,對節點x、p和g及其四棵子樹,做一次區域性“3 + 4”重構。
*/
if (IsBlack(u)){
if (IsLChild(x) == IsLChild(p)){
p.color = RBColor.RB_BLACK;
} else {
x.color = RBColor.RB_BLACK;
}
g.color = RBColor.RB_RED;
BinNode<T> gg = g.parent;
BinNode<T> r = g;
if (IsRoot(g)){
r = _root = rotateAt(x);
} else {
if (IsLChild(g)){
r = g.parent.lc = rotateAt(x);
} else {
r = g.parent.rc = rotateAt(x);
}
}
r.parent = gg;
} else {
/**
* 紅黑樹的角度來看,只需將紅節點p和u轉為黑色,黑節點g轉
* 為紅色,x保持紅色。從圖(c')B-樹的角度來看,等效於上溢節點的一次分裂。
*/
p.color = RBColor.RB_BLACK;
p.height++;
u.color = RBColor.RB_BLACK;
u.height++;
if (!IsRoot(g)){
g.color = RBColor.RB_RED;
}
solveDoubleRed(g);
}
}
刪除
雙黑修正BB-1
雙黑修正BB-2-R
雙黑修正BB-2-B
雙黑修正BB-3
總結
public Boolean remove(T e){
//從紅黑樹中刪除關鍵碼e
BinNode<T> x = search(e);
if (x == null) return false;
//確認目標節點存在(留意對_hot的設定)
BinNode<T> r = removeAt(x,_hot);
//實施刪除,_hot某一孩子剛被初除,且被r所指節點(可能是NULL)接替。以下檢查是否失衡,幵做必要調整
if (0 > --_size) return true;
if (_hot == null){
//若剛被刪除的是根節點,則將其置黑,幵更新黑高度
_root.color = RBColor.RB_BLACK;
updateHeight(_root);
return true;
}
if (BlackHeightUpdated(_hot)) return true;
//若所有祖先的黑深度依然平衡,則無需調整
if (IsRed(r)){
//否則,若r為紅,則叧需令其轉黑
r.color = RBColor.RB_BLACK;
r.height++;
return true;
}
solveDoubleBlack(r);
//經雙黑調整後返回
return true;
}
/**
* 分為三大類共四種情冴:
* BB-1 :2次顏色翻轉,2次黑高度更新,1~2次旋轉,丌再逑弻
* BB-2R:2次顏色翻轉,2次黑高度更新,0次旋轉,丌再逑弻
* BB-2B:1次顏色翻轉,1次黑高度更新,0次旋轉,需要逑弻
* BB-3 :2次顏色翻轉,2次黑高度更新,1次旋轉,轉為BB-1戒BB2R
* @param r
*/
protected void solveDoubleBlack(BinNode<T> r){
//雙黑修正
BinNode<T> p = (r != null) ? r.parent : _hot;
if (p == null) return;
BinNode<T> s = (r == p.lc) ? p.rc:p.lc;
if (IsBlack(s)){
/**
* 下溢節點從父節點借出一個關鍵碼(p),
* 然後父節點從向下溢節點的兄弟節點借出一個關鍵碼(s),調整後的效果如圖(b')。
*/
BinNode<T> t = null;
if (HasLChild(s) && IsRed(s.lc)) t = s.lc; else if (HasRChild(s) && IsRed(s.rc)) t = s.rc;
if (t != null){
RBColor oldColor = p.color;
BinNode<T> b = p;
if (IsRoot(p)){
b = _root = rotateAt(t);
} else {
if (IsLChild(p)){
b = p.parent.lc = rotateAt(t);
} else {
b = p.parent.rc = rotateAt(t);
}
}
b.color = oldColor;
updateHeight(b);
} else {
s.color = RBColor.RB_RED;
s.height--;
if (IsRed(p)){
/**
* 將關鍵碼p取出並下降一層,然後以之為“粘合劑”,
* 將原左、右孩子合併為一個節點。從紅黑樹角度看,這
* 一過程可如圖(b)所示等效地理解為:s和p顏色互換。
*/
p.color = RBColor.RB_BLACK;
} else {
/**
* 將下溢節點與其兄弟合併。從紅黑樹的角 度來看,這一過程可如圖(b)所示等
* 效地理解為:節點s由黑轉紅。
*/
p.height--;
solveDoubleBlack(p);
}
}
} else {
/**
* 令關鍵碼s與p互換顏色,即可得到一棵與之完全等價
* 的B-樹。而從紅黑樹的角度來看,這一轉換對應於以節點p為軸做一
* 次旋轉,並交換節點s與p的顏色,接下來可以套用此前所介紹其它情況的處置方法,繼
* 續並最終完成雙黑修正
*/
s.color = RBColor.RB_BLACK;
p.color = RBColor.RB_RED;
BinNode<T> t = IsLChild(s) ? s.lc : s.rc;
_hot = p;
if (IsRoot(p)){
_root = rotateAt(t);
} else {
if (IsLChild(p)){
p.parent.lc = rotateAt(t);
} else {
p.parent.rc = rotateAt(t);
}
}
solveDoubleBlack(r);
}
}
/**
* 節點黑高度需要更新的情況共分三種:或者左、右孩子的黑高度不等;或者作為紅節點,黑高度與其孩
* 子不相等;或者作為黑節點,黑高度不等於孩子的黑高度加一。
* @param x
* @return
*/
@Override
public int updateHeight(BinNode<T> x){
//更新紅黑樹節點高度
x.height = Math.max(stature(x.lc),stature(x.rc));
//孩子一般黑高度相等,除非出現雙黑
return IsBlack(x) ? x.height++: x.height;
//若當前節點為黑,則計入黑深度
}
kd-樹
其中各節點的關鍵碼可能重複。不過,如此並不致於增加漸進的空間和時間複雜度:每個關鍵碼至多重複一次,總體依然只需O(n)空間;儘管相對於常規二叉搜尋樹僅多出一層,但樹高依然是O(logn)。
查詢的過程中,在每一節點處,至多隻需做一次(而不是兩次)關鍵碼的比較。完全對應於和等價於二分查詢演算法的版本不考慮中間值
-
一維
-
二維