C++簡單實(shí)現(xiàn)與分析二叉搜索樹流程
二叉搜索樹
二叉搜索樹又被稱為二叉排序樹。它可以是一個(gè)空樹,如果不是空樹則滿足下列性質(zhì):
1、如果它的左子樹不為空,那么左子樹上的所有節(jié)點(diǎn)都小于根。
2、如果它的右子樹不為空,那么右子樹上的所有節(jié)點(diǎn)都大于根
3、它的左子樹、右子樹也是二叉搜索樹。
二叉搜索樹的重要操作
二叉搜索樹的插入
1、如果樹為空,則直接插入
2、如果樹不為空,則找到對(duì)應(yīng)的位置插入。
查找辦法:
根據(jù)二叉搜索樹的性質(zhì),
1、如果給出的關(guān)鍵碼比當(dāng)前節(jié)點(diǎn)的關(guān)鍵碼小,則在當(dāng)前節(jié)點(diǎn)的左子樹中找位置
2、如果給出的關(guān)鍵碼比當(dāng)前節(jié)點(diǎn)的關(guān)鍵碼大,則在當(dāng)前節(jié)點(diǎn)的右子樹中找位置
如此反復(fù)循環(huán)……,直到找到一個(gè)空的位置插入。
二叉搜索樹的刪除
刪除分為三種情況:
- 情況一:要?jiǎng)h除的節(jié)點(diǎn)左孩子為空
- 情況二:要?jiǎng)h除的節(jié)點(diǎn)左孩子不為空,右孩子為空
- 情況三:要?jiǎng)h除的節(jié)點(diǎn)既有左孩子也有右孩子。
刪除情況一分析:
例如,刪除關(guān)鍵碼為1的節(jié)點(diǎn)。它的左孩子為空,那么遍歷這個(gè)二叉樹,找到這個(gè)節(jié)點(diǎn)。讓這個(gè)節(jié)點(diǎn)的父親節(jié)點(diǎn)指向該節(jié)點(diǎn)的右孩子節(jié)點(diǎn)
但是需要考慮刪除節(jié)點(diǎn)的父節(jié)點(diǎn)是右孩子指向,還是左孩子指向。
刪除情況二分析:
例如,刪除關(guān)鍵碼為7的節(jié)點(diǎn)。它的左孩子不為空,右孩子為空。首先遍歷這個(gè)二叉樹,找到這個(gè)節(jié)點(diǎn)。讓這個(gè)節(jié)點(diǎn)的父親節(jié)點(diǎn)指向該節(jié)點(diǎn)的左孩子節(jié)點(diǎn)。同樣需要考慮刪除節(jié)點(diǎn)的父節(jié)點(diǎn)是左孩子指向還是右孩子指向。
情況一和情況二都面臨這樣一個(gè)問題,如果刪除的是根節(jié)點(diǎn)則需要單獨(dú)考慮。
刪除情況三分析:
解決辦法:替換法
替換法:如果刪除節(jié)點(diǎn)既有左孩子又有右孩子,為了刪除之后依舊能使其保留二叉搜索樹的性質(zhì),則需要將刪除的節(jié)點(diǎn)和一個(gè)合適的節(jié)點(diǎn)進(jìn)行替換,使這個(gè)合適的節(jié)點(diǎn)替換到刪除節(jié)點(diǎn)的位置,然后刪除被替換的節(jié)點(diǎn)即可解決。
兩個(gè)合適的節(jié)點(diǎn):
1、刪除節(jié)點(diǎn)的左子樹中最大節(jié)點(diǎn)。
2、刪除節(jié)點(diǎn)的右子樹中最小節(jié)點(diǎn)。
例如,刪除關(guān)鍵碼為5的節(jié)點(diǎn)。它的左孩子、右孩子都不為空。首先遍歷這個(gè)二叉樹,找到這個(gè)節(jié)點(diǎn)。為使刪除后依舊能保持二叉搜索樹的性質(zhì),需要挑選一個(gè)合適的節(jié)點(diǎn)進(jìn)行替換。這個(gè)合適的節(jié)點(diǎn)是關(guān)鍵碼為4的節(jié)點(diǎn)(刪除節(jié)點(diǎn)的左子樹中最大節(jié)點(diǎn))和關(guān)鍵碼為6的節(jié)點(diǎn)(刪除節(jié)點(diǎn)的右子樹中最小節(jié)點(diǎn)),選一個(gè)即可。將替換節(jié)點(diǎn)的值給刪除節(jié)點(diǎn)后,刪除替換節(jié)點(diǎn),然后這個(gè)時(shí)候就轉(zhuǎn)變?yōu)榱藙h除情況一了,按照刪除情況一的做法即可完美刪除!
二叉搜索樹實(shí)現(xiàn)(key模型)
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) {} //insert bool insert(const K& key) { if (_root == nullptr) { //為空 //直接就是給成根節(jié)點(diǎn) _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; //已經(jīng)有了,則不能插入 } } cur = new Node(key); if (parent->_key > key) { //插入左邊 parent->_left = cur; } else { //插入右邊 parent->_right = cur; } return true; } bool Find(const K& key) { if (_root == nullptr) { return false; } Node* cur = _root; while (cur) { if (cur->_key > key) { cur = cur->_left; } else if (cur->_key < key) { cur = cur->_right; } else { return true; } } return false; } bool erase(const K& value) { if (_root == nullptr) { return false; } Node* parent = nullptr; Node* cur = _root; while (cur) { if (cur->_key > value) { parent = cur; cur = cur->_left; } else if (cur->_key < value) { parent = cur; cur = cur->_right; } else { //找到了,開始刪除 //情況一:要?jiǎng)h除的節(jié)點(diǎn)左孩子為空 if (cur->_left == nullptr) { if (parent == nullptr) { //刪除的是根節(jié)點(diǎn) _root = cur->_right; } //判斷刪除的是左孩子節(jié)點(diǎn)還是右孩子節(jié)點(diǎn)以便更改連接關(guān)系 if (parent->_left == cur) { parent->_left = cur->_right; } else { parent->_right = cur->_right; } delete cur; } else if (cur->_right == nullptr) { //情況二:要?jiǎng)h除的節(jié)點(diǎn)左孩子不為空、右孩子為空 if (parent == nullptr) { //刪除的是根節(jié)點(diǎn) _root = cur->_left; } if (parent->_left == cur) { parent->_left = cur->_left; } else { parent->_right = cur->_left; } delete cur; } else { //情況三:要?jiǎng)h除的節(jié)點(diǎn)即有左孩子也有右孩子 //使用替換法 //兩種替換:1、用該節(jié)點(diǎn)的左子樹最大節(jié)點(diǎn) 2、用該節(jié)點(diǎn)右子樹的最小節(jié)點(diǎn) //這里使用第一種替換方法 //找到用于替換的節(jié)點(diǎn) Node* maxParent = cur; Node* maxCur = cur->_right; while (maxCur->_right) { maxParent = maxCur; maxCur = maxCur->_right; } // cur->_key = maxCur->_key; //刪除用于替換的節(jié)點(diǎn) if (maxParent->_left == maxCur) { maxParent->_left = maxCur->_left; } else { maxParent->_right = maxCur->_left; } delete maxCur; } return true; } } //要?jiǎng)h除的節(jié)點(diǎn)不存在 return false; } //由于類外使用不到私有成員_root //增加一個(gè)函數(shù) void inorder() { _inorder(_root); } //遞歸版 Node* 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 _inorder(Node* root) //不需要在類外顯示調(diào)用它,所以放在私有 { if (root == nullptr) { return; } _inorder(root->_left); cout << root->_key << " "; _inorder(root->_right); } Node* _FindR(Node* root, const K& key) { if (root == nullptr) { return nullptr; } if (root->_key > key) { _FindR(root->_left, key); } else if (root->_key < key) { _FindR(root->_right, key); } else { //找到了 return root; } } bool _insertR(Node*& root, const K& key) //注意root加引用 { if (root == nullptr) { root = new Node(key); return true; } if (root->_key > key) { _insertR(root->_left, key); } else if (root->_key < key) { _insertR(root->_right, key); } else { return false; } } bool _eraseR(Node*& root, const K& key) { if (root == nullptr) { //都已經(jīng)找到空了,表示不存在 return false; } if (root->_key > key) { _eraseR(root->_left, key); } else if (root->_key < key) { _eraseR(root->_right, key); } else { //找到要?jiǎng)h除的節(jié)點(diǎn)了,開始刪除 Node* del = root; //左孩子為空 if (root->_left == nullptr) { root = root->_right; //使用了引用,直接就是 } else if (root->_right == nullptr) { //左孩子不為空,右孩子為空 root = root->_left; } else { Node* min = root->_right; while (min->_left) { min = min->_left; } swap(min->_key, root->_key); // 遞歸到右子樹去刪除 return _eraseR(root->_right, key); } delete del; return true; } } private: Node* _root; };
二叉搜索樹的應(yīng)用
應(yīng)用一:排序+去重
應(yīng)用二:key模型、key/value模型
二叉搜索樹的排序體現(xiàn)在中序遍歷二叉搜索樹時(shí)是有序的。
key模型:key模型即只有key作為關(guān)鍵碼,結(jié)構(gòu)中只需要存儲(chǔ)Key即可,關(guān)鍵碼即為需要搜索到的值。其價(jià)值在于判斷“在不在”。
比如:給一個(gè)單詞word,判斷該單詞是否拼寫正確,具體方式如下:
以單詞集合中的每個(gè)單詞作為key,構(gòu)建一棵二叉搜索樹
在二叉搜索樹中檢索該單詞是否存在,存在則拼寫正確,不存在則拼寫錯(cuò)誤。
key/value模型:每一個(gè)關(guān)鍵碼key,都有與之對(duì)應(yīng)的值Value,即<Key, Value>的鍵值對(duì)。該種方式在現(xiàn)實(shí)生活中非常常見。其價(jià)值在于可通過一個(gè)信息,找到其對(duì)應(yīng)的其他東西。。
比如:
1、通過英文查找對(duì)應(yīng)的中文;
2、高鐵檢票通過身份證查找對(duì)應(yīng)的乘車信息……
二叉搜索樹的實(shí)現(xiàn)(key/value模型)
//二叉搜索樹key/value模型 namespace KV { template<class K, class V> struct BSTreeNode { BSTreeNode* _left; BSTreeNode* _right; K _key; V _value; BSTreeNode(const K& 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: BSTree() :_root(nullptr) {} 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; } } cur = new Node(key, value); // if (parent->_key > key) { //插入左孩子節(jié)點(diǎn) parent->_left = cur; } else { parent->_right = cur; } return true; } Node* Find(const K& key) { if (_root == nullptr) { return nullptr; } Node* cur = _root; while (cur) { if (cur->_key > key) { //在左子樹 cur = cur->_left; } else if (cur->_key < key) { //在右子樹 cur = cur->_right; } else { //相等,找到了 return cur; } } //不存在 return nullptr; } bool Erase(const K& key) { if (_root == nullptr) { return false; } //找到要?jiǎng)h除的節(jié)點(diǎn) 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 { //找到了 //開始刪除 //情況一:要?jiǎng)h除的節(jié)點(diǎn)沒有左子樹 if (cur->_left == nullptr) { if (parent == nullptr) { //刪除的是根節(jié)點(diǎn) _root = cur->_right; } //判斷刪除的是左孩子節(jié)點(diǎn)還是右孩子節(jié)點(diǎn),方便更改連接關(guān)系 if (parent->_left = cur) { parent->_left = cur->_right; } else { parent->_right = cur->_right; } delete cur; } else if (cur->_right == nullptr) { //情況二:要?jiǎng)h除的節(jié)點(diǎn)左孩子不為空,,右孩子為空 if (parent == nullptr) { _root = cur->_left; } if (parent->_left = cur) { parent->_left = cur->_left; } else { parent->_right = cur->_left; } delete cur; } else { //情況三:要?jiǎng)h除的節(jié)點(diǎn)既有左孩子也有右孩子 //要使用替換法刪除 //使用右子樹的最小節(jié)點(diǎn)進(jìn)行替換 Node* minParent = cur; Node* minCur = cur->_right; //找到右子樹的最小節(jié)點(diǎn) while (minCur->_left) { minParent = minCur; minCur = minCur->_left; } //替換 cur->_key = minCur->_key; cur->_value = minCur->_value; //刪除替換節(jié)點(diǎn),并更改連接關(guān)系 if (minParent->_left == minCur) { minParent->_left = minCur->_right; } else { minParent->_right = minCur->_right; } delete minCur; } return true; } } return false; } void inorder() { _inorder(_root); } private: void _inorder(Node* root) { if (root == nullptr) { return; } _inorder(root->_left); cout << root->_key << ":" << root->_value << endl; _inorder(root->_right); } private: Node* _root; }; }
到此這篇關(guān)于C++簡單實(shí)現(xiàn)與分析二叉搜索樹流程的文章就介紹到這了,更多相關(guān)C++二叉搜索樹內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
c++的glog與spdlog的性能對(duì)比測(cè)試分析
這篇文章主要為大家介紹了c++的glog與spdlog的性能對(duì)比測(cè)試分析,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-05-05如何在C++中通過模板去除強(qiáng)制轉(zhuǎn)換
本文講解的是如何在C++中通過模板去除強(qiáng)制轉(zhuǎn)換,在編程工作中應(yīng)盡量少使用強(qiáng)制類型轉(zhuǎn)換,模板有助于我們實(shí)現(xiàn)這一目的,需要的朋友可以參考下2015-07-07C語言的isatty函數(shù)和ttyname函數(shù)以及sendmsg函數(shù)用法
這篇文章主要介紹了C語言的isatty函數(shù)和ttyname函數(shù)以及sendmsg函數(shù)用法,是C語言入門學(xué)習(xí)中的基礎(chǔ)知識(shí),需要的朋友可以參考下2015-09-09c語言實(shí)現(xiàn)php的trim標(biāo)簽
本文給大家介紹的是使用C語言實(shí)現(xiàn)php的trim標(biāo)簽功能的代碼,非常的實(shí)用,其主要作用是清除字符串開頭結(jié)尾除空白,有需要的小伙伴可以參考下。2016-01-01C++數(shù)據(jù)結(jié)構(gòu)深入探究棧與隊(duì)列
棧和隊(duì)列,嚴(yán)格意義上來說,也屬于線性表,因?yàn)樗鼈円捕加糜诖鎯?chǔ)邏輯關(guān)系為 "一對(duì)一" 的數(shù)據(jù),但由于它們比較特殊,本章講解分別用隊(duì)列實(shí)現(xiàn)棧與用棧實(shí)現(xiàn)隊(duì)列2022-05-05