樹: unordered tree、Binary Search Tree、AVL、B Tree、Red Black Tree

本文所有代碼地址:GitHub

基本概念

引入原因:樹可以兼顧數組和鏈表的搜索和插入特性。

:一個節點有幾個子節點

葉子:無子節點的節點

樹的度:所有節點最大的度

層數:根在第一層

深度:從根到某節點的節點總數

高度:從當前節點到最遠葉子節點的節點總數

某結點的深度+該結點的高度 = 樹的高度

有序樹:將同一個父的子結點編號,確定其次序的樹

無序樹:樹種任意節點的子節點之間沒有順序關係

連通圖:節點之間均有路徑,樹是無環連通圖!

所有的樹都可以用二叉樹表示,只要將長子兄弟表示法的結構旋轉45°

二叉樹性質

  • 對於任一非空二叉樹,如果葉子節點個數爲n0,度爲2的節點個數爲n2,則n0 = n2+1
    • 推導過程:假設度爲1的節點個數爲n1,二叉樹的總結點數爲n = n0+n1+n2, 二叉樹的邊數T = n1+2*n2 = n-1 = n0+n1+n2-1,左右移動得到結論。

二叉樹分類

  • 真二叉樹:所有節點的度要麼爲0,要麼爲2,即沒有隻有一個子節點的節點
  • 滿二叉樹:所有節點的度要麼爲0,要麼爲2,且所有葉子節點都在最後一層。
  • 完全二叉樹:按層填滿,最後一層都從左往右填。假設樹的高度是h,樹的總結點數量是n,h = floor(log2(n))
    • 例題:已知完全二叉樹有768個結點,求葉子節點的個數。答:設葉子節點個數爲n0,度爲1的節點的個數爲n1,度爲2的節點個數爲n2。總結點個數n = n0+n1+n2,而且n0=n2+1,所以n = n1+2*n0-1.若是完全二叉樹,n1是0或者1.n是偶數,所以n1是1,n0=768/2=384。

無序樹的構建和遍歷

BinTree.h

#pragma once
#include <queue>

template <class T>
class BinNode {
public:
	T data;
	BinNode<T> *left, *right;
	BinNode() { left = right = nullptr; }
	BinNode(const T&e, BinNode<T>*l = nullptr, BinNode<T>*r = nullptr) {
		data = e; left = l; right = r;
	}
};

template <class T>
class BinTree
{
protected:
	BinNode<T> *root;
	
public:
	BinTree() { root = nullptr; }
	BinTree(BinNode<T> *r) { root = r; }
	~BinTree() {}

	void visit(BinNode<T> *node) { std::cout << node->data << " "; }

	//前序遍歷
	void preOrder() { preOrder(root); }
	void preOrder(BinNode<T>*curnode) {
		if (curnode)
		{
			visit(curnode);
			preOrder(curnode->left);
			preOrder(curnode->right);
		}
	}

	//中序遍歷
	void midOrder(){ midOrder(root); }
	void midOrder(BinNode<T> *curnode) {
		if (curnode)
		{
			midOrder(curnode->left);
			visit(curnode);
			midOrder(curnode->right);
		}
	}
	
	// 後序遍歷
	void postOrder() { postOrder(root); }
	void postOrder(BinNode<T>*curnode) {
		if (curnode)
		{
			postOrder(curnode->left);
			postOrder(curnode->right);
			visit(curnode);
		}
	}

	// 層序遍歷:申請一個新的隊列,將頭節點壓入隊列
	// 每次從出隊,打印node值,如果左孩子不爲空,左孩子入隊,如果有孩子不爲空,右孩子入隊
	// 直到隊列爲空
	void levelOrder() {
		std::queue<BinNode<T>*> q;
		BinNode<T> * curnode = root;
		while(curnode)
		{
			visit(curnode);
			if (curnode->left) { q.push(curnode->left); }
			if (curnode->right) { q.push(curnode->right); }
			if (q.empty()) return;
			curnode = q.front(); 
			q.pop();
		}
	}
};

測試代碼main.cpp

#include <iostream>
#include "BinTree.h"
using namespace std;

int main() {
	BinNode<char> A('A'), B('B'), C('C'),D('D'), E('E'),F('F'),G('G'),H('H'),I('I');
	A.left = &B; A.right = &C;
	B.left = &D;
	C.left = &E; C.right = &F;
	D.left = &G; D.right = &H;
	E.right = &I;

	BinTree<char> tree(&A);
	cout << "先序遍歷:"; tree.preOrder(); cout << endl;
	cout << "中序遍歷:"; tree.midOrder(); cout << endl;
	cout << "後序遍歷:"; tree.postOrder(); cout << endl;
	cout << "後序遍歷:"; tree.levelOrder(); cout << endl;
	
	system("pause");
	return 0;
}

BST二叉查找樹

二叉查找樹 BST(Binary Search Tree)

  • 每一個元素都有key,而且不允許重複
  • 左子樹的key都小於根結點的key
  • 右子樹的key都大於根節點的key
  • 左右子樹都是二叉查找樹
  • 樹在查找和刪除數據的時候都是二分操作 log(N)
  • 二叉搜索樹的中序遍歷是按照從小到大排序的!!!
  • 缺點:如果數據直接是按照排序後的順序插入,二叉查找樹會退化爲鏈表!!!根本的原因在於二叉查找樹不會自動平衡,無法動態選擇根節點。

BSTree.h

#pragma once
#include<iostream>
enum Boolean { FALSE, TRUE }; //自定義布爾類

template<class T>
class Element {  //可以添加更多的數據,靈活調整
public:
	T key;
};

template <class T> class BSTree; //加入BSTree的聲明,防止在聲明友元時出錯

template<class T>
class BSNode {
	friend class BSTree<T>; //將BSTree作爲友元使之可以訪問私有成員
public:
	Element<T> data;
	BSNode<T> *left;
	BSNode<T> *right;
	void display(int i);
};

template <class T>
class BSTree
{
private:
	BSNode<T> *root;
public:
	BSTree(BSNode<T>*init = nullptr) { root = init; }
	~BSTree() {
		clear(root); root = nullptr;
	}
	void clear(BSNode<T>*node);
	Boolean Insert(const Element<T> &data);
	BSNode<T> *Search(const Element<T> &data);
	BSNode<T> *Search(BSNode<T>*, const Element<T> &data);
	BSNode<T> *IterSearch(const Element<T>&data);
	BSNode<T> *remove(Element<T> &data);
	BSNode<T> *remove(BSNode<T> *node, Element<T> &data);
	void MidOrder();
	void MidOrder(BSNode<T>* node);
	void display();
};

template<class T>
void BSTree<T>::clear(BSNode<T>* node) {
	if (node){
		clear(node->left);
		clear(node->right);
		delete node;
		node = nullptr;
	}
}

template <class T>
void BSNode<T>::display(int i)
{
	std::cout << "Position: " << i; // i是結點的位置,按照層序遍歷往下數
	std::cout << "   DataKey: " << data.key << std::endl;  // 顯示數據
	if (left) left->display(2 * i);
	if (right) right->display(2 * i + 1);
}

template<class T>
Boolean BSTree<T>::Insert(const Element<T> &data) {
	BSNode<T> *p = root;       // 當前節點
	BSNode<T> *q = nullptr;    // 用於指向當前節點的父節點
	//insert之前需要先查找
	while (p) {
		q = p; //每次改變p之前,用q記錄
		if (data.key == p->data.key) return FALSE;//插入失敗
		else if (data.key < p->data.key) { p = p->left; }//向左
		else p = p->right; //向右
	}
	//當循環結束後找到的位置爲q
	p = new BSNode<T>;
	p->left = p->right = nullptr;
	p->data = data;
	if (!root) root = p;
	else if (data.key < q->data.key) q->left = p;
	else q->right = p;
	return TRUE;//表示插入成功
}

template <class T>
BSNode<T> * BSTree<T>::remove(Element<T> &data) {
	root = remove(root, data);
	return root;
}

// 刪除結點 並 返回被刪除的節點
template<class T>
BSNode<T> * BSTree<T>::remove(BSNode<T> *node, Element<T> &data) {
	if (node == nullptr) return node;
	if (data.key < node->data.key)
		node->left = remove(node->left, data);
	else if (data.key > node->data.key)
		node->right = remove(node->right, data);
	else
	{ //找到了待刪除結點
		if (node->left != nullptr &&  node->right != nullptr) //若待刪除結點度爲2
		{
			BSNode<T> * maxleft = node->left; //去找左子樹的最大值替換待刪除結點
			while (maxleft->right != nullptr)
			{
				maxleft = maxleft->right;
			}
			node->data = maxleft->data; // 替換
			node->left = remove(node->left, data); // 刪除最大結點
		}
		else // 待刪除結點的度是0或者1
		{
			BSNode<T> *temp = nullptr;
			if (node->left == nullptr) {
				temp = node->right;
				delete node;
				return temp;
			}
			else if(node->right == nullptr)
			{
				temp = node->left;
				delete node;
				return temp;
			}
		}
	}
	return node;
}


