1. 程式人生 > 實用技巧 >資料結構與演算法-高階搜尋樹

資料結構與演算法-高階搜尋樹

高階搜尋樹

伸展樹

區域性性

  • 剛剛被訪問過的元素,極可能在不久之後再次被訪問到

  • 將被訪問的下一元素,極有可能就處於不久之前被訪問過的某個元素的附近

因此,只需將剛被訪問的節點,及時地“轉移”至樹根(附近),即可加速後續的操作

逐層伸展

簡易伸展

隨著節點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)。
查詢的過程中,在每一節點處,至多隻需做一次(而不是兩次)關鍵碼的比較。完全對應於和等價於二分查詢演算法的版本不考慮中間值

  • 一維

  • 二維