本文使用C++實現二叉搜索樹(binary search tree,bst)數據結構。
一、爲什麼要引入二叉搜索樹
通過對向量和列表這兩類線性數據結構的原理分析,我們可以發現向量結構在對靜態查詢操作的支持比較好,有序向量的二分查找能做到在O(logn)的時間,但是對刪除和插入操作在最壞情況下能達到O(n)的時間。而與向量相對立的列表結構能很好地支持動態操作,如刪除和插入只需要O(1)的時間,但是它對於靜態查找操作的支持卻很差,如有序列表的查找在最壞情況下也需要O(n)的時間。所以問題來了:若既要求對象集合的組成可以高效率地動態調整,同時也需要能夠高效率的查找,則向量和列表結構都難以勝任。
若將二分查找策略推廣到類似列表這種動態存儲的數據結構,就能既能保證靜態查找操作的高效性能,也能保證動態增刪操作的高效性能。基於這種思想,將二分查找策略進行抽象和推廣,可以定義並實現二叉搜索樹結構。
二、二叉搜索樹的特點
所謂的二叉搜索樹,處處都滿足順序性:在任一節點r的左(右)子樹中,所有節點均不大於(不小於)r。
如上圖可見,任何一棵二叉樹是二叉搜索樹,當且僅當其中序遍歷序列單調非降。
三、二叉搜索樹的性能
因爲二叉搜索樹的結構融合了二分查找的思想,所以可以通過二分查找策略對指定值進行搜索,對於深度爲h的二叉搜索樹,不難證明在最壞情況下的時間複雜度爲O(h)。然而不幸的是,對於規模爲n的二叉搜索樹,深度在最壞的情況下能達到O(n),比如當樹退化爲一條單鏈時,查找的時間複雜度就和列表結構一樣爲O(n)了。
由上可見,若要控制單次查找在最壞情況下的運行時間,必須從二叉搜索樹的高度入手,其實二叉搜索樹本身沒有多麼實際的作用,它最大的貢獻是將二分查找策略融合到樹結構中,這種樹結構使樹結構的查找操作效率大大提升。事實上,若能解決高度的問題,那麼二叉搜索樹就能真正地使用了,這就是後面要介紹的平衡二叉樹,其具有很多的變種,可解決高度的問題。
四、二叉搜索樹的實現
所以,二叉搜索樹的實現最大的意義是爲各種平衡二叉樹提供模板接口,主要包括查找,插入和刪除操作。這裏通過構造bst類實現二叉搜索樹數據結構,其由binTree類繼承而來,參見https://blog.csdn.net/qq_18108083/article/details/84727888。
操作 | 功能 | 對象 |
search(const T& e) | 查找指定元素,返回命中節點,並返回其父親節點 | 二叉搜索樹 |
insert(const T& e) | 按照二叉搜索樹的結構要求插入指定元素 | 二叉搜索樹 |
remove(const T& e) | 按照二叉搜索樹的結構要求刪除元素 | 二叉搜索樹 |
(1) bst.h
#pragma once
#include"binTree.h"
//二叉搜索樹模板類
template<typename T> class bst :public binTree<T>
{
public:
binNode<T>* _hot; //所命中的節點的parent
binNode<T>* connect34( //按照3+4結構,聯接3個節點及4顆樹
binNode<T>*, binNode<T>*, binNode<T>*,
binNode<T>*, binNode<T>*, binNode<T>*, binNode<T>*
);
binNode<T>* rotateAt(binNode<T>* v); //對x及父親、祖父做統一旋轉調整
public:
static binNode<T>* & searchIn(binNode<T>* &v, const T& e, binNode<T>* &hot); //在以v爲根的子樹中查找關鍵碼e,並將命中節點的parent返回給hot
virtual binNode<T>* & search(const T& e); //查找
virtual binNode<T>* insert(const T& e); //插入
virtual bool remove(const T& e); //刪除
static binNode<T>* removeAt(binNode<T>* &x, binNode<T>* &hot); //刪除位置x所指的節點(以判斷存在),返回值指向實際被刪除者的接替者,hot指向被實際刪除者的父親
};
template<typename T> binNode<T>* & bst<T>::searchIn(binNode<T>* &v, const T& e, binNode<T>* &hot)
{
if (!v || (v->data == e)) //若v本身不存在或則直接命中,則返回v
return v;
hot = v;
return searchIn((e < (v->data)) ? v->lc : v->rc, e, hot);
}
template<typename T> binNode<T>* & bst<T>::search(const T& e)
{
return searchIn(_root, e, _hot = nullptr);
} //返回時,返回值指向命中節點或假想的哨兵節點,hot指向其parent
template<typename T> binNode<T>* bst<T>::insert(const T& e)
{
//首先search查詢是否存在或待插入的位置
binNode<T>* &x = search(e);
if (x) //若已經存在則返回
return x;
//否則直接插在x上,設置好前後連接關係
x = new binNode<T>(e, _hot);
_size++;
updateHeightAbove(x);
return x;
}
template<typename T> bool bst<T>::remove(const T& e)
{
binNode<T>* succ=nullptr; //緩存替代者節點
//首先搜索是否存在
binNode<T>* &x = search(e); //緩存將要被刪除的節點
binNode<T>* temp = x;
if (!x) //不存在則直接返回
return false;
//case 1 :命中節點至多隻有一個孩子,則直接刪除,孩子頂上
if (!(x->lc)) //左孩子爲空,則右孩子頂上
{
succ = x = x->rc;
}
else if(!(x->rc))
{
succ = x = x->lc;
}
//case 2 :命中節點有兩個孩子,則通過succ找直接後繼節點
else
{
temp = temp->succ();
swap(x->data, temp->data); //交換數據後temp成爲實際要刪除的點
binNode<T>* u = temp->parent;
((u == x) ? u->rc : u->lc) = succ = temp->rc; //跨過節點succ(只有一種情況是左孩子)
}
_hot = temp->parent;
if (succ)
succ->parent = _hot;
delete temp;
_size--;
updateHeightAbove(_hot);
return true;
}
template<typename T> binNode<T>* bst<T>::removeAt(binNode<T>* &x, binNode<T>* &hot)
{
binNode<T>* w = x; //實際被摘除的節點
binNode<T>* succ = nullptr; //實際被刪除的節點的接替者
if (!(x->lc)) //左孩子爲空,則右孩子頂上
{
succ = x = x->rc;
}
else if (!(x->rc))
{
succ = x = x->lc;
}
//case 2 :命中節點有兩個孩子,則通過succ找直接後繼節點
else
{
w = w->succ(); //中序遍歷的直接後繼
swap(x->data, w->data); //交換數據
binNode<T>* u = w->parent;
((u == x) ? u->rc : u->lc) = succ = w->rc; //隔離實際刪除點
}
hot = w->parent;
if (succ) succ->parent = hot;
delete w;
return succ;
}
template<typename T> binNode<T>* bst<T>::connect34( //按照3+4結構,聯接3個節點及4顆樹
binNode<T>* a, binNode<T>* b, binNode<T>* c,
binNode<T>* T0, binNode<T>* T1, binNode<T>* T2, binNode<T>* T3)
{
a->lc = T0; if (T0) T0->parent = a;
a->rc = T1; if (T1) T1->parent = a;
updateHeight(a); //更新a的高度
c->lc = T2; if (T2) T2->parent = c;
c->rc = T3; if (T3) T3->parent = c;
updateHeight(c);
b->lc = a; a->parent = b;
b->rc = c; c->parent = b;
updateHeight(b);
return b;
}
template<typename T> binNode<T>* bst<T>::rotateAt(binNode<T>* v)
{
binNode<T>* p = v->parent; binNode<T>* g = p->parent; //首先根據v找到p和g節點
if (g->lc == p) //失衡是由節點刪除導致的(p是g的左孩子)
{
if (p->lc == v) //進一步,v是p的左孩子
{
p->parent = g->parent;
return connect34(v, p, g, v->lc, v->rc, p->rc, g->rc);
}
else
{
v->parent = g->parent;
return connect34(p, v, g, p->lc, v->lc, v->rc, g->rc);
}
}
else //失衡是由節點插入造成的
{
if (p->rc == v)
{
p->parent = g->parent;
return connect34(g, p, v, g->lc, p->lc, v->lc, v->rc);
}
else
{
v->parent = g->parent;
return connect34(g, v, p, g->lc, v->lc, v->rc, p->rc);
}
}
}