二叉搜尋樹詳解及實現程式碼(BST)
阿新 • • 發佈:2018-12-24
概念
二叉搜尋樹(Binary Search Tree),又稱二叉排序樹,它或者是一顆空樹,或者具有如下性質的樹:
- 若它的左子樹不為空,則左子樹上所有節點的值都小於根節點的值
- 若它的右子樹不為空,則右子樹上所有節點的值都大於根節點的值
- 它的左右子樹也分別為二叉搜尋樹
基本操作
插入
向二叉搜尋樹中插入新元素時,必須先檢測這個元素是否在樹中已經存在。如果已經存在,則不進行插入,如果元素不存在則將新元素插入到搜尋停止的時候,也就是每次插入都是一個葉子節點。
查詢
在一棵不為空的二叉搜尋樹中查詢元素時,如果要查詢的元素與根節點的值相等,則返回true 或根節點,如果小於根節點的值,則在左子樹查詢,如果大於根節點的值,在其右子樹中查詢。否則,返回false或者NULL。
刪除
相對查詢和插入操作來說,刪除算是二叉搜尋樹中最複雜的一個操作。我們需要分情況討論。
- 首先判斷是否是一顆空樹,是空樹則直接返回false,表示刪除失敗,否則進行下一步
- 判斷當前樹是否只有一個結點,且比較要刪除的值與根節點的值是否相同,相同則刪除成功,不相同,表示刪除失敗
以上條件都不滿足,可分以下三步
- 找到要刪除的結點
- 分情況討論節點的情況,根據所在位置的不同,進行值變換,和指標調整(下面有具體描述)
- 刪除該節點
注意:這裡我們將葉子結點進行了歸併,總共分為下面三種情況:
// 第2步 分情況討論結點情況
/*
* 要刪除的結點分別對應三種情況:
* 一. 只有左孩子
* (1). pcur 為根節點 更新pRoot的指向為當前結點的左孩子
* (2). pcur 不為根節點 更改要刪除結點的父節點的指向
* 1>. 如果要刪除結點是父節點的左孩子 parent->left = pcur->left
* 2>. 如果要刪除結點是父節點的右孩子 parent->right = pcur->left
* 二. 只有右孩子
* (1). pcur 為根節點 更新pRoot的指向為當前結點的右孩子
* (2). pcur 不為根節點
* 1>.
* 三. 左右孩子都有
* (1). 第一步找到要刪除結點右子樹中最小的結點並替換,轉換為刪除右子樹中最小的結點
* (2). 刪除右子樹中最小的結點分兩種情況
* 1>. 找到的結點為pCur的右孩子 此時將pCur->right = pDelete->right
* 2>. 找到的結點不是pCur的右孩子 需要更新pDelete結點的父節點指向 parent->left = pDelete->right
*/
上述文字對應情況的圖解
一、只有左孩子
(1)、pCur為根節點的情況:
(2)、pCur不為根節點的情況
二、只有右孩子
(1)、pCur為根節點的情況:
(2)、pCur不為根節點的情況
三、左右孩子都有
有兩種實現方式,第一種是在當前結點的左子樹中找到最大的節點,第二種是在當前結點的右子樹中找到最小的結點(這裡是用了第二種)
找到右子樹中最小的結點後有如下兩種情況
- d 找到的結點為pCur的右孩子
- d 找到的結點不是pCur的右孩子
實現程式碼
BinarySearchTree.hpp
#ifndef _BINARYSEARCHTREE_H_
#define _BINARYSEARCHTREE_H_
#include <iostream>
using namespace std;
template<class K, class V>
class BSTree
{
// 二叉搜尋樹結點型別
template<class K, class V>
struct BSTNode
{
BSTNode(const K& _key, const V& _val)
: key(_key)
, val(_val)
, left(NULL)
, right(NULL)
{}
K key; // 結點的鍵
V val; // 結點對應的值
BSTNode<K, V> * left; // 指向左孩子
BSTNode<K, V> * right; // 指向右孩子
};
public:
// 建構函式
BSTree()
: pRoot(NULL)
{}
// 拷貝構造
BSTree(const BSTree<K, V>& bst)
:pRoot(NULL)
{
pRoot = _Copy(bst.GetRoot());
}
// 賦值運算子過載
BSTree<K, V>& operator=(const BSTree<K, V>& bst)
{
if (this != &bst)
{
_Clear(pRoot);
pRoot = _Copy(bst.GetRoot());
}
return *this;
}
// 虛構函式
~BSTree()
{
_Clear(pRoot);
}
// 插入結點遞迴寫法
void Insert(const K& key, const V& val)
{
_Insert(pRoot, key, val);
}
// 插入結點非遞迴
void InsertNor(const K& key, const V& val)
{
_InsertNor(pRoot, key, val);
}
// 查詢結點遞迴寫法
bool Find(const K& _key)
{
return _Find(pRoot, _key);
}
// 查詢結點非遞迴寫法
bool FindNor(const K& _key)
{
return _FindNor(pRoot, _key);
}
// 刪除指定key結點
bool Remove(const K& key)
{
if (NULL == pRoot)
return false;
else
return _Remove(pRoot, key);
}
// 判空
bool Empty()const
{
return pRoot == NULL;
}
// 獲取根節點
const BSTNode<K, V>* GetRoot()const
{
return pRoot;
}
private:
// 拷貝構造和賦值運算子過載呼叫
BSTNode<K, V>* _Copy(const BSTNode<K, V>* pRoot)
{
BSTNode<K, V>* temp = NULL;
if (pRoot != NULL)
{
temp = new BSTNode<K, V>(pRoot->key, pRoot->val);
temp->left = _Copy(pRoot->left);
temp->right = _Copy(pRoot->right);
}
return temp;
}
// 刪除
bool _Remove(BSTNode<K, V>*& _pRoot, const K& _key)
{
// 特殊處理要刪除的結點是根節點,且只有一個結點
if (_pRoot->left == NULL && _pRoot->right == NULL && _pRoot->key == _key)
{
delete _pRoot;
_pRoot = NULL;
return true;
}
// 第一步 找到要刪除的結點
BSTNode<K, V>* pCur = _pRoot;
BSTNode<K, V>* pParent = NULL;
while (pCur != NULL)
{
if (_key < pCur->key)
{
pParent = pCur;
pCur = pCur->left;
}
else if (pCur->key < _key)
{
pParent = pCur;
pCur = pCur->right;
}
else
break;
}
// 第二步 分情況討論結點情況
/*
* 要刪除的結點分別對應三種情況:
* 1. 只有左孩子
* (1). pcur 為根節點 更新pRoot的指向為當前結點的左孩子
* (2). pcur 不為根節點 更改要刪除結點的父節點的指向
* 1>. 如果要刪除結點是父節點的左孩子 parent->left = pcur->left
* 2>. 如果要刪除結點是父節點的右孩子 parent->right = pcur->left
* 2. 只有右孩子
* (1). pcur 為根節點 更新pRoot的指向為當前結點的右孩子
* (2). pcur 不為根節點
* 1>.
* 3. 左右孩子都有
* (1). 第一步找到要刪除結點右子樹中最小的結點並替換,轉換為刪除右子樹中最小的結點
* (2). 刪除右子樹中最小的結點分兩種情況
* 1>. 找到的結點為pCur的右孩子 此時將pCur->right = pDelete->right
* 2>. 找到的結點不是pCur的右孩子 需要更新pDelete結點的父節點指向 parent->left = pDelete->right
*/
/* 找到要刪除的結點 */
if (NULL == pCur) // 需要刪除的結點不存在
return false;
if (pCur->right == NULL) // 當前結點無右孩子
{
if (pCur == pRoot) // 刪除的結點是根節點時
{
pRoot = pRoot->left;
}
else // 刪除的結點非根節點
{
if (pParent->left == pCur) // 要刪除的結點是父節點的左孩子
pParent->left = pCur->left;
else // 要刪除的結點是父節點的右孩子
pParent->right = pCur->left;
}
}
else if (pCur->left == NULL) // 當前結點無左孩子
{
if (pCur == pRoot)
{
pRoot = pRoot->right;
}
else
{
if (pParent->left == pCur)
pParent->left = pCur->right;
else
pParent->right = pCur->right;
}
}
else // 左右孩子都有
{
BSTNode<K, V>* pDelete = pCur->right; // 在右子樹中找到最小的結點
pParent = pCur;
while ( pDelete->left )
{
pParent = pDelete;
pDelete = pDelete->left;
}
pCur->key = pDelete->key; // 將右子樹中最小的結點 賦給pCur
pCur->val = pDelete->val;
if (pCur->right == pDelete) // 如果右子樹中最小的結點就是 pCur的右孩子
pCur->right = pDelete->right;
else
pParent->left = pDelete->right;
pCur = pDelete; // 將最終要delete的結點賦給pCur為了在if else 外部統一delete pCur
}
/* 第三步 已經結點找到刪除即可*/
delete pCur;
pCur = NULL;
return true;
}
// 清空
void _Clear(BSTNode<K, V>*& pRoot)
{
if (pRoot != NULL)
{
_Clear(pRoot->left);
_Clear(pRoot->right);
delete pRoot;
pRoot = NULL;
}
}
// 非遞迴實現查詢
bool _FindNor(const BSTNode<K, V>* pRoot, const K& _key)
{
while (pRoot != NULL)
{
if (pRoot->key == _key)
return true;
else if (pRoot->key > _key)
pRoot = pRoot->left;
else
pRoot = pRoot->right;
}
return false;
}
// 遞迴實現查詢
bool _Find(const BSTNode<K, V>* pRoot, const K& _key)
{
if (pRoot == NULL)
return false;
if (pRoot->key == _key)
return true;
else if (pRoot->key > _key)
return _Find(pRoot->left, _key);
else
return _Find(pRoot->right, _key);
}
// 遞迴實現插入
void _Insert(BSTNode<K, V>*& pRoot, const K& _key, const V& _val)
{
if (pRoot == NULL)
{
pRoot = new BSTNode<K, V>(_key, _val);
}
else
{
if (_key > pRoot->key)
_Insert(pRoot->right, _key, _val);
else if (_key < pRoot->key)
_Insert(pRoot->left, _key, _val);
else
return;
}
}
// 非遞迴實現插入
void _InsertNor(BSTNode<K, V>*& pRoot, const K& _key, const V& _val)
{
if (pRoot == NULL)
{
pRoot = new BSTNode<K, V>(_key, _val);
return;
}
BSTNode<K, V>* pCur = pRoot;
BSTNode<K, V>* pPre = pRoot;
while (pCur != NULL)
{
pPre = pCur;
if (_key < pCur->key)
{
pCur = pCur->left;
}
else if (_key > pCur->key)
{
pCur = pCur->right;
}
else
return;
}
if (_key < pPre->key)
pPre->left = new BSTNode<K, V>(_key, _val);
else
pPre->right = new BSTNode<K, V>(_key, _val);
}
private:
BSTNode<K, V>* pRoot; // 維護一個根節點
};
#endif //_BINARYSEARCHTREE_H_
test.cpp
#include "BinarySearchTree.hpp"
void Test_BST()
{
BSTree<int, string> bst;
bst.Insert(20, "根節點");
bst.Insert(1, "根節點");
bst.Insert(10, "左子樹");
bst.Insert(30, "右子樹");
bst.Insert(9, "嘿嘿");
cout << boolalpha << bst.FindNor(20)<< endl;
cout << boolalpha << bst.FindNor(10)<< endl;
cout << boolalpha << bst.FindNor(30)<< endl;
cout << boolalpha << bst.FindNor(9) << endl;
cout << boolalpha << bst.FindNor(0) << endl;
bst.~BSTree();
bst.Find(1);
}
void Test_BST_Remove()
{
BSTree<int, int> bt;
bt.InsertNor(5, 5);
bt.InsertNor(3, 3);
bt.InsertNor(4, 4);
bt.InsertNor(1, 1);
bt.InsertNor(7, 7);
bt.InsertNor(8, 8);
bt.InsertNor(2, 2);
bt.InsertNor(6, 6);
bt.InsertNor(0, 0);
bt.InsertNor(9, 9);
bt.Remove(5);
bt.Remove(6);
bt.Remove(1);
bt.Remove(3);
bt.Remove(7);
BSTree<int, int> bt1(bt);
BSTree<int, int> bt2;
bt1 = bt2 = bt;
}
int main()
{
//Test_BST();
Test_BST_Remove();
return 0;
}