樹
本文所有代碼地址: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樹的添加操作
一、大致思路
- 將插入結點 w 視作標準BST插入
- 從w開始,向上移動,找到第一個不平衡結點,g,由此確定p和n。注意:此處向上查找的方法用的是遞歸!!!
- 修復g,使之平衡(令其高度和插入之前的高度一致), gpn的排列方式分爲四種情況
- LL:右旋g
- LR:左旋p+右旋g
- RR:左旋g
- RL:右旋p+左旋g
二、具體實現流程
- 執行正常的BST插入
- 當前節點一定是新插入節點的祖先,更新當前節點的高度
- 獲取當前節點的平衡因子
- 若平衡因子大於1,則失衡,處於LL或者LR的情況,通過比較插入結點和左子樹根結點的大小可以確定LL和LR
- 若平衡因子小於-1,則失衡,處於RR或者RL的情況,通過比較插入結點和右子樹根結點的情況可以確定RR或者RL的情況。
三、實際例子:
AVL樹的刪除操作
一、大致思路
-
對要刪除結點w進行標準BST刪除
-
從w開始,向上移動找到第一個不平衡結點g,則可同時找到對應的結點pn,注意,此處的pn和插入處不同
-
調整g使之平衡。分爲四種情況LL、LR、RR、RL.但是即使g平衡之後其祖先也不一定平衡(因爲平衡後的高度不是原來的高度了),這與插入的情況不同,因此還需要往上修復g的祖先,直到根結點爲止,比如這種情況:
二、具體流程
- 執行正常的BST刪除
- 找到待刪除結點
- 若待刪除結點的度爲2,則找到待刪除結點的右子樹的最小值來替換待刪除結點,並刪除掉被替換的節點
- 若待刪除結點的爲0或1,刪除待刪除結點
- 更新當前節點的高度,這裏的當前節點指的是所有待刪除結點的祖先。注意,對於空節點無法也無需更新高度
- 獲取當前節點的平衡因子驗證是否失衡
- 若平衡因子>1,則處於左側失衡情況,可以分爲LL和LR,通過獲取左子樹的平衡因子>=0,爲LL,否則爲LR
- 若平衡因子<-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左旋右旋複習
紅黑樹的插入
方法:
-
執行標準BST插入,若空樹,創造黑色結點
-
執行標準BST插入,若樹非空,創造紅色節點
-
若新節點的父爲黑,退出
-
若新節點的父爲紅(祖父結點必爲黑),檢查叔叔結點
-
若爲紅,則叔父均變黑色,如果祖父結點不是根結點,則祖父結點變紅色。再檢查祖父結點(遞歸),直到根結點爲止
-
若爲黑或空,旋轉操作(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
-
執行標準的BST刪除。若刪除爲中間結點,會發生pred或succ的key替換,color不變。最終所有情況都轉化爲leaf node刪除(single child node刪除就繼續替換), 記爲 u。特例:若 c 是葉子,則 u 是nil。
-
若 u 爲Red,直接刪除;
-
若 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刪除之前的引用丟失了有些是從視頻中截的圖,侵刪
- https://blog.csdn.net/FreeeLinux/article/details/52204851 AVL樹刪除
- https://www.geeksforgeeks.org/avl-tree-set-1-insertion/ AVL樹
- https://www.geeksforgeeks.org/insert-operation-in-b-tree/ B樹
- https://blog.csdn.net/waiwai0331/article/details/51723630 B樹
- https://github.com/StudentErick/BTree/blob/master/BTree.h B樹
- https://blog.csdn.net/zhuanzhe117/article/details/78039692 B樹
- https://blog.csdn.net/u011711997/article/details/80317609 B樹
- https://www.youtube.com/watch?v=GKa_t7fF8o0 B樹刪除演示
- https://www.cs.usfca.edu/~galles/visualization/BTree.html B樹動畫
- https://www.youtube.com/watch?v=w5cvkTXY0vQ RBT
- http://alrightchiu.github.io/SecondRound/red-black-tree-deleteshan-chu-zi-liao-yu-fixupxiu-zheng.html
- https://github.com/anandarao/Red-Black-Tree/blob/master/RBTree.cpp RBT