template<class T>
void BSTree<T>::display() {
	if (root) {
		root->display(1);
	}
	else std::cout << "empty tree\n";
}

// 遞歸的查找
template<class T>
BSNode<T>* BSTree<T>::Search(const Element<T> &data) {
	return Search(root, data);
}
template<class T>
BSNode<T>* BSTree<T>::Search(BSNode<T>* node, const Element<T> &data) {
	if (!node) return nullptr;
	if (node->data.key == data.key) return node;
	else if ((node->data.key < data.key))
	{
		return Search(node->right, data);
	}
	else
	{
		return Search(node->left, data);
	}
}

//迭代的查找
template<class T>
BSNode<T>* BSTree<T>::IterSearch(const Element<T>&data) {
	for (BSNode<T>*node = root; node;)
	{
		if (node->data.key == data.key) return node;
		else if (node->data.key < data.key) node = node->right;
		else node = node->left;
	}
}

template <class T>
void BSTree<T>::MidOrder() {
	MidOrder(root);
}

template <class T>
void BSTree<T>::MidOrder(BSNode<T>* node) {
	if (!node) return;
	MidOrder(node->left);
	std::cout << node->data.key << std::endl;
	MidOrder(node->right);
}

main.cpp

#include<iostream>
#include "BSTree.h"
using namespace std;

int main(int argc, char**argv) {
	BSTree<int> tree;
	Element<int> a, b, c, d, e, f, g, h;
	a.key = 5; b.key = 3; c.key = 11;
	d.key = 3; e.key = 15; f.key = 2;
	g.key = 8; h.key = 22;

	tree.Insert(a); tree.Insert(b); tree.Insert(c); tree.Insert(d);
	tree.Insert(e); tree.Insert(f); tree.Insert(g); tree.Insert(h);

	tree.display();
	BSNode<int> *p = tree.Search(g);
	cout << "finding result = " << p->data.key << endl;
	BSNode<int> *p2 = tree.IterSearch(g);
	cout << "finding result = " << p2->data.key << endl;
	cout << "----------------------" << endl;
	tree.MidOrder();
	cout << "delete b=3 " << endl;
	tree.remove(b);
	tree.MidOrder();
	cout << "delete f=2" << endl;
	tree.remove(f);
	tree.MidOrder();
	cout << "delete e=15 " << endl;
	tree.remove(e);
	tree.MidOrder();
	system("pause");
	return 0;
}

AVL平衡二叉查找樹:自平衡

基本:Adelson -Velsky-Landis樹

平衡:節點數量固定時,左右子樹的高度越接近,樹越平衡

平衡因子: 某結點的左右子樹的高度差

AVL樹的特點 : 每個結點的平衡因子只可能是1、0、-1,否則失衡

添加結點,令AVL失衡:最壞的情況:可能會導致所有祖先結點失衡。父節點和非祖先結點都不可能失衡,只有G以上的所有祖先結點會失衡

AVL樹失衡的四種姿態

LL : 右旋g:g的左孩子的左子樹導致失衡

在這裏插入圖片描述

RR : 左旋g:g的右孩子的右子樹導致失衡

在這裏插入圖片描述

LR : 左旋p+右旋g = RR+LL

在這裏插入圖片描述

RL : 右旋p+左旋g = LL+RR

在這裏插入圖片描述

AVL樹的添加操作

一、大致思路
  1. 將插入結點 w 視作標準BST插入
  2. 從w開始,向上移動,找到第一個不平衡結點,g,由此確定p和n。注意:此處向上查找的方法用的是遞歸!!!
  3. 修復g,使之平衡(令其高度和插入之前的高度一致), gpn的排列方式分爲四種情況
    • LL:右旋g
    • LR:左旋p+右旋g
    • RR:左旋g
    • RL:右旋p+左旋g
二、具體實現流程
  • 執行正常的BST插入
  • 當前節點一定是新插入節點的祖先,更新當前節點的高度
  • 獲取當前節點的平衡因子
  • 若平衡因子大於1,則失衡,處於LL或者LR的情況,通過比較插入結點和左子樹根結點的大小可以確定LL和LR
  • 若平衡因子小於-1,則失衡,處於RR或者RL的情況,通過比較插入結點和右子樹根結點的情況可以確定RR或者RL的情況。
三、實際例子:

在這裏插入圖片描述

在這裏插入圖片描述

在這裏插入圖片描述

在這裏插入圖片描述

在這裏插入圖片描述

AVL樹的刪除操作

一、大致思路
  1. 對要刪除結點w進行標準BST刪除

  2. 從w開始,向上移動找到第一個不平衡結點g,則可同時找到對應的結點pn,注意,此處的pn和插入處不同

  3. 調整g使之平衡。分爲四種情況LL、LR、RR、RL.但是即使g平衡之後其祖先也不一定平衡(因爲平衡後的高度不是原來的高度了),這與插入的情況不同,因此還需要往上修復g的祖先,直到根結點爲止,比如這種情況:

在這裏插入圖片描述

二、具體流程
  1. 執行正常的BST刪除
    1. 找到待刪除結點
    2. 若待刪除結點的度爲2,則找到待刪除結點的右子樹的最小值來替換待刪除結點,並刪除掉被替換的節點
    3. 若待刪除結點的爲0或1,刪除待刪除結點
  2. 更新當前節點的高度,這裏的當前節點指的是所有待刪除結點的祖先。注意,對於空節點無法也無需更新高度
  3. 獲取當前節點的平衡因子驗證是否失衡
  4. 若平衡因子>1,則處於左側失衡情況,可以分爲LL和LR,通過獲取左子樹的平衡因子>=0,爲LL,否則爲LR
  5. 若平衡因子<-1,則處於右側失衡情況,可以分爲RR和RL,通過獲取右子樹的平衡因子>0,爲RL,否則爲RR。 注意,平衡因子=0的情況適用於RR和LL。

AVLTree.h

#pragma once
#include<iostream>
template<class T>
class AVLNode {
public:
	T key;
	int height;
	AVLNode * left;
	AVLNode * right;
	AVLNode(T value, AVLNode *l, AVLNode *r) :
		key(value), height(0), left(l), right(r) {}
};

template<class T>
class AVLTree
{
public:
	AVLTree():root(nullptr){}
	AVLTree(AVLNode<T> *r) :root(r) {}
	~AVLTree() { clear(root); root = nullptr; }
	int height();                 // 獲取樹的高度
	AVLNode<T> * insert(T key);
	AVLNode<T> * remove(T key);

	// 刪除AVL樹
	void clear(AVLNode<T> *root);

	void preOrder();
	void midOrder();
	void postOrder();

private:
	AVLNode<T> *root;
	int height(AVLNode<T> *node); // 獲取結點的高度
	int max(int a, int b);
	int getBalance(AVLNode<T> *node); // 獲取平衡因子

	// 自平衡的四種方式
	AVLNode<T> *  LLrotation(AVLNode<T>* node);
	AVLNode<T> *  RRrotation(AVLNode<T>* node);
	AVLNode<T> *  LRrotation(AVLNode<T>* node);
	AVLNode<T> *  RLrotation(AVLNode<T>* node);

	// 插入結點
	AVLNode<T> * insert(AVLNode<T> * root, T key);
	// 刪除節點
	AVLNode<T> * remove(AVLNode<T> *root, T key);

	void preOrder(AVLNode<T> *node);
	void midOrder(AVLNode<T> *node);
	void postOrder(AVLNode<T> *node);
};

/**********************獲取AVL樹的高度*******************/
template<class T> int AVLTree<T>::height(AVLNode<T> *node){
	if (node == nullptr) return 0;
	return node->height;
}
template<class T> int AVLTree<T>::height() {
	return height(root);
}
/**********************獲取AVL結點的平衡因子*******************/
template<class T> int AVLTree<T>::getBalance(AVLNode<T>*node) {
	if (node == nullptr) return 0;
	return height(node->left) - height(node->right);
}
/**********************更新結點比較大小*******************/
template<class T> int AVLTree<T>::max(int a, int b) {
	return (a > b) ? a : b;
}

