C++二叉搜索樹模擬實現(xiàn)示例
一、二叉搜索樹的概念
搜索二叉樹結(jié)構(gòu)上跟普通的二叉樹一樣,它要么是一棵空樹,要么是具有以下性質(zhì)的二叉樹:
- 若它的左子樹不為空,則左子樹上所有節(jié)點的值都小于根節(jié)點的值
- 若它的右子樹不為空,則右子樹上所有節(jié)點的值都大于根節(jié)點的值
- 它的左右子樹也分別為二叉搜索樹
如下圖所示,這就是一顆二叉搜索樹。我們可以發(fā)現(xiàn)這顆樹的中序遍歷就是有序的
二、二叉搜索樹的結(jié)構(gòu)
首先我們肯定需要樹節(jié)點,來幫助我們保持樹的結(jié)構(gòu)和存放數(shù)據(jù)。
如下代碼我們就使用模板定義了樹的結(jié)構(gòu),并添加了構(gòu)造函數(shù),方便我們創(chuàng)建結(jié)點
template<class T> struct BSTreeNode { BSTreeNode<T>* _left; BSTreeNode<T>* _right; T _key; BSTreeNode(const T& key) :_left(nullptr) ,_right(nullptr) ,_key(key) {} };
二叉樹的成員變量只有根結(jié)點
template<class K> class BSTree { typedef BSTreeNode<K> Node; private: Node* _root }
三、二叉搜索樹的操作(非遞歸)
1.插入
我們定義了一個數(shù)組,現(xiàn)在要將這個數(shù)組的內(nèi)容放到搜索二叉樹里。
int a[] = {8, 3, 1, 10, 6, 4, 7, 14, 13};
就會成為如下這棵樹
依次插入0和16
插入部分代碼還算簡單,首先就判斷當(dāng)前樹是否為空,通過成員變量根結(jié)點來判斷,根節(jié)點為空就直接new一個結(jié)點賦值給根結(jié)點返回true。
我們可以通過key值來比較往左走還是往右走,如果插入的key值比當(dāng)前結(jié)點的值小,就往左走,如果比當(dāng)前結(jié)點的值大,就往右走,如果相等,就證明你找到了結(jié)點,我們返回false,代表插入失敗(二叉搜索樹為了保持他的結(jié)構(gòu),是不能有相等的key的)。
- 目前我們已經(jīng)找到了這個結(jié)點應(yīng)該存放的位置,那么我們?nèi)绾螌⑺麄冩溄悠饋砟兀?/li>
這就需要一個父結(jié)點來幫助我們處理,讓父結(jié)點一直跟隨著當(dāng)前結(jié)點走。直到找到應(yīng)該存在的地方之后,再判斷這個地方是父親的左還是右(同樣是用key來比較,key比父結(jié)點的值小,放在父結(jié)點的左邊,大就放在父結(jié)點的右邊,通過new一個結(jié)點來完成)最后返回true。
代碼如下
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->_left; } else if (cur->_key < key) { parent = cur; cur = cur->_right; } else { return false; } } if (parent->_key > key) { parent->_left = new Node(key); } else { parent->_right = new Node(key); } return true; }
2.查找
查找是插入的簡潔版,依然用key來比較,找到了返回true,沒找到就返回false
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; }
3.刪除
刪除部分是二叉搜索樹較難的地方,因為我們得讓這棵樹刪除之后依然保持原來的特性,也就是
- 若它的左子樹不為空,則左子樹上所有節(jié)點的值都小于根節(jié)點的值
- 若它的右子樹不為空,則右子樹上所有節(jié)點的值都大于根節(jié)點的值
- 它的左右子樹也分別為二叉搜索樹
因此我們得考慮很多種情況
首先查找元素是否在二叉搜索樹中,如果不存在,則返回, 否則要刪除的結(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é)點的刪除問題--替換法刪除
對于情況b和c,我們首先要判斷找到的結(jié)點是否為根節(jié)點,如果是,根節(jié)點就會往相應(yīng)方向走
左為空
右為空也是同理,我們就不多贅述了。
對于情況d我們使用替換法刪除,比如刪除4,我們可以拿4的右子樹中的最小值(也就是3)和4交換,交換之后再去刪除下面那個結(jié)點就行。這樣也不會破壞數(shù)的結(jié)構(gòu),
當(dāng)然你選擇刪除節(jié)點的左子樹的最大值也是可行的
知道了先交換之后,我們也要分情況來看,判斷右樹的第一個節(jié)點是否為最左節(jié)點
代碼如下
bool Erase(const K& key) { Node* parent = _root; Node* cur = _root; while (cur) { if (cur->_key > key) { parent = cur; cur = cur->_left; } else if (cur->_key < key) { parent = cur; cur = cur->_right; } else { //左為空 if (cur->_left == nullptr) { //判斷是否為根節(jié)點 if (_root == cur) { _root = cur->_right; } //判斷是父親的左還是父親的右 else if (parent->_left == cur) { parent->_left = cur->_right; } else { parent->_right = cur->_right; } delete cur; } //右為空 else if(cur->_right == nullptr) { //判斷是否為根節(jié)點 if (_root == cur) { _root = cur->_left; } //判斷是父親的左還是父親的右 else if (parent->_left == cur) { parent->_left = cur->_left; } else { parent->_right = cur->_left; } delete cur; } else { parent = cur; //定義右樹的最左節(jié)點 Node* subleft = cur->_right; //往最左走 while (subleft->_left) { parent = subleft; subleft = subleft->_left; } //交換 swap(subleft->_key, cur->_key); //右樹第一個節(jié)點就是最左節(jié)點(沒有進入循環(huán)) if (parent == cur) { parent->_right = subleft->_right; } else { parent->_left = subleft->_right; } delete subleft; } return true; } } return false; }
4.遍歷
對于搜索二叉樹,我們選擇中序遍歷,因為這樣遍歷出來的值是有序的。
這里我們先用非遞歸版本,遞歸版本在后面會寫上。
使用stack來幫助我們操作,這里代碼的重要點就是cur,對cur進行操作防止再次陷入循環(huán)。
循環(huán)著先往最左子樹走,邊走邊入棧,走到為空就結(jié)束循環(huán),當(dāng)前取出的棧頂元素就是最左結(jié)點,先打印,再將 cur = top->_right;往top結(jié)點的右邊走,如此循環(huán),便能中序遍歷。
代碼真的很巧妙,不理解的可以畫遞歸圖來幫助理解。
void InOrder() { stack<Node*> s; Node* cur = _root; while (cur || !s.empty()) { while (cur) { s.push(cur); cur = cur->_left; } Node* top = s.top(); s.pop(); cout << top->_key << " "; cur = top->_right; } cout << endl; }
我們測試一下,代碼正常運行。
四、二叉搜索樹的操作(遞歸)
1.遞歸插入
我們?yōu)榱四軌蜻M行遞歸,需要再寫一個函數(shù),因為無法傳入_root進行遞歸,這里我們將遞歸的函數(shù)用private修飾,防止類外調(diào)用。
代碼邏輯跟普通的差不都,都是通過key來判斷,key比當(dāng)前結(jié)點的值大,就遞歸當(dāng)前節(jié)點的右邊,key比當(dāng)前結(jié)點的值小,就遞歸當(dāng)前節(jié)點的左邊,相等就返回。
重要的點是我們無法找到父親結(jié)點,因此可以選擇引用傳參,你給到的參數(shù)是root->left或者是 root->right,引用傳參傳遞的就是root->left或者是root->right的別名,因此找到合適的位置可以直接 root = new Node(key);
public: bool InsertR(const K& key) { return _InsertR(_root, key); } private: bool _InsertR(Node*& root,const K& key) { if(root==nullptr) { root = new Node(key); return true; } if (root->_key > key) { return _InsertR(root->_left, key); } else if (root->_key < key) { return _InsertR(root->_right, key); } else { return false; } }
2.遞歸查找
依然是用兩個函數(shù),查找代碼比插入更簡單,不多贅述
public: bool FindR(const K& key) { return _FindR(_root,key); } private: bool _FindR(Node* root,const K& key) { if (root == nullptr) return false; if (root->_key > key) { return _FindR(root->_left, key); } else if(root->_key < key) { return _FindR(root->_right, key); } else { return true; } }
3.遞歸刪除
刪除也得使用引用,因為要讓父親結(jié)點指向空,因為傳遞的是別名,不需要讓父親跟隨了,因此代碼可以簡潔不少,情況d(也就是左右都不為空),依然是用之前的交換,交換后再遞歸到右樹去查找即可。
public: bool EraseR(const K& key) { return _EraseR(_root, key); } private: bool _EraseR(Node*& root, const K& key) { if (root == nullptr) { return false; } if (root->_key > key) { return _EraseR(root->_left, key); } else if (root->_key < key) { return _EraseR(root->_right, key); } else { if (root->_left == nullptr) { Node* del = root; root = root->_right; delete del; 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(subleft->_key, root->_key); //到右子樹再去遞歸刪除 return _EraseR(root->_right, key); } } return false; }
4.遞歸遍歷
更是小菜一碟,這是普通二叉樹的操作,遞歸左,打印,遞歸右即可。
public: void InOrderR() { _InOrderR(_root); cout << endl; } private: void _InOrderR(Node* root) { if (root == nullptr) return; _InOrderR(root->_left); cout << root->_key << " "; _InOrderR(root->_right); }
五、二叉搜索樹的默認(rèn)成員函數(shù)
1.拷貝構(gòu)造
想要完成深拷貝,就需要將樹的所有結(jié)點都拷貝一遍,這里用遞歸處理也是很方便的,因此我們定義了一個Copy函數(shù),去進行先序遍歷拷貝。
public: BSTree(const BSTree<K>& bst) { _root = Copy(bst._root); } private: Node* Copy(Node* root) { if (root == nullptr) return nullptr; Node* cur = new Node(root->_key); cur->_left = Copy(root->_left); cur->_right = Copy(root->_right); return cur; }
2.賦值運算符重載
這個很簡單,不要傳遞 const 和 & 直接交換即可。
BSTree<K>& operator=(BSTree<K> bst) { swap(bst._root, _root); return *this; }
3.析構(gòu)函數(shù)
調(diào)用后續(xù)遞歸函數(shù)進行析構(gòu)
public: ~BSTree() { clear(_root); } private: void clear(Node* root) { if (root == nullptr) return; clear(root->_left); clear(root->_right); delete root; }
4.默認(rèn)構(gòu)造函數(shù)
因為我們重寫了構(gòu)造函數(shù),因此編譯器默認(rèn)構(gòu)造函數(shù)的就不提供了,我們隨便寫一份,這里使用了C++11的新特性,default代表默認(rèn)生成
BSTree() = default;
到目前為止,我們的二叉搜索樹就算完成了,以下是代碼
namespace k { template<class T> struct BSTreeNode { BSTreeNode<T>* _left; BSTreeNode<T>* _right; T _key; BSTreeNode(const T& key) :_left(nullptr) ,_right(nullptr) ,_key(key) {} }; template<class K> class BSTree { typedef BSTreeNode<K> Node; public: BSTree() = default; BSTree(const BSTree<K>& bst) { _root = Copy(bst._root); } ~BSTree() { clear(_root); } BSTree<K>& operator=(BSTree<K> bst) { swap(bst._root, _root); return *this; } 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->_left; } else if (cur->_key < key) { parent = cur; cur = cur->_right; } else { return false; } } if (parent->_key > key) { parent->_left = new Node(key); } else { parent->_right = new Node(key); } 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* parent = _root; Node* cur = _root; while (cur) { if (cur->_key > key) { parent = cur; cur = cur->_left; } else if (cur->_key < key) { parent = cur; cur = cur->_right; } else { //左為空 if (cur->_left == nullptr) { //判斷是否為根節(jié)點 if (_root == cur) { _root = cur->_right; } //判斷是父親的左還是父親的右 else if (parent->_left == cur) { parent->_left = cur->_right; } else { parent->_right = cur->_right; } delete cur; } //右為空 else if(cur->_right == nullptr) { //判斷是否為根節(jié)點 if (_root == cur) { _root = cur->_left; } //判斷是父親的左還是父親的右 else if (parent->_left == cur) { parent->_left = cur->_left; } else { parent->_right = cur->_left; } delete cur; } else { parent = cur; //定義右樹的最左節(jié)點 Node* subleft = cur->_right; //往最左走 while (subleft->_left) { parent = subleft; subleft = subleft->_left; } //交換 swap(subleft->_key, cur->_key); //右樹第一個節(jié)點就是最左節(jié)點(沒有進入循環(huán)) if (parent == cur) { parent->_right = subleft->_right; } else { parent->_left = subleft->_right; } delete subleft; } return true; } } return false; } void InOrder() { stack<Node*> s; Node* cur = _root; while (cur||!s.empty()) { while (cur) { s.push(cur); cur = cur->_left; } Node* top = s.top(); s.pop(); cout << top->_key << " "; cur = top->_right; } cout << endl; } void InOrderR() { _InOrderR(_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); } private: void clear(Node* root) { if (root == nullptr) return; clear(root->_left); clear(root->_right); delete root; } Node* Copy(Node* root) { if (root == nullptr) return nullptr; Node* cur = new Node(root->_key); cur->_left = Copy(root->_left); cur->_right = Copy(root->_right); return cur; } void _InOrderR(Node* root) { if (root == nullptr) return; _InOrderR(root->_left); cout << root->_key << " "; _InOrderR(root->_right); } bool _FindR(Node* root,const K& key) { if (root == nullptr) return false; if (root->_key > key) { return _FindR(root->_left, key); } else if(root->_key < key) { return _FindR(root->_right, key); } else { 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->_left, key); } else if (root->_key < key) { return _InsertR(root->_right, key); } else { return false; } } bool _EraseR(Node*& root, const K& key) { if (root == nullptr) { return false; } if (root->_key > key) { return _EraseR(root->_left, key); } else if (root->_key < key) { return _EraseR(root->_right, key); } else { if (root->_left == nullptr) { Node* del = root; root = root->_right; delete del; 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(subleft->_key, root->_key); //到右子樹再去遞歸刪除 return _EraseR(root->_right, key); } } return false; } private: Node* _root = nullptr; }; }
六、二叉搜索樹的KV模型
二叉搜索樹不僅僅有K模型,還有KV模型,我們只需要給結(jié)點多添加上一個value即可
template<class T, class V> struct BSTreeNode { BSTreeNode<T,V>* _left; BSTreeNode<T,V>* _right; T _key; V _value; BSTreeNode(const T& key,const V&value) :_left(nullptr) ,_right(nullptr) ,_key(key) ,_value(value) {} };
代碼邏輯部分都是沒問題的,只有少部分地方需要略做修改,大家直接看代碼吧
namespace kv { template<class T, class V> struct BSTreeNode { BSTreeNode<T,V>* _left; BSTreeNode<T,V>* _right; T _key; V _value; BSTreeNode(const T& key,const V&value) :_left(nullptr) ,_right(nullptr) ,_key(key) ,_value(value) {} }; template<class K, class V> class BSTree { typedef BSTreeNode<K, V> Node; public: bool Insert(const K& key,const V& value) { if (_root == nullptr) { _root = new Node(key,value); return true; } Node* parent = nullptr; Node* cur = _root; while (cur) { if (cur->_key > key) { parent = cur; cur = cur->_left; } else if (cur->_key < key) { parent = cur; cur = cur->_right; } else { return false; } } if (parent->_key > key) { parent->_left = new Node(key,value); } else { parent->_right = new Node(key, value); } 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 = _root; Node* cur = _root; while (cur) { if (cur->_key > key) { parent = cur; cur = cur->_left; } else if (cur->_key < key) { parent = cur; cur = cur->_right; } else { if (cur->_left == nullptr) { if (_root == cur) { _root = cur->_right; } else if (parent->_left == cur) { parent->_left = cur->_right; } else { parent->_right = cur->_right; } delete cur; } else if (cur->_right == nullptr) { if (_root == cur) { _root = cur->_left; } else if (parent->_left == cur) { parent->_left = cur->_left; } else { parent->_right = cur->_left; } delete cur; } else { parent = cur; Node* subleft = cur->_right; while (subleft->_left) { parent = subleft; subleft = subleft->_left; } swap(subleft->_key, cur->_key); if (parent == cur) { parent->_right = subleft->_right; } else { parent->_left = subleft->_right; } delete subleft; } return true; } } return false; } void InOrder() { _InOrder(_root); cout << endl; } Node* FindR(const K& key) { return _FindR(_root, key); } bool InsertR(const K& key,const V& value) { return _InsertR(_root, key,value); } bool EraseR(const K& key) { return _EraseR(_root, key); } private: void _InOrder(Node* root) { if (root == nullptr) return; _InOrder(root->_left); cout << root->_key << " " << root->_value << endl; _InOrder(root->_right); } Node* _FindR(Node* root, const K& key) { if (root == nullptr) return nullptr; if (root->_key > key) { return _FindR(root->_left, key); } else if (root->_key < key) { return _FindR(root->_right, key); } else { return root; } } bool _InsertR(Node*& root, const K& key,const V& value) { if (root == nullptr) { root = new Node(key,value); return true; } if (root->_key > key) { return _InsertR(root->_left, key,value); } else if (root->_key < key) { return _InsertR(root->_right, key, value); } else { return false; } } bool _EraseR(Node*& root, const K& key) { if (root == nullptr) { return false; } if (root->_key > key) { return _EraseR(root->_left, key); } else if (root->_key < key) { return _EraseR(root->_right, key); } else { if (root->_left == nullptr) { Node* del = root; root = root->_right; delete del; 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(subleft->_key, root->_key); //到右子樹再去遞歸刪除 return _EraseR(root->_right, key); } } return false; } private: Node* _root = nullptr; }; }
KV模型測試
如此一來我們就算大功告成了。
到此這篇關(guān)于C++二叉搜索樹模擬實現(xiàn)示例的文章就介紹到這了,更多相關(guān)C++二叉搜索樹模擬內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
C++教程之a(chǎn)rray數(shù)組使用示例詳解
這篇文章主要為大家介紹了C++教程之a(chǎn)rray數(shù)組使用示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪2023-03-03C++實現(xiàn)LeetCode(56.合并區(qū)間)
這篇文章主要介紹了C++實現(xiàn)LeetCode(56.合并區(qū)間),本篇文章通過簡要的案例,講解了該項技術(shù)的了解與使用,以下就是詳細(xì)內(nèi)容,需要的朋友可以參考下2021-07-07C++構(gòu)造函數(shù)初始化列表的實現(xiàn)詳解
構(gòu)造函數(shù)主要作用在于創(chuàng)建對象時為對象的成員屬性賦值,構(gòu)造函數(shù)由編譯器自動調(diào)用,無須手動調(diào)用;析構(gòu)函數(shù)主要作用在于對象銷毀前系統(tǒng)自動調(diào)用,執(zhí)行一 些清理工作2022-09-09一篇文章讓你輕松理解C++中vector和list區(qū)別
對于學(xué)c語言的同學(xué)來說,vector和list這兩個東西經(jīng)常會搞錯,下面這篇文章主要給大家介紹了關(guān)于C++中vector和list區(qū)別的相關(guān)資料,需要的朋友可以參考下2022-01-01