1、二叉搜索樹
1.1 二叉搜索數(shù)的概念
二叉搜索樹又稱二叉排序樹,它或者是一棵空樹,或者是具有以下性質(zhì)的二叉樹:
- 若它的左子樹不為空,則左子樹上所有節(jié)點的值都小于根節(jié)點的值
- 若它的右子樹不為空,則右子樹上所有節(jié)點的值都大于根節(jié)點的值
- 它的左右子樹也分別為二叉搜索樹
我們先給出兩個示例:
此二叉樹就不是搜索二叉樹,根據(jù)性質(zhì)來看,每顆子樹也是二叉搜索樹,紅圈圈起來的地方顯然是違背了這個性質(zhì)。
此搜索二叉樹按性質(zhì)來看是完全滿足的,因此此二叉樹就是二叉搜索樹。
1.2 二叉搜索樹的操作
二叉搜索樹的操作包含,增刪查,下面我們就來分別模擬實現(xiàn)這些接口。
1.2.1 二叉搜索樹的查找
思路:
a、從根開始比較,查找,比根大則往右邊走查找,比根小則往左邊走查找。
b、最多查找高度次,走到到空,還沒找到,這個值不存在。
查找比較好理解,這里就不畫圖了,直接開始寫代碼。
1.查找的非遞歸方法:
bool Find(const K& key)
{
Node* root = _root;
while (root)
{
if (key > root->_key)
root = root->_right;
else if (key < root->_key)
root = root->_left;
else
return true;
}
return false;
}
2.查找的遞歸方法:
bool FindR(const K& key)
{
return _FindR(_root, key);
}
bool _FindR(Node* root, const K& key)
{
if (nullptr == root)
return false;
if (key > root->_key)
return _FindR(root->_right, key);
else if (key < root->_key)
return _FindR(root->_left, key);
else
return true;
}
在遞歸查找中,我們用戶在使用的時候只需要填入要查找的數(shù)字,但是我們的查找接口需要兩個參數(shù),開始節(jié)點與查找數(shù)值,因此我們在 FindR 下再封裝一層 _FindR 就可以實現(xiàn)了。
1.2.2 二叉搜索樹的插入
二叉搜索樹的插入思路:
a. 樹為空,則直接新增節(jié)點,賦值給root指針
b. 樹不空,按二叉搜索樹性質(zhì)查找插入位置,插入新節(jié)點
我們先來以圖例演示一遍過程:
對下面這棵樹插入一個值為11的節(jié)點
這里是成功插入的情況,具體過程是上面的圖例,我們在梳理一遍:
1、首先,我們用兩個結(jié)點指針 parent和cur ,cur不斷尋找正確插入位置,parent不斷記下來cur的父節(jié)點指針;
2、當(dāng) cur 找到正確位置之后,先 new 一個節(jié)點對象給 cur,此時 new 出來的對象只是給了 cur 這個指針變量,并沒有鏈接起來;
3、判斷要插入的位置是 parent 的左孩子還是右孩子,再將其鏈接到正確位置插入就算完成了。
失敗情況:
因為是二叉搜索樹,節(jié)點左子樹的值全小于節(jié)點的值,右子樹的值全大于節(jié)點的值,不存在相等的情況,因此當(dāng)找到一個節(jié)點的值與要插入的值相等時,就是插入失敗,此時返回false。
根據(jù)上面的圖示以及過程的梳理我們來寫非遞歸版本的代碼:
bool Insert(const K& key)
{
if (nullptr == _root)
{
_root = new Node(key);
return true;
}
Node* parent = nullptr;
Node* cur = _root;
while (cur)
{
if (key > cur->_key)
{
parnet = cur;
cur = cur->_right;
}
else if (key < cur->_key)
{
parent = cur;
cur = cur->_left;
}
else
{
return false;
}
}
cur = new Node(key);
if (key > parent->_key)
{
parent->_right = cur;
}
else
{
parent->_left = cur;
}
return true;
}
insert接口我們只提供非遞歸版本的。
1.2.3 二叉搜索樹的刪除
首先查找元素是否在二叉搜索樹中,如果不存在,則返回, 否則要刪除的結(jié)點可能分下面四種情
況:
a. 要刪除的結(jié)點無孩子結(jié)點
b. 要刪除的結(jié)點只有左孩子結(jié)點
c. 要刪除的結(jié)點只有右孩子結(jié)點
d. 要刪除的結(jié)點有左、右孩子結(jié)點
看起來有待刪除節(jié)點有4中情況,實際情況a可以與情況b或者c合并起來,因此真正的刪除過程
如下:
情況b:刪除該結(jié)點且使被刪除節(jié)點的雙親結(jié)點指向被刪除節(jié)點的左孩子結(jié)點–直接刪除
情況c:刪除該結(jié)點且使被刪除節(jié)點的雙親結(jié)點指向被刪除結(jié)點的右孩子結(jié)點–直接刪除
情況d:在它的右子樹中尋找中序下的第一個結(jié)點(關(guān)鍵碼最小),用它的值填補到被刪除節(jié)點
中,再來處理該結(jié)點的刪除問題–替換法刪除
1、刪除節(jié)點沒有左孩子
// 1、左為空
if (cur->_left == nullptr)
{
if (cur == _root)
{
_root = cur->_right;
}
if (parent->_left == cur)
{
parent->_left = cur->_right;
}
else
{
parent->_right = cur->_right;
}
}
2、刪除節(jié)點沒有右孩子
// 2、右為空
else if (cur->_right == nullptr)
{
if (cur == _root)
{
_root = cur->_left;
}
if (parent->_left == cur)
{
parent->_left = cur->_left
}
else
{
parent->_right = cur->_left;
}
}
3、刪除節(jié)點左右孩子都存在
// 3、左右都不為空
else
{
Node* parent = cur;
Node* subLeft = cur->_right;
while (subLeft->_left)
{
parent = subLeft;
subLeft = subLeft->_left;
}
swap(cur->_key, subLeft->_key);
if (subLeft == parent->_left)
parent->_left = subLeft->_right;
else
parent->_right = subLeft->_right;
delete(subLeft);
}
整個刪除接口合在一起的代碼:
bool Erase(const K& key)
{
Node* parent = nullptr;
Node* cur = _root;
while (cur)
{
if (key > cur->_key)
{
parnet = cur;
cur = cur->_right
}
else if (key < cur->_key)
{
parent = cur;
cur = cur->_left;
}
else
{
// 1、左為空
if (cur->_left == nullptr)
{
if (cur == _root)
{
_root = cur->_right;
}
if (parent->_left == cur)
{
parent->_left = cur->_right;
}
else
{
parent->_right = cur->_right;
}
delete(cur);
}
// 2、右為空
else if (cur->_right == nullptr)
{
if (cur == _root)
{
_root = cur->_left;
}
if (parent->_left == cur)
{
parent->_left = cur->_left
}
else
{
parent->_right = cur->_left;
}
delete(cur);
}
// 3、左右都不為空
else
{
Node* parent = cur;
Node* subLeft = cur->_right;
while (subLeft->_left)
{
parent = subLeft;
subLeft = subLeft->_left;
}
swap(cur->_key, subLeft->_key);
if (subLeft == parent->_left)
parent->_left = subLeft->_right;
else
parent->_right = subLeft->_right;
delete(subLeft);
}
}
}
}
遞歸版本:
遞歸版本與非遞歸版本類似, 非遞歸版本中使用while循環(huán)來找key位置,遞歸就是不斷調(diào)用自身來找key位置,當(dāng)找到后,依然分三種情況來分析:左為空、右為空、左右都不為空。
遞歸版本中用引用來接受傳來的root,因此不用考慮鏈接問題,也就不存在判斷刪除位置是父節(jié)點的左還是右,直接修改root即可。
1、左為空/左右都為空:先記下要刪除的節(jié)點,再修改root,最后釋放刪除的節(jié)點。 Node* del = root; root = root->_right; delete(del);
2、右為空:先記下要刪除的節(jié)點,再修改root,最后釋放刪除的節(jié)點。Node* del = root; root = root->_left; delete(del);
3、左右都不為空:先找到右子樹的最左節(jié)點(最小值),交換 root->_key與subLeft->_key, 右子樹的最左節(jié)點一定是葉子節(jié)點,因此刪除subLeft直接再去調(diào)用函數(shù)自身即可,即再來一次左右都為空(左為空)的刪除。
bool EraseR(const K& key)
{
return _EraseR(_root, key);
}
bool _EraseR(Node*& root, const K& key)
{
if (root == nullptr)
return false;
if (root->_key < key)
{
_EraseR(root->_right, key);
}
else if (root->_key > key)
{
_EraseR(root->_left, key);
}
else
{
if (root->_left == nullptr)
{
Node* del = root;
root = root->_right;
delete(del);// 必須釋放,不釋放會導(dǎo)致內(nèi)存泄漏
return true;
}
else if (root->_right == nullptr)
{
Node* del = root;
root = root->_left;
delete(del);
return true;
}
else
{
Node* subLeft = root->_right;
while (subLeft->_left)
{
subLeft = subLeft->_left;
}
swap(root->_key, subLeft->_key);
return _EraseR(root->_right, key);
}
}
}
2、二叉搜索樹的應(yīng)用
2.1 K模型
K模型:K模型即只有key作為關(guān)鍵碼,結(jié)構(gòu)中只需要存儲Key即可,關(guān)鍵碼即為需要搜索到的值。
比如:給一個單詞word,判斷該單詞是否拼寫正確,具體方式如下:
- 以詞庫中所有單詞集合中的每個單詞作為key,構(gòu)建一棵二叉搜索樹
- 在二叉搜索樹中檢索該單詞是否存在,存在則拼寫正確,不存在則拼寫錯誤。
2.2 KV模型
KV模型:每一個關(guān)鍵碼key,都有與之對應(yīng)的值Value,即<Key, Value>的鍵值對。 該種方式在現(xiàn)實生活中非常常見:
● 比如英漢詞典就是英文與中文的對應(yīng)關(guān)系,通過英文可以快速找到與其對應(yīng)的中文,英文單詞與其對應(yīng)的中文<word, chinese>就構(gòu)成一種鍵值對;
● 再比如統(tǒng)計單詞次數(shù),統(tǒng)計成功后,給定單詞就可快速找到其出現(xiàn)的次數(shù),單詞與其出現(xiàn)次數(shù)就是<word, count>就構(gòu)成一種鍵值對。文章來源:http://www.zghlxwxcb.cn/news/detail-760348.html
3、二叉搜索樹的性能分析
插入和刪除操作都必須先查找,查找效率代表了二叉搜索樹中各個操作的性能。
對有n個結(jié)點的二叉搜索樹,若每個元素查找的概率相等,則二叉搜索樹平均查找長度是結(jié)點在二叉搜索樹的深度的函數(shù),即結(jié)點越深,則比較次數(shù)越多。
但對于同一個關(guān)鍵碼集合,如果各關(guān)鍵碼插入的次序不同,可能得到不同結(jié)構(gòu)的二叉搜索樹:
最優(yōu)情況下,二叉搜索樹為完全二叉樹(或者接近完全二叉樹),其平均比較次數(shù)為:log_2 N
最差情況下,二叉搜索樹退化為單支樹(或者類似單支),其平均比較次數(shù)為:N
最差情況是退化為單支樹,性能就變得很差了,因為后續(xù)我們將改進(jìn)搜索二叉樹的插入與刪除,來實現(xiàn)平衡二叉搜索樹,即AVL樹與紅黑樹(RB樹)。文章來源地址http://www.zghlxwxcb.cn/news/detail-760348.html
4、K模型與KV模型完整代碼
4.1 二叉搜索樹的模擬實現(xiàn)(K模型)
namespace key
{
template <class K>
struct BSTreeNode
{
BSTreeNode* _left;
BSTreeNode* _right;
K _key;
BSTreeNode(const K& key)
:_left(nullptr)
, _right(nullptr)
, _key(key)
{}
};
template <class K>
class BSTree
{
typedef BSTreeNode<K> Node;
public:
bool Insert(const K& key)
{
if (_root == nullptr)
{
_root = new Node(key);
return true;
}
Node* parent = nullptr;
Node* cur = _root;
while (cur)
{
if (cur->_key < key)
{
parent = cur;
cur = cur->_right;
}
else if (cur->_key > key)
{
parent = cur;
cur = cur->_left;
}
else
{
return false;
}
}
cur = new Node(key);
if (parent->_key < key)
{
parent->_right = cur;
}
else
{
parent->_left = cur;
}
return true;
}
bool Find(const K& key)
{
Node* cur = _root;
while (cur)
{
if (cur->_key < key)
{
cur = cur->_right;
}
else if (cur->_key > key)
{
cur = cur->_left;
}
else
{
return true;
}
}
return true;
}
bool Erase(const K& key)
{
Node* parent = nullptr;
Node* cur = _root;
while (cur)
{
if (cur->_key < key)
{
parent = cur;
cur = cur->_right;
}
else if (cur->_key > key)
{
parent = cur;
cur = cur->_left;
}
else
{
// 刪除分情況討論:
// 1. 左為空(左右都為空);2. 右為空;3.左右都不為空
if (cur->_left == nullptr)// 左為空
{
if (cur == _root)// 根節(jié)點是刪除的節(jié)點情況
_root = cur->_right;
else
{
if (cur == parent->_left)
{
parent->_left = cur->_right;
}
else
{
parent->_right = cur->_right;
}
}
delete(cur);
}
else if (cur->_right == nullptr)// 右為空
{
if (cur == _root)// 根節(jié)點是刪除的節(jié)點情況
_root = cur->_left;
else
{
if (cur == parent->_left)
{
parent->_left = cur->_left;
}
else
{
parent->_right = cur->_left;
}
}
delete(cur);
}
else// 左右都不為空
{
// 替換法,找左子樹最大 / 右子樹最小來替換cur
Node* parent = cur;
Node* subLeft = cur->_right;
while (subLeft->_left)
{
parent = subLeft;
subLeft = subLeft->_left;
}
swap(cur->_key, subLeft->_key);
if (subLeft == parent->_left)
parent->_left = subLeft->_right;
else// 處理刪除根節(jié)點的情況
parent->_right = subLeft->_right;
delete(subLeft);
}
return true;
}
}
return false;
}
void InOrder()
{
_InOrder(_root);
cout << endl;
}
//遞歸
bool InsertR(const K& key)
{
return _InsertR(_root, key);
}
bool FindR(const K& key)
{
return _FindR(_root, key);
}
bool EraseR(const K& key)
{
return _EraseR(_root, key);
}
// C++11
BSTree() = default; // 強制編譯器生成默認(rèn)構(gòu)造
BSTree(const BSTree<K>& t)
{
_root = Copy(t._root);
}
// 賦值重載,現(xiàn)代寫法
BSTree<K>& operator=(BSTree<K> t)
{
swap(_root, t._root);
return *this;
}
~BSTree()
{
Destroy(_root);
}
private:
Node* Copy(Node* root)
{
if (root == nullptr)
return nullptr;
Node* newRoot = new Node(root->_key);
newRoot->_left = Copy(root->_left);
newRoot->_right = Copy(root->_right);
return newRoot;
}
void Destroy(Node*& root)
{
if (root == nullptr)
return;
Destroy(root->_left);
Destroy(root->_right);
delete root;
root = nullptr;
}
bool _InsertR(Node*& root, const K& key)
{
if (root == nullptr)
{
root = new Node(key);
return true;
}
if (root->_key < key)
{
_InsertR(root->_right, key);
}
else if (root->_key > key)
{
_InsertR(root->_left, key);
}
else
{
return false;
}
}
bool _FindR(Node* root, const K& key)
{
if (root == nullptr)
{
return false;
}
if (root->_key < key)
{
_FindR(root->_right, key);
}
else if (root->_key > key)
{
_FindR(root->_left, key);
}
else
{
return true;
}
}
bool _EraseR(Node*& root, const K& key)
{
if (root == nullptr)
return false;
if (root->_key < key)
{
_EraseR(root->_right, key);
}
else if (root->_key > key)
{
_EraseR(root->_left, key);
}
else
{
if (root->_left == nullptr)
{
Node* del = root;
root = root->_right;
delete(del);// 必須釋放,不釋放會導(dǎo)致內(nèi)存泄漏
return true;
}
else if (root->_right == nullptr)
{
Node* del = root;
root = root->_left;
delete(del);
return true;
}
else
{
Node* subLeft = root->_right;
while (subLeft->_left)
{
subLeft = subLeft->_left;
}
swap(root->_key, subLeft->_key);
return _EraseR(root->_right, key);
}
}
}
void _InOrder(Node* root)
{
if (root == nullptr)
return;
_InOrder(root->_left);
cout << root->_key << " ";
_InOrder(root->_right);
}
private:
Node* _root = nullptr;
};
};
4.2 KV模型的模擬實現(xiàn)
namespace kv
{
template <class K, class V>
struct BSTreeNode
{
BSTreeNode<K, V>* _left;
BSTreeNode<K, V>* _right;
K _key;
V _val;
BSTreeNode(const K& key, const V& val)
:_left(nullptr)
, _right(nullptr)
, _key(key)
, _val(val)
{}
};
template <class K, class V>
class BSTree
{
typedef BSTreeNode<K, V> Node;
public:
bool Insert(const K& key, const V& val)
{
if (_root == nullptr)
{
_root = new Node(key, val);
return true;
}
Node* parent = nullptr;
Node* cur = _root;
while (cur)
{
if (cur->_key < key)
{
parent = cur;
cur = cur->_right;
}
else if (cur->_key > key)
{
parent = cur;
cur = cur->_left;
}
else
{
return false;
}
}
cur = new Node(key, val);
if (parent->_key < key)
{
parent->_right = cur;
}
else
{
parent->_left = cur;
}
return true;
}
Node* Find(const K& key)
{
Node* cur = _root;
while (cur)
{
if (cur->_key < key)
{
cur = cur->_right;
}
else if (cur->_key > key)
{
cur = cur->_left;
}
else
{
return cur;
}
}
return nullptr;
}
bool Erase(const K& key)
{
Node* parent = nullptr;
Node* cur = _root;
while (cur)
{
if (cur->_key < key)
{
parent = cur;
cur = cur->_right;
}
else if (cur->_key > key)
{
parent = cur;
cur = cur->_left;
}
else
{
// 刪除分情況討論:
// 1. 左為空(左右都為空);2. 右為空;3.左右都不為空
if (cur->_left == nullptr)// 左為空
{
if (cur == _root)// 根節(jié)點是刪除的節(jié)點情況
_root = cur->_right;
else
{
if (cur == parent->_left)
{
parent->_left = cur->_right;
}
else
{
parent->_right = cur->_right;
}
}
delete(cur);
}
else if (cur->_right == nullptr)// 右為空
{
if (cur == _root)// 根節(jié)點是刪除的節(jié)點情況
_root = cur->_left;
else
{
if (cur == parent->_left)
{
parent->_left = cur->_left;
}
else
{
parent->_right = cur->_left;
}
}
delete(cur);
}
else// 左右都不為空
{
// 替換法,找左子樹最大 / 右子樹最小來替換cur
Node* parent = cur;
Node* subLeft = cur->_right;
while (subLeft->_left)
{
parent = subLeft;
subLeft = subLeft->_left;
}
swap(cur->_key, subLeft->_key);
if (subLeft == parent->_left)
parent->_left = subLeft->_right;
else// 處理刪除根節(jié)點的情況
parent->_right = subLeft->_right;
delete(subLeft);
}
return true;
}
}
return false;
}
void InOrder()
{
_InOrder(_root);
cout << endl;
}
private:
void _InOrder(Node* root)
{
if (root == nullptr)
return;
_InOrder(root->_left);
cout << root->_key << " : " << root->_val << endl;
_InOrder(root->_right);
}
private:
Node* _root = nullptr;
};
};
到了這里,關(guān)于[數(shù)據(jù)結(jié)構(gòu)進(jìn)階 C++] 二叉搜索樹(BinarySearchTree)的模擬實現(xiàn)的文章就介紹完了。如果您還想了解更多內(nèi)容,請在右上角搜索TOY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!