/************AVL樹失衡的四種姿態對應的自平衡的方式***********/
template<class T> AVLNode<T> * AVLTree<T>::LLrotation(AVLNode<T>* g) {
	// 右旋祖父結點,讓祖父結點下去
	// g:祖父結點(失衡結點) p:父結點 
	AVLNode<T> *p = g->left;
	g->left = p->right;
	p->right = g;
	// 更新結點高度
	g->height = 1+max(height(g->left), height(g->right));
	p->height = 1+max(height(p->left), height(p->right));

	return p;
}
template<class T> AVLNode<T> * AVLTree<T>::RRrotation(AVLNode<T> *g) {
	// 左旋祖父結點,讓祖父結點下去
	// g:祖父結點(失衡結點) p:父結點 
	AVLNode<T> *p = g->right;
	g->right = p->left;
	p->left = g;
	// 更新結點高度
	g->height = 1 + max(height(g->left), height(g->right));
	p->height = 1 + max(height(p->left), height(p->right));

	return p;
}
template<class T> AVLNode<T> * AVLTree<T>::LRrotation(AVLNode<T> *g) {
	// 左旋p + 右旋g = RR + LL
	// g:祖父結點(失衡結點) p:父結點 
	g->left = RRrotation(g->left);
	return LLrotation(g);
}
template<class T> AVLNode<T> * AVLTree<T>::RLrotation(AVLNode<T> *g) {
	// 右旋p + 左旋g = LL + RR
	// g:祖父結點(失衡結點) p:父結點 
	g->right = LLrotation(g->right);
	return RRrotation(g);;
}

/**********************AVL 結點插入*******************/
template<class T> AVLNode<T> * AVLTree<T>::insert(AVLNode<T> * node, T key) {
	// 1. 執行標準BST插入操作
	if (node == nullptr)
		return new AVLNode<T>(key, nullptr, nullptr);
	else if (key < node->key)
		node->left = insert(node->left, key);
	else if (key > node->key)
		node->right = insert(node->right, key);
	else
		return node;// 不允許插入重複結點
	
	// 2. 更新該祖先節點的高度
	node->height = max(height(node->left), height(node->right)) + 1;

	// 3. 獲取平衡因子,並判斷是否失衡
	int balance = getBalance(node);
	// 4. 若失衡,則判斷四種失衡情況
	if (balance > 1 && key < node->left->key) return LLrotation(node);
	if (balance > 1 && key > node->left->key) return LRrotation(node);
	if (balance < -1 && key > node->right->key) return RRrotation(node);
	if (balance < -1 && key < node->right->key) return RLrotation(node);

	return node;
}  

template<class T> AVLNode<T> * AVLTree<T>::insert(T key) {
	root =  insert(root, key);
	return root;
}

/**********************AVL 刪除樹*******************/
template<class T> void AVLTree<T>::clear(AVLNode<T> *node) {
	if (node != nullptr) {
		clear(node->left);
		clear(node->right);
		delete node;
		node = nullptr;
	}
}

/**********************AVL 結點刪除*******************/
template <class T> AVLNode<T> * AVLTree<T>::remove(T key) {
	root = remove(root, key);
	return root;
}

// 刪除結點 並 返回被修正子樹的根結點
template<class T> AVLNode<T> * AVLTree<T>::remove(AVLNode<T> *node, T key) {
	// 1. 執行標準BST刪除
	if (node == nullptr) {
		std::cout << "結點不存在,無法刪除\n";
		return nullptr;
	}
	if (key < node->key){
		node->left = remove(node->left, key);
	}
	else if (key > node->key) {
		node->right = remove(node->right, key);
	}
	else{
		// 找到了要被刪除的節點,開始分類討論
		// 若刪除結點的度爲2
		if (node->left != nullptr && node->right != nullptr) {
			// 找到待刪除結點的右子樹的最小值來替換待刪除結點
			AVLNode<T> * minright = node->right;
			while (minright->left){
				minright = minright->left;
			}
			node->key = minright->key;  // 替換待刪除結點
			node->right = remove(node->right, minright->key); // 刪除被替換的結點
		}
		else // 若待刪除結點的度爲0或1,刪除待刪除結點
		{   
			// 並將node設置成爲非空子節點(度爲1)
			// 或者設爲空指針(度爲0)
			AVLNode<T> * temp = node;
			node = node->left ? node->left : node->right;
			delete temp;
		}
	}

	// 2. 更新當前節點的高度
	if (node == nullptr) return nullptr; //對於空節點無法也無需更新高度
	node->height = 1 + max(height(node->left), height(node->right));

	// 3. 獲取當前節點的平衡因子,驗證是否失衡
	int balance = getBalance(node);
	// 4. 恢復平衡的四種情況
	if (balance > 1) {  //左側失衡情況,可以分爲LR,LL
		if (getBalance(node->left) >= 0) return LLrotation(node);
		else return LRrotation(node);
	}
	if (balance < -1) { // 右側失衡情況,可以分爲RL,RR
		if (getBalance(node->right) > 0) return RLrotation(node);
		else return RRrotation(node);
	}
	return node;
}

/**********************樹的各種遍歷*************************************/
template<class T> void AVLTree<T>::preOrder(AVLNode<T> *node) {
	if (node) {
		std::cout << node->key << " "; preOrder(node->left); preOrder(node->right);
	}
}

template<class T> void AVLTree<T>::midOrder(AVLNode<T> *node) {
	if (node){
		midOrder(node->left); std::cout << node->key << " "; midOrder(node->right);
	}	
}

template<class T> void AVLTree<T>::postOrder(AVLNode<T> *node) {
	if (node) {
		postOrder(node->left); postOrder(node->right); std::cout << node->key<<" ";
	}
}
template<class T> void AVLTree<T>::preOrder() { preOrder(root); }
template<class T> void AVLTree<T>::midOrder() { midOrder(root); }
template<class T> void AVLTree<T>::postOrder() { postOrder(root); }

隨機測試代碼main.cpp

#include<iostream>
#include<time.h>
#include<vector>
#include "AVLTree.h"
using namespace std;

int main(int argc, char**argv) {
	srand((int)time(0));
	AVLTree<int> tree;
	int times = rand()%20; //隨機生成樹的節點數量
	int copytimes = times;
	vector<int> arr;
	while (copytimes--)
	{
		int val = rand() % 100;
		arr.push_back(val);
		tree.insert(val); // 隨機插入任意節點的大小,範圍定在0~99之間
	}
	tree.midOrder();
	cout << "**************" << endl;
	while (times--)
	{
		int todel = rand() % arr.size();
		cout << "\n try to del " << arr[todel] << endl;
		tree.remove(arr[todel]);
		cout << " the result is ";
		tree.midOrder();
	}
	cout << "\n***********" << endl;
	system("pause");
	return 0;
} 

B樹:自平衡

B樹是一種平衡的多路搜索樹,(多叉樹),多用於文件系統,數據庫的實現,可以減少磁盤訪問次數。

一、爲什麼要有B樹?

磁盤結構

一個圓可以根據扇區劃分,也可以根據圓環軌道劃分。塊id = 扇形id + 軌道id。塊有固定大小,比如爲512Bytes,則可以通過塊中的下標定義到任何一個字節的位置。

在這裏插入圖片描述
在這裏插入圖片描述

假設數據庫內的一條信息是128bytes,則一個大小爲512bytes的塊可以存儲4條信息。100條數據庫信息一共需要25個塊。

在這裏插入圖片描述

在查找信息的時候,必須要遍歷25個塊。這個時間是可以減小的,只要給當前數據庫增加一個索引表,表內存放id和指針信息,假設每條信息在索引表中的大小爲16,則一個塊一共可以存儲512/16 = 32條信息,那麼100條信息需要100/32~=4個塊信息來存儲索引表,算上需要訪問的信息條一共需要訪問4+1個塊。 如果擴大記錄條數爲1000,索引表佔據的塊數會更多,變爲需要40個塊,這40個塊的索引會需要很多時間,則可以通過創建索引表的索引表來加速查找。比如創建新的索引表,每個索引表中存放每個塊的首id和地址,則只需要2個塊就存儲檢索標的檢索表(也就是稀疏表)。

在這裏插入圖片描述

綜上,可以通過多層索引來加速檢索速度,如圖所示,這就是B樹和B+樹的基礎 。 B樹中,所有的結點都有多路指針指向數據庫中的元素

在這裏插入圖片描述

M-way搜索樹

一個結點中可以有n個key,每個結點可以有n+1個數量的子節點 。m-way搜索樹用m=n+1表示去往子節點的路數。

