前言
大家好吖,歡迎來到 YY 滴數(shù)據(jù)結(jié)構(gòu)系列 ,熱烈歡迎! 本章主要內(nèi)容面向接觸過C++的老鐵
主要內(nèi)容含:
歡迎訂閱 YY滴C++專欄!更多干貨持續(xù)更新!以下是傳送門!
- YY的《C++》專欄
- YY的《C++11》專欄
- YY的《Linux》專欄
- YY的《數(shù)據(jù)結(jié)構(gòu)》專欄
- YY的《C語言基礎(chǔ)》專欄
- YY的《初學(xué)者易錯點》專欄
- YY的《小小知識點》專欄
一.二叉搜索樹的基本概念
二叉搜索樹又稱二叉排序樹,它或者是一棵空樹,或者是具有以下性質(zhì)的二叉樹:
- 若它的左子樹不為空,則 左子樹 上所有節(jié)點的值都 小于 根節(jié)點的值
- 若它的右子樹不為空,則 右子樹 上所有節(jié)點的值都 大于 根節(jié)點的值
- 它的 左右子樹 也分別為二叉搜索樹 ;
![]()
二.增刪查改基本操作
文章來源:http://www.zghlxwxcb.cn/news/detail-755349.html
//結(jié)點模板
template<class K>
struct BSTreeNode
{
BSTreeNode<K>* _left;
BSTreeNode<K>* _right;
K _key;
BSTreeNode(const K& key)
:_left(nullptr)
,_right(nullptr)
,_key(key)
{}
};
//在二叉搜索樹模板中
typedef BSTreeNode<K> Node;
1.二叉搜索樹的查找(分析&代碼演示)
分析
- 從根開始比較,查找,比根大則往右邊走查找,比根小則往左邊走查找
- 最多查找高度次 ,走到到空,還沒找到,這個值不存在。
代碼演示
//查找操作
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;//最多查找高度次 ,走到到空,還沒找到,這個值不存在。
}
2.二叉搜索樹的插入(分析&代碼演示)
分析
- 樹為空,則直接新增節(jié)點,賦值給root指針
- 樹不空, 按二叉搜索樹性質(zhì)的查找方式(前后指針) 找到插入位置,插入新節(jié)點
代碼演示
//插入操作
bool Insert(const K& key)
{
//樹為空,則直接新增節(jié)點,賦值給root指針
if (_root == nullptr)
{
_root = new Node(key);
return true;
}
//樹不空, 按二叉搜索樹性質(zhì)的查找方式(前后指針) 找到插入位置,插入新節(jié)點
Node* parent = nullptr;//后指針
Node* cur = _root;//前指針
while (cur)
{
if (cur->_key < key)//比keycur的_key大,往右走
{
parent = cur;
cur = cur->_right;
}
else if(cur->_key > key)//比keycur的_key小,往左走
{
parent = cur;
cur = cur->_left;
}
else
{
return false;
}
}
//cur走到空了,開始給插入的key值創(chuàng)建結(jié)點,根據(jù)其比后一個結(jié)點(parent)大還是小,決定其是插在左還是右
cur = new Node(key);
if (parent->_key < key)
{
parent->_right = cur;
}
else
{
parent->_left = cur;
}
return true;
}
3.二叉搜索樹的刪除【※核心重點】(分析&代碼演示)
分析
- 首先查找元素是否在二叉搜索樹中,如果不存在,則返回
- 否則要刪除的結(jié)點可能分下面四種情況:
- 要刪除的結(jié)點無孩子結(jié)點
- 要刪除的結(jié)點只有左孩子結(jié)點
- 要刪除的結(jié)點只有右孩子結(jié)點
- 要刪除的結(jié)點有左、右孩子結(jié)點
![]()
- 對上面四種情況整理后(1與2,3分別結(jié)合),剩下下面2種情況(直接刪除,替換法),分出3種具體情況(直接刪除占兩種):
![]()
- 直接刪除情況: 只有左/右/無孩子結(jié)點(無孩子,只有一個孩子)
(雙親結(jié)點指向被刪除節(jié)點的左還是右————取決于被刪除節(jié)點是其雙親節(jié)點的左還是右節(jié)點)- 情況1:被刪除節(jié)點是其雙親節(jié)點的左節(jié)點,刪除該結(jié)點且使被刪除節(jié)點的雙親結(jié)點指向被刪除節(jié)點的 左孩子 結(jié)點
- 情況2:被刪除節(jié)點是其雙親節(jié)點的右節(jié)點,刪除該結(jié)點且使被刪除節(jié)點的雙親結(jié)點指向被刪除結(jié)點的 右孩子 結(jié)點
![]()
- 還要考慮結(jié)點為根結(jié)點情況:
![]()
- 替換法情況:【※核心難點】 (有兩個孩子)
- 情況3 :在它的右子樹中尋找中序下的第一個結(jié)點(關(guān)鍵碼最小),用它的值填補到被刪除節(jié)點中,再來處理該結(jié)點的刪除問題
- 分析:要 找到左子樹的最大(右)結(jié)點,或者 右子樹的最小(左)結(jié)點(下圖演示中是找到左子樹的最大結(jié)點)
- 具體過程分析:
- 設(shè)置前后指針,留一個cur指針指向要刪除結(jié)點,parent指針跟著LeftMax指針向下逐個移動
- 找到leftMax以后,交換其和cur的數(shù)值,(收完尾后,最后一步再將指針也一同轉(zhuǎn)移)
- 要分為兩種情況(如下圖所示) (1) leftMax指針的左指針為空,(2) leftMax指針的左指針不為空
(為什么不用討論右指針呢?因為leftMax的右指針必定為空,否則leftMax會繼續(xù)向下移動)- 因為采用的是前后指針法,所以這時留下的后指針(parent)就對應(yīng)指向leftMax的左/右結(jié)點
- 最后將cur指針指向leftMax,leftMax動不動無所謂
![]()
代碼演示
//刪除操作
bool Erase(const K& key)
{
Node* parent = nullptr;//后指針
Node* cur = _root;//前指針
while (cur)
{
//通過二叉搜索樹規(guī)則向下查找
if (cur->_key < key)
{
parent = cur;
cur = cur->_right;
}
else if (cur->_key > key)
{
parent = cur;
cur = cur->_left;
}
//直接刪除情況:只有左/右/無孩子結(jié)點
//(雙親結(jié)點指向被刪除節(jié)點的左還是右————取決于被刪除節(jié)點是其雙親節(jié)點的左還是右節(jié)點)
else // 找到了
{
// 左為空
if (cur->_left == nullptr)
{
if (cur == _root)
{
_root = cur->_right;
}
else
{
if (parent->_right == cur)//被刪除節(jié)點是其雙親節(jié)點的右節(jié)點
{
parent->_right = cur->_right;//刪除該結(jié)點且使被刪除節(jié)點的雙親結(jié)點指向被刪除結(jié)點的 右孩子 結(jié)點
}
else//被刪除節(jié)點是其雙親節(jié)點的左節(jié)點
{
parent->_left = cur->_right;//刪除該結(jié)點且使被刪除節(jié)點的雙親結(jié)點指向被刪除結(jié)點的 左孩子 結(jié)點
}
}
}
// 右為空
else if (cur->_right == nullptr)
{
if (cur == _root)
{
_root = cur->_left;
}
else
{
if (parent->_right == cur)
{
parent->_right = cur->_left;
}
else
{
parent->_left = cur->_left;
}
}
}
// 替換法情況:左右都不為空
else
{
// 找替代節(jié)點
Node* parent = cur;
Node* leftMax = cur->_left;
while (leftMax->_right)
{
parent = leftMax;
leftMax = leftMax->_right;
}
swap(cur->_key, leftMax->_key);
if (parent->_left == leftMax)
{
parent->_left = leftMax->_left;
}
else
{
parent->_right = leftMax->_left;
}
cur = leftMax;
}
delete cur;
return true;
}
}
return false;
}
4.二叉搜索樹的中序遍歷(分析&代碼演示)
分析
- 中序遍歷要從通過模板實例化的樹中調(diào)用中序遍歷函數(shù)
- 需要傳根結(jié)點指針,但是 根結(jié)點指針是在private域中,域外不能直接傳一個根結(jié)點指針 ,所以要引入_InOrder函數(shù),在二叉搜索樹模板中 再次封裝一層
代碼演示
void TestBSTree1()
{
int a[] = { 8, 3, 1, 10, 6, 4, 7, 14, 13 };
BSTree<int> t;
for (auto e : a)
{
t.Insert(e);
}
t.InOrder(); //需要傳根結(jié)點指針,但是根結(jié)點指針是在private域中,域外不能直接傳一個根結(jié)點指針,
//所以要引入_InOrder函數(shù),在二叉搜索樹模板中再次封裝一層
}
//中序遍歷——————————————————————————————————————————為了解決中序要傳入根節(jié)點的問題,引入_InOrder函數(shù)
void InOrder()
{
_InOrder(_root);
cout << endl;
}
void _InOrder(Node* root)
{
if (root == NULL)
{
return;
}
_InOrder(root->_left);
cout << root->_key << " ";
_InOrder(root->_right);
}
三.二叉搜索樹的性能問題:需要AVL樹…紅黑樹…
- 插入和刪除操作都必須先 查找,查找效率代表了二叉搜索樹中各個操作的性能
- 當(dāng)二叉搜索樹 退化為單支時,其效率為O(N),二叉搜索樹的性能就失去了
- 對二叉搜索樹進行改進后,得到的AVL樹紅黑樹效率為 Log(N)
四.二叉搜索樹的完整實現(xiàn)代碼演示
//結(jié)點模板
template<class K>
struct BSTreeNode
{
BSTreeNode<K>* _left;
BSTreeNode<K>* _right;
K _key;
BSTreeNode(const K& key)
:_left(nullptr)
,_right(nullptr)
,_key(key)
{}
};
//二叉搜索樹類模板
template<class K>
class BSTree
{
typedef BSTreeNode<K> Node;
public:
//初始化列表
BSTree()
:_root(nullptr)
{}
//查找操作
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 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 Erase(const K& key)
{
Node* parent = nullptr;//后指針
Node* cur = _root;//前指針
while (cur)
{
//通過二叉搜索樹規(guī)則向下查找
if (cur->_key < key)
{
parent = cur;
cur = cur->_right;
}
else if (cur->_key > key)
{
parent = cur;
cur = cur->_left;
}
//直接刪除情況:只有左/右/無孩子結(jié)點
//(雙親結(jié)點指向被刪除節(jié)點的左還是右————取決于被刪除節(jié)點是其雙親節(jié)點的左還是右節(jié)點)
else // 找到了
{
// 左為空
if (cur->_left == nullptr)
{
if (cur == _root)
{
_root = cur->_right;
}
else
{
if (parent->_right == cur)//被刪除節(jié)點是其雙親節(jié)點的右節(jié)點
{
parent->_right = cur->_right;//刪除該結(jié)點且使被刪除節(jié)點的雙親結(jié)點指向被刪除結(jié)點的 右孩子 結(jié)點
}
else//被刪除節(jié)點是其雙親節(jié)點的左節(jié)點
{
parent->_left = cur->_right;//刪除該結(jié)點且使被刪除節(jié)點的雙親結(jié)點指向被刪除結(jié)點的 左孩子 結(jié)點
}
}
}
// 右為空
else if (cur->_right == nullptr)
{
if (cur == _root)
{
_root = cur->_left;
}
else
{
if (parent->_right == cur)
{
parent->_right = cur->_left;
}
else
{
parent->_left = cur->_left;
}
}
}
// 替換法情況:左右都不為空
else
{
// 找替代節(jié)點
Node* parent = cur;
Node* leftMax = cur->_left;
while (leftMax->_right)
{
parent = leftMax;
leftMax = leftMax->_right;
}
swap(cur->_key, leftMax->_key);
if (parent->_left == leftMax)
{
parent->_left = leftMax->_left;
}
else
{
parent->_right = leftMax->_left;
}
cur = leftMax;
}
delete cur;
return true;
}
}
return false;
}
//中序遍歷——————————————————————————————————————————為了解決中序要傳入根節(jié)點的問題,引入_InOrder函數(shù)
void InOrder()
{
_InOrder(_root);
cout << endl;
}
void _InOrder(Node* root)
{
if (root == NULL)
{
return;
}
_InOrder(root->_left);
cout << root->_key << " ";
_InOrder(root->_right);
}
private:
Node* _root;
};
void TestBSTree1()
{
int a[] = { 8, 3, 1, 10, 6, 4, 7, 14, 13 };
BSTree<int> t;
for (auto e : a)
{
t.Insert(e);
}
t.InOrder();
t.Erase(4);
t.InOrder();
t.Erase(6);
t.InOrder();
t.Erase(7);
t.InOrder();
t.Erase(3);
t.InOrder();
for (auto e : a)
{
t.Erase(e);
}
t.InOrder();
}
五.進階二叉樹習(xí)題傳送門
進階二叉樹習(xí)題傳送門文章來源地址http://www.zghlxwxcb.cn/news/detail-755349.html
到了這里,關(guān)于【C++&數(shù)據(jù)結(jié)構(gòu)】超詳細一文帶小白輕松全面理解 [ 二叉搜索樹 ]—— [從零實現(xiàn)&逐過程分析&代碼演示&簡練易懂](23)的文章就介紹完了。如果您還想了解更多內(nèi)容,請在右上角搜索TOY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!