??專欄導(dǎo)讀
??作者簡介:花想云,在讀本科生一枚,致力于 C/C++、Linux 學(xué)習(xí)。
??本文收錄于 C++系列,本專欄主要內(nèi)容為 C++ 初階、C++ 進(jìn)階、STL 詳解等,專為大學(xué)生打造全套 C++ 學(xué)習(xí)教程,持續(xù)更新!
??相關(guān)專欄推薦:C語言初階系列 、C語言進(jìn)階系列 、數(shù)據(jù)結(jié)構(gòu)與算法、Linux從入門到精通
??文章導(dǎo)讀
本章我們將認(rèn)識一種新的二叉樹——搜索二叉樹
。這棵樹有個神奇的功能就是會對數(shù)據(jù)自動排序且有著非常高的查找效率。搜索二叉樹作為set、map
的基礎(chǔ)結(jié)構(gòu),同樣又是接下來將要學(xué)到的AVL樹
以及紅黑樹
的實現(xiàn)基礎(chǔ)非常值得我們?nèi)ド钊雽W(xué)習(xí)~
??搜索二叉樹概念
二叉搜索樹本質(zhì)上也是一種二叉樹,只不過多了一個約束規(guī)則
——
若一棵二叉樹不為空
,則:
- 若它的左子樹不為空,則
左子樹
上所有節(jié)點的值都小于
根節(jié)點的值; - 若它的右子樹不為空,則
右子樹
上所有節(jié)點的值都大于
根節(jié)點的值; - 它的左右子樹也分別為搜索二叉樹;
所以構(gòu)建一個搜索二叉樹,只需要在插入結(jié)點時滿足約束規(guī)則即可。
??二叉搜索樹的構(gòu)建
與二叉樹相同,二叉搜索樹由一個個結(jié)點鏈接而成。每個結(jié)點包含三個成員——
-
_left
(左孩子); -
_right
(有孩子); -
_key
(鍵值);
所以首先定義一個BSTNode
(Binary Search Tree簡寫)結(jié)構(gòu)體——
template <class K>
struct BSTreeNode
{
BSTreeNode* _left;
BSTreeNode* _right;
K _key;
BSTreeNode(const K& key) // 構(gòu)造函數(shù)
:_left(nullptr)
, _right(nullptr)
, _key(key)
{}
};
同樣的,再定義一個搜索二叉樹的類,即class BSTree
——
template <class K>
class BSTree
{
typedef BSTreeNode<K> Node;
public:
// 成員函數(shù)的實現(xiàn)
// 插入、刪除、查找...
private:
Node* _root = nullptr;
};
接著就是各種成員函數(shù)的實現(xiàn)了~
??查找操作
搜索二叉樹的查找比較簡單而且更容易幫助我們理解搜索二叉樹的性質(zhì),所以先從查找入手。
以上圖為例,倘若我們要查找 7
,具體的思路是這樣的——
-
7 < 8
,因此去8
的左子樹去查找; -
7 > 3
,因此去3
的右子樹去查找; -
7 > 6
,因此去6
的右子樹去查找; -
7 = 7
,找到了,返回true
;
于是我們試著著手實現(xiàn)一個Find
函數(shù)。
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; // 找到返回true
}
return false; // 未找到返回false
}
??插入操作
理解了如何查找,插入也就非常簡單。
還是以此圖為例,倘若我們要插入 9
,具體步驟為——
- 首先確定
cur
的位置,并隨時更新parent
; - 最終,
cur
走到10
的左節(jié)點的位置,即cur = nullptr
,循環(huán)結(jié)束; - 此時
patent = Node*(10)
; - 最后一步,
new
一個結(jié)點Node*(key)
并賦值給parent->_left
即可。
bool Insert(const K& key)
{
// 如果是第一次插入,直接new一個新結(jié)點給_root
if (_root == nullptr)
{
_root = new Node(key);
return true;
}
Node* cur = _root; // cur用來定位插入的位置
Node* parent = cur; // 記錄parent的父親
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);
// 插入時依舊要進(jìn)行判斷
if (parent->_key < key)
parent->_right = cur;
else
parent->_left = cur;
return true;
}
??刪除操作
二叉搜索樹的刪除是最精華的部分。對與葉子節(jié)點,例如4、7、13
,刪除非常簡單,只需將自身的位置替換為nullptr
即可。
如果要刪除14
或者10
,也是比較簡單的,因為10
的左右子樹只有一方為nullptr
(10的左子樹為空),所以只需要載刪除的時候讓父結(jié)點接管自己不為空的子樹
即可。
倘若要刪除6
或者3
,由于它們的左右子樹都不為空,刪除時無法將兩個子樹都交給父結(jié)點,情況就較為復(fù)雜。
所以此種情況,我們只能想辦法請一個人來接替自己的位置
,但是并不是誰來都能勝任這個位置的。這個接替者必須滿足二叉搜索樹的條件——左子樹都比它小,右子樹都比它大
。那么這個接替者的人選只能有這兩個——
- 左子樹的最大(最右)節(jié)點;
- 或右子樹的最?。ㄗ钭螅┕?jié)點;
例如,倘若要刪除3
,此時有兩種做法都可行——
- 用
1
替換3
; - 用
7
替換3
;
綜上所述,刪除操作共分為一下幾種情況——
- 左子樹為空;
- 右子樹為空;
- 左右子樹都不為空 ;
- (左右子樹都為空其實可以歸并到1或2的情況中);
bool Erase(const K& key)
{
Node* cur = _root;
Node* parent = cur;
while (cur)
{
// 找到值為key的結(jié)點
if (cur->_key < key)
{
parent = cur;
cur = cur->_right;
}
else if (cur->_key > key)
{
parent = cur;
cur = cur->_left;
}
else // 找到了
{
// 刪除
if (cur->_left == nullptr) // 1.左子樹為空
{
if (cur == _root) // 根節(jié)點的刪除
{
_root = cur->_right;
return true;
}
else
{
if (parent->_left == cur)
parent->_left = cur->_right;
else
parent->_right = cur->_right;
delete cur;
}
}
else if (cur->_right == nullptr) // 2.右子樹為空
{
if (cur == _root) // 根節(jié)點的刪除
{
_root = cur->_left;
return true;
}
else
{
if (parent->_left == cur)
parent->_left = cur->_left;
else
parent->_right = cur->_left;
delete cur;
}
}
else // 左右子樹都不為空
{
// 找左子樹的最大結(jié)點 或者 右子樹的最小結(jié)點
Node* minRight = cur->_right;
Node* pminRight = cur;
while (minRight->_left)
{
pminRight = minRight;
minRight = minRight->_left;
}
cur->_key = minRight->_key; // 替換
if (pminRight->_left == minRight)
{
pminRight->_left = minRight->_right;
}
else
{
pminRight->_right = minRight->_right;
}
delete minRight;
}
return true;
}
}
return false;
}
??遍歷操作
最后,二叉搜索樹的遍歷非常簡單,就是之前學(xué)習(xí)過的二叉樹的中序遍歷
。
void InOrder()
{
_InOrder(_root);
cout << endl;
}
void _InOrder(Node* root)
{
if (root == nullptr)
return;
_InOrder(root->_left);
cout << root->_key << ' ';
_InOrder(root->_right);
}
注:由于調(diào)用函數(shù)時C++封裝的特性,需設(shè)計兩個函數(shù),InOrder
接口對外提供,-—_InOrder
不對外提供。
??測試
我們可以構(gòu)建一棵這樣的搜索二叉樹,再對每一個結(jié)點進(jìn)行刪除操作,驗證代碼是否正確~
void BTreeTest()
{
BSTree<int> tree;
int a[] = { 8, 3, 1, 10, 6, 4, 7, 14, 13 };
for (auto e : a)
{
tree.InsertR(e);
}
for (auto e : a)
{
tree.EraseR(e);
tree.InOrder();
}
}
???拓展——遞歸實現(xiàn)
對于搜索二叉樹來說,上面實現(xiàn)的非遞歸版本是比遞歸版本更優(yōu)的。此處的遞歸實現(xiàn)完全屬于多余了,但是作為拓展內(nèi)容看一看也未嘗不可。
??遞歸查找
bool FindR(const K& key)
{
return _FindR(_root, key);
}
bool _FindR(Node* root, const K& key)
{
if (root == nullptr)
return false;
if (root->_key == key)
return true;
if (root->_key > key)
_FindR(root->_left, key);
else
_FindR(root->_right, key);
}
??遞歸插入
bool EraseR(const K& key)
{
return _EraseR(_root, key);
}
bool _InsertR(Node*& root, const K& key)
{
if (root == nullptr)
{
root = new Node(key);
return true;
}
if (root->_key < key)
return _InsertR(root->_right, key);
else if (root->_key > key)
return _InsertR(root->_left, key);
else
return false;
}
??遞歸刪除
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)
return _EraseR(root->_right, key);
else if (root->_key > key)
return _EraseR(root->_left, key);
else
{
Node* del = root;
if (root->_left == nullptr)
root = root->_right;
else if (root->_right == nullptr)
root = root->_left;
else
{
Node* maxLeft = root->_left;
while (maxLeft->_right)
maxLeft = maxLeft->_right;
std::swap(root->_key, maxLeft->_key);
return _EraseR(root->_left, key);
}
delete del;
return true;
}
}
??完整源碼
??非遞歸版
#include<iostream>
using namespace std;
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:
BSTree() = default;
bool Insert(const K& key)
{
if (_root == nullptr)
{
_root = new Node(key);
return true;
}
Node* cur = _root;
Node* parent = cur;
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 false;
}
bool Erase(const K& key)
{
Node* cur = _root;
Node* parent = cur;
while (cur)
{
if (cur->_key < key)
{
parent = cur;
cur = cur->_right;
}
else if (cur->_key > key)
{
parent = cur;
cur = cur->_left;
}
else
{
// 刪除
if (cur->_left == nullptr)
{
if (cur == _root)
{
_root = cur->_right;
return true;
}
else
{
if (parent->_left == cur)
parent->_left = cur->_right;
else
parent->_right = cur->_right;
delete cur;
}
}
else if (cur->_right == nullptr)
{
if (cur == _root)
{
_root = cur->_left;
return true;
}
else
{
if (parent->_left == cur)
parent->_left = cur->_left;
else
parent->_right = cur->_left;
delete cur;
}
}
else
{
// 找左子樹的最大結(jié)點 或者 右子樹的最小結(jié)點
Node* minRight = cur->_right;
Node* pminRight = cur;
while (minRight->_left)
{
pminRight = minRight;
minRight = minRight->_left;
}
cur->_key = minRight->_key;
if (pminRight->_left == minRight)
{
pminRight->_left = minRight->_right;
}
else
{
pminRight->_right = minRight->_right;
}
delete minRight;
}
return true;
}
}
return false;
}
void InOrder()
{
_InOrder(_root);
cout << endl;
}
protected:
void _InOrder(Node* root)
{
if (root == nullptr)
return;
_InOrder(root->_left);
cout << root->_key << ' ';
_InOrder(root->_right);
}
private:
Node* _root = nullptr;
};
??遞歸版本
#pragma once
#include<iostream>
using namespace std;
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:
BSTree() = default;
void InOrder()
{
_InOrder(_root);
cout << endl;
}
bool FindR(const K& key)
{
return _FindR(_root, key);
}
bool InsertR(const K& key)
{
return _InsertR(_root, key);
}
bool EraseR(const K& key)
{
return _EraseR(_root, key);
}
protected:
bool _FindR(Node* root, const K& key)
{
if (root == nullptr)
return false;
if (root->_key == key)
return true;
if (root->_key > key)
_FindR(root->_left, key);
else
_FindR(root->_right, key);
}
bool _EraseR(Node*& root,const K& key)
{
if (root == nullptr)
return false;
if (root->_key < key)
return _EraseR(root->_right, key);
else if (root->_key > key)
return _EraseR(root->_left, key);
else
{
Node* del = root;
if (root->_left == nullptr)
root = root->_right;
else if (root->_right == nullptr)
root = root->_left;
else
{
Node* maxLeft = root->_left;
while (maxLeft->_right)
maxLeft = maxLeft->_right;
std::swap(root->_key, maxLeft->_key);
return _EraseR(root->_left, key);
}
delete del;
return true;
}
}
bool _InsertR(Node*& root, const K& key)
{
if (root == nullptr)
{
root = new Node(key);
return true;
}
if (root->_key < key)
return _InsertR(root->_right, key);
else if (root->_key > key)
return _InsertR(root->_left, key);
else
return false;
}
void _InOrder(Node* root)
{
if (root == nullptr)
return;
_InOrder(root->_left);
cout << root->_key << ' ';
_InOrder(root->_right);
}
private:
Node* _root = nullptr;
};
?抽獎活動?
抽獎文章鏈接——Spring Cloud——演進(jìn)與應(yīng)用的分布式系統(tǒng)開發(fā)利器(文末贈書3冊)
?感謝贊助?
618,清華社 IT BOOK 多得圖書活動開始啦!活動時間為2023年6月7日至6月18日,清華社為您精選多款高分好書,涵蓋了C++、Java、Python、前端、后端、數(shù)據(jù)庫、算法與機(jī)器學(xué)習(xí)等多個IT開發(fā)領(lǐng)域,適合不同層次的讀者。全場5折,掃碼領(lǐng)券更有優(yōu)惠哦!文章來源:http://www.zghlxwxcb.cn/news/detail-479944.html
優(yōu)惠購書請戳這里
文章來源地址http://www.zghlxwxcb.cn/news/detail-479944.html
到了這里,關(guān)于〖數(shù)據(jù)結(jié)構(gòu)〗一棵有點自律的樹——搜索二叉樹的文章就介紹完了。如果您還想了解更多內(nèi)容,請在右上角搜索TOY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!