在這裏插入圖片描述

M-way搜索樹存在的問題,比如對於10-way搜索樹,有10,20,30三個key,當插入這三個節點時,很可能會出現如下這種糟糕情況,因爲M-way搜索樹沒有插入規則。B樹就是加上了這些規則的M-way搜索樹

在這裏插入圖片描述
比如規定了除根結點外的所有非葉節點要求必須填滿一半,才能創建孩子節點,這樣就可以消除m-ways樹的問題

二、B樹的規則

B樹的規則有兩個版本:規定最小度的版本從keys角度來考慮,數據清晰些;規定階數的版本從nodes角度來考慮,易於理解些。

規定B樹的最小度爲 t :從keys角度來考慮

  • 所有結點最多有 2t-1 個keys,也就是最多有 2t 個childnodes.

  • 非根結點至少有 t-1 個keys,也就是至少有 t 個childnodes. 若樹非空,根至少有1個keys.

  • 一個結點的childnodes數量 = keys數量 + 1

規定B樹的階數爲 m : 從nodes角度來考慮

  • 階數 m 表示每個結點最多有 m 個childnodes

  • 非根結點的childnodes爲k, 則m/2 <= k <= m(要求必須填滿一半,才能創建孩子節點,也就是非葉節點至少有ceil(m/2)-1個keys)

  • 根結點最少有2個childnodes(如果不是葉子節點)

  • 所有葉子都在同一層

  • 從下至上插入

舉個例子: 當 t = 2 :所有結點最多可以有4個孩子,非根結點至少有2個孩子,因此非根結點的孩子數有2,3,4三種可能,被稱爲2-3-4樹

2.1 B樹的查找

從根結點開始查找 k ,設置一個下標 i=0,通過比較大小,確定 k 在該結點中的下標範圍,設size爲當前節點擁有的keys數量。

  • 如果 i < size,並且能找到 k== node->keys[i], 則找到
  • 否則,如果該結點爲葉子節點,那就是沒有
  • 再否則,k就處在keys[i-1]和keys[i],相鄰兩個keys之間,需要繼續遞歸向下遍歷。
2.2 B樹的遍歷

從最左邊的孩子開始,遞歸地打印最左邊的孩子,然後對剩餘的childnodes和keys重複相同的過程。最後遞歸打印最右邊的孩子

2.3 B樹的插入
方法一:

類似BST,從根開始向下遍歷到葉節點。到達leaf後,將key插入到childnodes。問題在於childnodes中可容納的keys是固定的,在插入新的key時必須要保證childnodes有足夠的空間可以容納key,這可以通過在插入前分裂結點來實現。

因此,要插入key,則需要從root開始遍歷到 leaf, 在遍歷時檢查該node是否滿了,如果滿員,就分裂結點來創造空間。缺點是:可能會進行不必要的拆分

例子

在這裏插入圖片描述

方法二:
  • 找到應添加X的葉節點

  • 將X添加到已有值之間的適當位置。作爲葉節點,無需擔心子樹

  • 若添加X之後,節點中的keys數量<=2*t-1,則沒有問題。否則節點溢出,開始分裂結點,將結點分爲三部分

    • 原結點存放左邊 t-1 個 keys
    • 新建結點存放右邊 t-1 個 keys
    • 中間key被放到父節點上
  • 判讀父節點是否溢出,直到沒有溢出發生

例子

創建4階B樹,結點爲10,20,40,50,60,70,80,30,35,5,15

在這裏插入圖片描述

簡單地說,就是在放滿的情況下,抽出一個放到上面,其他數據分爲兩塊。存40的結點是在50插入之後創建的,所以說B樹是從下往上建立的

繼續加60,70,加到80有了變化

在這裏插入圖片描述

繼續加30,加到35有了變化

在這裏插入圖片描述

繼續加5,加到15有了變化

在這裏插入圖片描述

2.4 B樹的刪除:
分類討論
  • 如果被刪除的key在leaf中

    • 若leaf中的 n > t-1,直接刪除

    • 若leaf中的 n = t-1,

      • “借”:若兩個相鄰左右兄弟結點中有一個是n>t-1的,就從兄弟結點借,借的方式是當前節點從父節點拿一個,父節點從兄弟結點拿一個
      • “合併”:若兩個相鄰左右兄弟都是n = t -1,則將該leaf、左兄弟
        (或有兄弟)、父中的中間key合併成一個結點,並刪除key
      • “單邊”:對於邊緣上的leaf,則只考慮一邊結點是否滿足n>t-1的條件,滿足:借,不滿足:合併

      注意:在合併之後,父節點會少一個key,若合併後父節點n<t-1,需要對父節點進行重複借或者合併的操作

  • 如果被刪除的key在中間結點中

    • 若前驅結點爲根的子樹中找到最大值,或者從後繼結點爲根的子樹中找到了最小值,並且這個值所在葉子結點的 n>t-1,則這個值和key交換,然後直接刪除key
    • 若前驅和後繼找到的葉子結點都滿足 n = t-1,則合併前驅和後繼,並刪除key
  • 在實際的操作中,先給讓結點膨脹在刪除以保證平衡。


例子

原圖
在這裏插入圖片描述


若leaf中的n > t-1,直接刪除

刪除65:n >t-1, 直接刪除

在這裏插入圖片描述


若leaf中的 n = t-1, 若左右兄弟結點中有一個是n>t-1的,就從兄弟結點借,借的方式是當前節點從父節點拿一個,父節點從兄弟結點拿一個。

刪除23,n = t-1, left sibling n > t-1,從 left sibling 拿一個

刪除72,n = t-1, right sibling n > t-1,從right sibling 拿一個

在這裏插入圖片描述


若leaf中的n = t-1, 若左右兩兄弟都是n = t-1,則任意選擇一個兄弟與之合併,還有父節點中介於兩個結點之間的那個key,一起合併,然後刪除待刪除的節點

刪除64, n=t-1,left&right n = t-1, merge(this,leftsibling), delete key

在這裏插入圖片描述


leaf位於父節點邊緣的情況處理也類似,只是借或者合併的選擇只能是單邊而已。如果合併之後父節點的n<t-1則需要對父節點進行重複操作。

在這裏插入圖片描述


如果被刪除的key在中間結點中

  • 若前驅結點爲根的子樹中找到最大值,或者從後繼結點爲根的子樹中找到了最小值,並且這個值所在葉子結點的 n>t-1,則這個值和key交換,然後直接刪除key

    刪除 70 ,95,對應的前驅和後繼n > t-1, 則交換並直接刪除

在這裏插入圖片描述

  • 若前驅和後繼找到的葉子結點都滿足 n = t-1,則合併前驅和後繼,並刪除key

    刪除77,前驅和後繼n = t-1
    在這裏插入圖片描述

    刪除80,前驅最大79 n>t-1

在這裏插入圖片描述

刪100,左右n = t-1, 合併完之後需要討論父節點是否符合規則

在這裏插入圖片描述


塌陷情況:

刪6,27,60,16,之後刪50

在這裏插入圖片描述

實現方法

在這裏插入圖片描述

三、 B樹的c++實現

動畫:https://www.cs.usfca.edu/~galles/visualization/BTree.html

在這裏插入圖片描述

3.1 BTree.h
#pragma once
#include<iostream>
// 規定B樹的最小度爲m
template <class T,int m = 3>
class BNode {
public:
	int n;       // 當前存儲的key數量
	T keys[2*m-1];  // 存keys數組的指針
	BNode<T>* childnodes[2 * m]; // 存childpointers
	bool isleaf;    // 若結點爲葉子節點則爲真,否則爲假

	BNode(int _n=0,bool _isleaf=true):n(_n),isleaf(_isleaf){
		for (int i = 0; i < 2*m; i++)
		{
			childnodes[i] = nullptr;
		}
	}	
};

template <class T,int m=3>
class BTree
{
public:
	BNode<T,m> * root;
	void traverse(BNode<T,m>* node); // 遍歷
public:
	BTree():root(nullptr) {}
	BNode<T, m>* search(BNode<T, m>* node, T key);
	// 插入函數
	void splitchild(BNode<T, m>* parentnode, int index);
	void insert(T key);
	void insert_nonfull(BNode<T, m>*node, T key);

	void traverse(); // 遍歷

	// 刪除函數
	void remove(const T& key);
	void remove(BNode<T,m>*node, const T & key);
	int findKey(BNode<T, m>*node, const T & key);
	bool leakfill(BNode<T, m>*node, int id);
	void removeFromLeaf(BNode<T, m>*node,int id);
	void removeFromNonLeaf(BNode<T, m>*node, int id);
	BNode<T, m>* getPred(BNode<T,m>*node,int id); // 獲得前驅
	BNode<T, m>* getSucc(BNode<T, m>*node, int id); // 獲得後繼
	void borrowFromPrev(BNode<T, m>*node, int id);
	void borrowFromNext(BNode<T, m>*node, int id);
	void merge(BNode<T, m>*node, int id);
};



/*****************************B樹的搜索*********************************/
template<class T,int m>
BNode<T, m>* BTree<T,m>::search(BNode<T, m>* node, T key) {
	if (node == nullptr) return nullptr;
	// 去找key在當前節點中的哪個範圍,對每個內部結點做n+1路的分支選擇
	int i = 0;  // 令下標從0開始
	while (i < node->n && key > node->keys[i]) { i++; } 

	//分類討論下標的情況
	if ( key == node->keys[i]) return node; // 1. 找到了,返回地址
	if (node->isleaf) return nullptr;       // 2. 未找到,且是葉子,徹底沒找到,返回空
	return search(node->childnodes[i], key);// 3. 未找到,且非葉,繼續往下找
}

/*****************************B樹的插入*********************************/
template <class T,int m> 
void BTree<T, m>::splitchild(BNode<T, m>*node, int i) {
	// 功能: 分裂node的滿子節點,並調整中間key到node
	// node:未滿的節點 i: node的滿子節點L的下標
	// m:最小度
	// L : node中下標爲i的全滿childnodes 全滿也就是n = 2*t-1,下標從0~2*t-2
	// L中共有2*t-1個keys,留左邊t-1個keys給L,出右邊t-1個keys給R,中間key給node
	// R : L分裂出的新結點,n將被設爲t-1,下標從0~t-2
	BNode<T,m> * R = new BNode<T>();  
	BNode<T,m> * L = node->childnodes[i]; // 原來的滿子節點
	

	// 1. 從L中拷貝keys和nodes構造R
	R->isleaf = L->isleaf;
	R->n = m - 1; 
	for (int j = 0; j < m-1; j++) 
		R->keys[j] = L->keys[m + j];
	if (L->isleaf==false){
		for (int j = 0; j < m; j++)
			R->childnodes[j] = L->childnodes[j + m];
	}
	L->n = m - 1; // 修改L中的keys數量
	
	// 2.將中間結點提升爲父節點
	for (int j = node->n ; j > i; j--)	{
		// 從下標爲x->n的尾結點開始,i之前的所有指針都不動,
		//i之後的所有指針都往後移動,空出i+1的指針位置鏈接新的結點
		node->childnodes[j+1] = node->childnodes[j];
	}
	node->childnodes[i + 1] = R;

	for (int j = node->n;j>i;j--) { 
		//key挪動的次數和childnodes移動次數相同
		node->keys[j] = node->keys[j-1];
	}
	node->keys[i] = L->keys[m - 1]; //將L的中間值key放到父節點相應空位上

	node->n++; // 更新node內keys數量
}

template<class T, int m>
void BTree<T, m>::insert_nonfull(BNode<T,m>*node,T key) {
	int i = node->n - 1; 
	if (node->isleaf)
	{  // 插入到未滿的葉子節點,直接騰位置插入即可
		while (i>=0 && key < node->keys[i])
		{
			node->keys[i+1] = node->keys[i];
			i--;
		}
		node->keys[i+1] = key;
		node->n++;
	}
	else
	{
		// 插入到未滿的非葉結點,需要進入到childnodes內部進行插入
		//找到key處在keys[i]和keys[i+1]之間的childnodes[i+1]中
		while (i >= 0 && key < node->keys[i]) --i;
		if (node->childnodes[i+1]->n == 2*m-1)
		{	// 若滿則分裂,node會在keys[i+1]位置被插入一個新的值
			splitchild(node, i + 1);
			// 根據新值的大小來判斷key處在split後的左邊node[i+1]還是右邊node[i+2]
			if (node->keys[i + 1] < key) i++;
		}
		insert_nonfull(node->childnodes[i+1], key);
		// 進入到子節點內部繼續插入,實質是遞歸到了葉子節點
	}
}

template <class T,int m>
void BTree<T, m>::insert(T key) {
	// 空樹的插入情況
	if (root == nullptr) {
		root = new BNode<T, m>();
		root->keys[0] = key;
		root->n = 1;
	}
	// 若樹非空
	else {
		if (root->n == 2*m-1) // 若根結點滿員,需要手動創造根結點的父節點來調用splitchild
		{	BNode<T, m> *oldroot = root;
			BNode<T, m> *newroot = new BNode<T, m>();
			root = newroot;
			newroot->childnodes[0] = oldroot; 
			// 0是因爲根結點的父節點是空的
			splitchild(newroot, 0); //分裂oldroot,之後newroot上會多一個數
			root->isleaf = false;
			insert_nonfull(newroot, key); // 重新對根結點非滿的新樹進行插入
		}
		else insert_nonfull(root, key); // 去找葉子結點
	}
}

/*****************************B樹的遍歷*********************************/
template<class T, int m> void BTree<T, m>::traverse() {
	traverse(root);
}

template<class T,int m>
void  BTree<T, m>::traverse(BNode<T,m>* node) {
	if (node == nullptr) return;
	int i = 0;
	for (; i < node->n; i++)
	{
		if (node->isleaf==false) // 若非葉子節點,則在打印之前需要先打印當前key左邊的childnodes
		{
			traverse(node->childnodes[i]);
		}
		std::cout << " " << node->keys[i]; //再打印i所在的key
	}
	// 對於最右邊的childnodes進行單獨打印
	if (node->childnodes[i] != nullptr) traverse(node->childnodes[i]);
}


/*****************************B樹的刪除*********************************/
template<class T, int m>
void BTree<T, m>::remove(const T&key) {
	remove(root, key);
}
// 找到 node->keys[i]>=key的i
template<class T, int m>
int BTree<T, m>::findKey(BNode<T, m>*node, const T &key) {
	int id = 0;
	while (id<node->n && key>node->keys[id]) id++;
	return id;
}

template<class T, int m>
void BTree<T, m>::remove(BNode<T, m>*node, const T &key) {
	if (node == nullptr) return;
	int id = findKey(node, key);
	if (id < node->n && node->keys[id] == key)
	{   // 對於找到結點的情況
		if (node->isleaf)
			removeFromLeaf(node, id);
		else
			removeFromNonLeaf(node, id);
	}
	else // 對於還未找到結點的情況
	{
		if (node->isleaf) { //若爲葉子則這個key不在樹中
			std::cout << key << " does not exist\n";
			return;
		}
		// 走下去的節點很有可能會失衡,所以要先讓它膨脹一波,
		// 但是要注意的是膨脹之後,id會發生變化,因此需要對可能出現的膨脹情況做分析
		// key的id要發生變化,一定是因爲node中下標發生了變化,
		// 在leakfill中borrow不會影響node的下標變化,只有merge可能會改變下標
		// 結點和右邊結點合併的時候,id不會發生變化
		// 只有最右邊的節點,在和左邊結點發生合併後,在node中的下標id會減1

		bool flag = false; // 默認不發生合併的情況
		if (node->childnodes[id]->n < m)
			flag = leakfill(node, id);//如果發生了合併
		if (flag)
			remove(node->childnodes[id - 1], key);
		else
			remove(node->childnodes[id], key);
	}
}

// 則調用該函數,來防止結點下溢
// 返回真,說明是最右結點和其左兄弟發生了合併,檢索id會-1
template<class T, int m>
bool BTree<T, m>::leakfill(BNode<T, m>*node, int id) {
	// 第一種:找左兄弟結點借
	if (id != 0 && node->childnodes[id - 1]->n >= m)
		borrowFromPrev(node, id);
	// 第二種:找右兄弟結點借
	else if (id != node->n && node->childnodes[id + 1]->n >= m)
		borrowFromNext(node, id);
	// 第三種,該childnodes[id]和兄弟結點合併
	else
	{
		if (id < node->n)
			merge(node, id); //若非最右結點,則和右節點合併
		else {
			merge(node, id - 1); // 否則和左節點合併
			return true;
		}
	}
	return false;
}

template<class T, int m>
void BTree<T, m>::removeFromLeaf(BNode<T, m>*node, int id) {
	for (int i = id + 1; i < node->n; i++)
		node->keys[i - 1] = node->keys[i];
	node->n--;
}

template<class T, int m>
void BTree<T, m>::removeFromNonLeaf(BNode<T, m>*node, int id) {
	// 當前爲node->keys[id],則前驅爲node->childnodes[id],後繼爲node->childnodes[id+1]
	// 判斷前驅是否滿足n>t-1,是就替換換然後刪除
	T key = node->keys[id];
	if (node->childnodes[id]->n > m - 1)
	{
		BNode<T, m> *pred = getPred(node, id);
		node->keys[id] = pred->keys[pred->n - 1];
		remove(pred, pred->keys[pred->n - 1]);
	}// 若後繼滿足n>t-1,是就替換然後刪除
	else if (node->childnodes[id + 1]->n > m - 1)
	{
		BNode<T, m> *succ = getSucc(node, id);
		node->keys[id] = succ->keys[0];
		remove(succ, succ->keys[0]);
	}
	else //若前驅和後繼都滿足n = t-1,則合併前驅後繼和父key
	{
		merge(node, id);
		remove(node->childnodes[id], key);
	}
}

// 獲得node->keys[id]的前驅最大值所在的節點
template<class T, int m>
BNode<T, m>* BTree<T, m>::getPred(BNode<T, m>*node, int id) {
	BNode<T, m> *cur = node->childnodes[id];  // cur爲node->keys[id]的前驅孩子
	//不斷往右找到葉子節點中的最大值
	while (!cur->isleaf) {
		cur = cur->childnodes[cur->n];
	}
	return cur;
}
// 獲取node->keys[id]的後繼最小值所在的節點
template<class T, int m>
BNode<T, m>* BTree<T, m>::getSucc(BNode<T, m>*node, int id) {
	BNode<T, m> *cur = node->childnodes[id + 1]; // cur爲node->keys[id]的後繼孩子
	// 不斷往左找到葉子節點中的最小值
	while (!cur->isleaf)
	{
		cur = cur->childnodes[0];
	}
	return cur;
}


/****node->childnodes[ id ]->n  < t-1  向兄弟結點借,或者和他們合併******/
template<class T, int m>
void BTree<T, m>::borrowFromPrev(BNode<T, m>*node, int id) {
	// node->childnodes[ id ]->n  < t-1 
	// node->childnodes[id-1]->n  > t-1
	// [id]向node拿一個,node向[id-1]拿一個
	BNode<T, m> *child_i = node->childnodes[id];
	BNode<T, m> *sibling = node->childnodes[id - 1];

	//騰位置給node中拿來的key
	for (int i = child_i->n; i > 0; i--) {
		child_i->keys[i] = child_i->keys[i - 1];
	}
	//非葉節點,child的指針也需要隨之移動
	if (!child_i->isleaf) {
		for (int i = node->childnodes[id]->n; i >= 0; i--) {

			child_i->childnodes[i + 1] = child_i->childnodes[i];
		}
	}
	child_i->keys[0] = node->keys[id - 1];

	//設sibling的最右邊childnodes爲child_i的第一個childnodes
	if (!child_i->isleaf) {
		child_i->childnodes[0] = sibling->childnodes[sibling->n];
	}

	node->keys[id - 1] = sibling->keys[sibling->n - 1];

	child_i->n++;
	sibling->n--;
}
template<class T, int m>
void BTree<T, m>::borrowFromNext(BNode<T, m>*node, int id) {
	// node->childnodes[ id ]->n  < t-1 
	// node->childnodes[id+1]->n  > t-1
	// [id]向node拿一個,node向[id+1]拿一個
	BNode<T, m>*child_i = node->childnodes[id];
	BNode<T, m>*sibling = node->childnodes[id + 1];

	child_i->keys[child_i->n] = node->keys[id];
	if (!child_i->isleaf)
	{
		child_i->childnodes[child_i->n + 1] = sibling->childnodes[0];
	}
	node->keys[id] = sibling->keys[0];
	for (int i = 0; i < sibling->n - 1; i++)
	{
		sibling->keys[i] = sibling->keys[i + 1];
	}
	if (!sibling->isleaf)
	{
		for (int i = 0; i <= sibling->n - 1; i++)
		{
			sibling->childnodes[i] = sibling->childnodes[i + 1];
		}
	}
	child_i->n++;
	sibling->n--;
}

template<class T, int m>
void BTree<T, m>::merge(BNode<T, m>*node, int id) {
	// node->childnodes[id] n = t-1
	// node->childnodes[id+1] n = t-1
	//合併node->key[id],node->childnodes[id]和node->childnodes[id+1]
	BNode<T, m>* child = node->childnodes[id];
	BNode<T, m>* sibling = node->childnodes[id + 1];

	// 合併child\sibling\node的key到child中
	child->keys[m - 1] = node->keys[id];
	for (int i = 0; i < sibling->n; i++)
	{
		child->keys[m + i] = sibling->keys[i];
	}
	//對於非孩子節點,還需要合併孩子結點
	if (!child->isleaf) {
		for (int i = 0; i < sibling->n; i++) {
			//合併三者的nodes
			child->childnodes[m + i] = sibling->childnodes[i];
		}
	}
	// 把node->keys[id]之後的位置往前移
	for (int i = id; i < node->n - 1; i++)
	{
		node->keys[i] = node->keys[i + 1];
		node->childnodes[i + 1] = node->childnodes[i + 2];
	}

	child->n += sibling->n + 1;
	node->n--;
	delete sibling;
    sibling = nullptr;
}
3.2 main.cpp
#include <iostream>
#include "BTree.h"
using namespace std;

int main(int argc, char ** argv) {
	const int m = 3;
	BTree<int,m> t;
	t.insert(1);	t.insert(3);	t.insert(7);	t.insert(10);	
	t.insert(11);	t.insert(13);	t.insert(14);	t.insert(15);	
	t.insert(18);	t.insert(16);	t.insert(19);	t.insert(24);
	t.insert(25);	t.insert(26);	t.insert(21);	t.insert(4);
	t.insert(5);	t.insert(20);	t.insert(22);	t.insert(2);
	t.insert(17);	t.insert(12);	t.insert(6);
	cout << "遍歷結果:  ";  t.traverse();  cout << endl;
	t.remove(16);
	cout << "遍歷結果:  ";  t.traverse();  cout << endl;
	t.remove(13);
	cout << "遍歷結果:  ";  t.traverse();  cout << endl;
	system("pause");
}

四、 B樹的升級版本

B+樹:

B樹的結點有數據信息,

B+樹的結點只引路,所有數據信息都被放置在葉子節點中。

B樹的葉子節點存放的指針爲nullptr,

B+樹的葉子節點無指向孩子節點的指針,但是有順序訪問指針。每個葉子結點都有指向相鄰葉子結點的指針,在底層形成了線性的數據結構。

因爲結點內部不存儲數據,一個結點可以存儲更多的key(一個塊中可以存放更多的記錄),因此成爲數據庫索引的首選(數據庫索引的索引肯定不帶數據啊)

在這裏插入圖片描述

在這裏插入圖片描述

在這裏插入圖片描述

B*樹:

B樹要求半滿,B*樹要求2/3滿

紅黑樹:

雖然二者的高度都以O(logn)的速度增長,但是在B樹中,對數的底可以大很多倍,因此在查詢比較次數相同的情況下,B樹可以避免大量的磁盤訪問。

B樹在插入元素之後,許多結點都有可能會產生連鎖改變,這也是B樹的自平衡優點

紅黑樹RBT:自平衡

RBT VS AVL

紅黑樹RBT(Red Black Tree):可平衡的二叉搜索樹,中序遍歷單調不減。

​ 和AVL樹相比,AVL樹是嚴格平衡的二叉樹,平衡因子在0,-1,1,但是在插入和刪除的過程中他們可能引起更多旋轉。比如在插入操作的時候,下部分平衡調整結束可能引起上部分的不平衡,這樣可能會一直旋轉調整到根結點,因此AVL樹適用於頻繁的查找操作,而不適合頻繁的增加和刪除操作。

而對於紅黑樹來說,紅黑樹是局部平衡的二叉樹,它只能保證每個結點的一條子樹比另一條子樹長不超過兩倍。在插入和刪除操作中,最多隻需要兩次旋轉,以及改變結點的顏色的操作即可,因此時間複雜度低,適用於插入和刪除頻繁的操作。在c++中的set和map的底層都是紅黑樹。

紅黑規則

紅黑樹特徵:節點分紅黑二色,插入和刪除節點時需要遵守紅黑規則,紅黑規則確保了紅黑樹的平衡。

  • 自平衡BST

  • 節點只有紅黑兩種情況

  • 根總是黑的

  • 紅黑樹的葉子節點都是黑色的虛節點(NIL:external nodes of the red black tree)

    • 葉子節點是虛擬的節點,並不真實存在於樹中
    • 葉子節點的顏色必須是黑色的,不存在Value
  • 紅節點的孩子必定是黑的(不許有兩個連續的紅,可以有兩個連續的黑)

  • 從根到葉子節點NIL的每條路徑,必須包含相同數量的黑色節點

    (如果消掉紅色節點,黑色形成的樹是完美平衡的,層級和深度是相同的)

推論

  • 添加節點時,默認加紅色節點,因爲加紅色節點不會影響黑色的層級

  • 最長路徑不超過最短路徑的兩倍

在這裏插入圖片描述

AVL左旋右旋複習

在這裏插入圖片描述

在這裏插入圖片描述

紅黑樹的插入

方法:

  1. 執行標準BST插入,若空樹,創造黑色結點

  2. 執行標準BST插入,若樹非空,創造紅色節點

  3. 若新節點的父爲黑,退出

  4. 若新節點的父爲紅(祖父結點必爲黑),檢查叔叔結點

    1. 若爲紅,則叔父均變黑色,如果祖父結點不是根結點,則祖父結點變紅色。再檢查祖父結點(遞歸),直到根結點爲止
      在這裏插入圖片描述

    2. 若爲黑或空,旋轉操作(LL,LR,RR,RL,和AVL一樣)且變色

    LL:右旋 g,改變 g,p 顏色

    LR:右旋 p,調用LL

    RR:左旋 g,改變g,p 顏色

    RL:左旋 p,調用RR

    在這裏插入圖片描述

紅黑樹的刪除

double black (db):若刪除Black,並用其BlackChild替換時,該BlackChild爲db。因爲db會導致黑色結點數量在每條路徑上不相等,這是RBTree刪除的複雜之處。

刪除步驟:假設待刪除結點爲 c

  1. 執行標準的BST刪除。若刪除爲中間結點,會發生pred或succ的key替換,color不變。最終所有情況都轉化爲leaf node刪除(single child node刪除就繼續替換), 記爲 u。特例:若 c 是葉子,則 u 是nil。

  2. 若 u 爲Red,直接刪除;

  3. 若 u 爲Black, u是葉節點,其child NIL爲黑色,則 u 爲db。

    • 若root是db,直接改爲single black,結束

    定義 u 的sibling s,parent p,s的left child x, s的right child y;

    • 若s爲黑,且x,y爲黑,則 u 分一個black給p

      • 若p爲紅,則令p爲黑,s爲紅,結束。

      在這裏插入圖片描述

      • 若p爲黑,則令p爲db,s爲紅,遞歸p的db處理。
        在這裏插入圖片描述
    • 若s爲黑,且x,y至少有一爲紅,則檢查xy中遠離u的結點far是什麼顏色

      • 若far是紅(包括near是紅或黑的兩種)。令s爲p的顏色,令p爲黑,令far爲黑,讓s上去(若s爲右孩子,左旋p;若s爲左孩子,右旋p)

        經過這樣調整之後,黑色結點數一定相同
        在這裏插入圖片描述

      • 若far是黑,near是紅。則令s爲紅,near爲黑,s往與u相反的方向旋轉。遞歸調用far爲紅的情況

      • 在這裏插入圖片描述

    • 若s爲紅,則p,x,y必爲黑

      • 令s爲黑,p爲紅。通過旋轉讓s上去(s爲右結點,則左旋p;s爲左節點,則右旋p)。處理完後u結點不變,其sibling不再是原來的s,而是x或y,路徑長度仍未修復,因此進入到s爲黑的情況。

    在這裏插入圖片描述

Tip: p一定往db方向旋轉,s一定往遠離db方向旋轉

db一家人:

若s爲黑,far near都爲黑,db把sb丟給p,s發火

若s爲黑,far爲紅,db告訴p,讓p授意s,p冷靜,s讓far冷靜,p去揍db

若s爲黑,near爲紅,db告訴s,s讓near冷靜,s生氣,s覺得db不是好人,離開db

若s爲紅,p讓s冷靜,p發火,p去揍db

RBTree.h

#pragma once
#include<iostream>
enum COLOR {RED,BLACK};
template<class T> class RBTree;

template<class T>
class RBTNode {
private:
	T key;
	COLOR color; // 紅色爲0,黑色爲1,db爲2
	RBTNode<T> * left;
	RBTNode<T> * right;
	RBTNode<T> * parent;
public:
	RBTNode(T value):key(value),color(RED),left(nullptr),right(nullptr),parent(nullptr) {}
	friend  class RBTree<T>;
};

template<class T>
class RBTree{
private:
	RBTNode<T> * root;
public:
	RBTree() :root(nullptr) {}
	~RBTree() { clear(root); root = nullptr; }
	void clear(RBTNode<T>*node);
	void MidOrder();
	void insert(T key);
	void remove(T key);
private:
	// 遍歷基操
	void MidOrder(RBTNode<T>* node);
	// 旋轉基操
	void leftrotate(RBTNode<T>*node);
	void rightrotate(RBTNode<T>*node);
	// 顏色基操
	COLOR getcolor(RBTNode<T>*node);
	// 插入基操
	void LLinsert(RBTNode<T>*node);
	void RRinsert(RBTNode<T>*node);
	RBTNode<T>* BSTinsert(T key);
	void fixColor(RBTNode<T>* node);
	// 刪除基操
	RBTNode<T>* getfar(RBTNode<T>*u);
	RBTNode<T>* getnear(RBTNode<T>*u);
	void farred(RBTNode<T>*u);
	void farblack(RBTNode<T>*u);
	void fixdb(RBTNode<T>*u);
	RBTNode<T>* getnodeBST(RBTNode<T>*node,T key);
	RBTNode<T>* getPred(RBTNode<T>* node);
	RBTNode<T>* getSucc(RBTNode<T>* node);
};

// 以x的孩子爲圓心,對x進行左旋或右旋
template<class T> void RBTree<T>::leftrotate(RBTNode<T> *node) {
	RBTNode<T> *child = node->right;
	if (node == nullptr || child == nullptr) return;
	//先處理child->left和node的關係
	if(child->left != nullptr) child->left->parent = node;
	node->right = child->left;
	//再處理child和node->parent的關係,判斷node是parent的左右孩子
	child->parent = node->parent;
	if (node->parent == nullptr) root = child;
	else if(node == node->parent->left) node->parent->left = child;
	else node->parent->right = child;
	//在處理child和 node的關係
	child->left = node;
	node->parent = child;
}
template<class T> void RBTree<T>::rightrotate(RBTNode<T> *node) {
	if (node == nullptr || node->left == nullptr) return;
	RBTNode<T>*child = node->left;
	// 先處理child->right和node的關係
	node->left = child->right;
	if (child->right != nullptr) child->right->parent = node;
	// 再處理child和node->parent的關係
	child->parent = node->parent;
	if (node->parent == nullptr) root = child;
	else if (node == node->parent->right) node->parent->right = child;
	else node->parent->left = child;
	// 最後處理node和child的關係
	node->parent = child;
	child->right = node;
}
// 顏色基操,紅色爲0,黑色爲1
template<class T> COLOR RBTree<T>::getcolor(RBTNode<T>*node) {
	if (node == nullptr) return BLACK;
	return node->color;
}
/************************插入基操********************************/
template<class T> void RBTree<T>::LLinsert(RBTNode<T>*node) {
	RBTNode<T> *p = node->parent;
	RBTNode<T> *g = p->parent;
	rightrotate(g);
	g->color = RED;
	p->color = BLACK;
}
template<class T> void RBTree<T>::RRinsert(RBTNode<T>*node) {
	RBTNode<T> *p = node->parent;
	RBTNode<T> *g = p->parent;
	leftrotate(g);
	g->color = RED;
	p->color = BLACK;
}
template<class T> RBTNode<T>* RBTree<T>::BSTinsert(T key) {
	RBTNode<T> * p = root;
	RBTNode<T> * q = nullptr; // p的父節點
	while (p)
	{
		q = p;
		if (key == p->key) return nullptr;
		else if (key > p->key) p = p->right;
		else p = p->left;
	}
	p = new RBTNode<T>(key);
	if (root == nullptr) {
		root = p;
		root->color = BLACK;
	}
	else if (key < q->key) {
		q->left = p;
		p->parent = q;
	}
	else {
		q->right = p;
		p->parent = q;
	}
	return p; // 返回新節點
}
template<class T> void RBTree<T>::fixColor(RBTNode<T>* node) {
	if (node == nullptr) return;
	// node是新插入的節點,需要對他們進行recolor
	RBTNode<T> *p = node->parent;
	if (node == nullptr || node == root || p->color == BLACK) return;
	//只剩下parent->color爲RED,parent->parent->color爲BLACK的情況
	RBTNode<T> *g = p->parent;
	RBTNode<T> *u = nullptr;
	if (p == g->right) u = g->left;
	else u = g->right;

	if (getcolor(u) == RED) {
		u->color = BLACK;
		p->color = BLACK;
		if (g != root) g->color = RED;
		fixColor(g);
	}
	else {
		if (g->left == p && p->left == node) {
			// LL情況
			LLinsert(node);
		}
		else if (g->right == p && p->right == node ) {
			// RR情況
			RRinsert(node);
		}
		else if(g->right ==p && p->left == node){
			// RL情況
			rightrotate(p);
			RRinsert(p);
		}
		else {
			//LR情況
			leftrotate(p);
			LLinsert(p);
		}
	}
}
template<class T> void RBTree<T>::insert(T key) {
	fixColor(BSTinsert(key));
}
/************************刪除基操********************************/
template<class T> RBTNode<T>* RBTree<T>::getfar(RBTNode<T>*u) {
	RBTNode<T> * p = u->parent;
	RBTNode<T> * s = nullptr;
	if (u == p->left) {
		s = p->right;
		return s->right;
	}
	else
	{
		s = p->left;
		return s->left;
	}
}
template<class T> RBTNode<T>* RBTree<T>::getnear(RBTNode<T>*u) {
	RBTNode<T> * p = u->parent;
	RBTNode<T> * s = nullptr;
	if (u == p->left) {
		s = p->right;
		return s->left;
	}
	else
	{
		s = p->left;
		return s->right;
	}
}
template<class T> void RBTree<T>::farred(RBTNode<T>*u) {
	RBTNode<T> * p = u->parent;
	RBTNode<T> * s = (u == p->left) ? p->right : p->left;
	RBTNode<T>* far = getfar(u);
	if (far->color == RED)
	{
		s->color = p->color;
		p->color = BLACK;
		far->color = BLACK;
		if (s == p->right) leftrotate(p);
		else rightrotate(p);
	}
}
template<class T> void RBTree<T>::farblack(RBTNode<T>*u) {
	RBTNode<T> * p = u->parent;
	RBTNode<T> * s = nullptr;
	if (u == p->left) s = p->right;
	else s = p->left;
	RBTNode<T>* far = getfar(u);
	RBTNode<T>* near = getnear(u);

	if ((getcolor(far) == BLACK) && (getcolor(near) == RED))
	{
		s->color = RED;
		near->color = BLACK;
		if (s == p->right) rightrotate(s);
		else leftrotate(s);
		farred(u);
	}
}
template<class T> void RBTree<T>::fixdb(RBTNode<T>*u) {	// 當u是db的情況
	if (u == root) return;

	RBTNode<T> * p = u->parent;
	RBTNode<T> * s = ((u == p->left) ? p->right : p->left);
	if (getcolor(s) == RED) {
		s->color = BLACK;
		p->color = RED;
		if (s == p->right) leftrotate(p);
		else rightrotate(p);
		// 如果進入過s爲紅的情況,就一定要刷新sp,再進入到s爲黑的情況
	}
	p = u->parent;
	s = ((u == p->left) ? p->right : p->left);

	if (getcolor(s) == BLACK) {
		if ((getcolor(s->left) == BLACK) && (getcolor(s->right) == BLACK)) {
			if (getcolor(p) == RED) {
				p->color = BLACK; s->color = RED;
				return;
			}
			else {
				s->color = RED; fixdb(p);
			}
		}
		else { //s爲黑,xy至少有一紅
			RBTNode<T>* far = getfar(u);
			if (getcolor(far) == RED)
				farred(u);
			else  farblack(u);
		}
	}
}
template<class T> RBTNode<T>* RBTree<T>::getnodeBST(RBTNode<T>*node, T key) {
	// 返回待被刪除結點的位置
	if (node == nullptr) return node;
	if (key < node->key) return getnodeBST(node->left, key);
	else if (key > node->key) return getnodeBST(node->right, key);
	else { // 找到了要刪除的節點

		if (node->left != nullptr) {
			RBTNode<T> * maxleft = getPred(node);
			node->key = maxleft->key;
			return getnodeBST(node->left, maxleft->key);
		}
		else if (node->right != nullptr) {
			RBTNode<T> *minright = getSucc(node);
			node->key = minright->key;
			return getnodeBST(node->right, minright->key);
		}
		else { //若爲葉子節點,則
			return node;
		}
	}
}
template<class T> RBTNode<T>* RBTree<T>::getPred(RBTNode<T>* node) {
	// 返回node的最大前驅
	RBTNode<T>* maxleft = node->left;
	while (maxleft->right != nullptr) {
		maxleft = maxleft->right;
	}
	return maxleft;
}
template<class T> RBTNode<T>* RBTree<T>::getSucc(RBTNode<T>* node) {
	// 返回node的最小後繼
	RBTNode<T>* minright = node->right;
	while (minright->left != nullptr) {
		minright = minright->left;
	}
	return minright;
}
template<class T> void RBTree<T>::remove(T key) {
	RBTNode<T> * u = getnodeBST(root, key);  //找到待刪除結點
	if (u == nullptr) {
		std::cout << "結點不存在" << std::endl;
		return;
	}
	if (u == root) { delete root; root = nullptr; return; }
	else if (u->color == RED)
	{
		if (u == u->parent->left) u->parent->left = nullptr;
		else u->parent->right = nullptr;
	}
	else {
		// 當u是db,調用db修正函數
		fixdb(u);
		if (u == u->parent->left) u->parent->left = nullptr;
		else u->parent->right = nullptr;
	}
	delete u;
	u = nullptr;
	return;
}
/************************遍歷基操********************************/
template <class T> void RBTree<T>::MidOrder() {
	if (root == nullptr) return;
	MidOrder(root);
}
template <class T> void RBTree<T>::MidOrder(RBTNode<T>* node) {
	if (node==nullptr) return;
	MidOrder(node->left);
	std::cout << node->key << ":" << node->color << "   ,"<< "left:";
	if (node->left != nullptr) std::cout << node->left->key;
	std::cout << "," << "right:  ";
	if (node->right!=nullptr) std::cout  << node->right->key;
	std::cout << std::endl;
	MidOrder(node->right);
}

template<class T> void RBTree<T>::clear(RBTNode<T>* node) {
	if (node)
	{
		clear(node->left);
		clear(node->right);
		delete node;
		node = nullptr;
	}
}

main.h

#include<iostream>
#include<time.h>
#include<vector>
#include "RBTree.h"
using namespace std;

int main(int argc, char** argv) {
	srand((int)time(0));
	RBTree<int> tree;
	int times = rand() % 20;
	int copytimes = times;
	vector<int>arr;
	while (copytimes--)
	{
		int val = rand() % 100;
		arr.push_back(val);
		tree.insert(val);
	}
	tree.MidOrder();
	cout << "-------------------------------------" << endl;
	times = 50;
	while (times--)
	{
		int todel = rand() % arr.size();
		cout << "\n try to del " << arr[todel] << endl;
		tree.remove(arr[todel]);
		tree.MidOrder();
		cout << "-------------------------------------" << endl;
	}

	system("pause");
	return 0;
}

參考文獻:

—AVL刪除之前的引用丟失了有些是從視頻中截的圖,侵刪

  1. https://blog.csdn.net/FreeeLinux/article/details/52204851 AVL樹刪除
  2. https://www.geeksforgeeks.org/avl-tree-set-1-insertion/ AVL樹
  3. https://www.geeksforgeeks.org/insert-operation-in-b-tree/ B樹
  4. https://blog.csdn.net/waiwai0331/article/details/51723630 B樹
  5. https://github.com/StudentErick/BTree/blob/master/BTree.h B樹
  6. https://blog.csdn.net/zhuanzhe117/article/details/78039692 B樹
  7. https://blog.csdn.net/u011711997/article/details/80317609 B樹
  8. https://www.youtube.com/watch?v=GKa_t7fF8o0 B樹刪除演示
  9. https://www.cs.usfca.edu/~galles/visualization/BTree.html B樹動畫
  10. https://www.youtube.com/watch?v=w5cvkTXY0vQ RBT
  11. http://alrightchiu.github.io/SecondRound/red-black-tree-deleteshan-chu-zi-liao-yu-fixupxiu-zheng.html
  12. https://github.com/anandarao/Red-Black-Tree/blob/master/RBTree.cpp RBT
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章