1. 程式人生 > >二叉搜尋樹詳解及實現程式碼(BST)

二叉搜尋樹詳解及實現程式碼(BST)

概念

二叉搜尋樹(Binary Search Tree),又稱二叉排序樹,它或者是一顆空樹,或者具有如下性質的樹:

  1. 若它的左子樹不為空,則左子樹上所有節點的值都小於根節點的值
  2. 若它的右子樹不為空,則右子樹上所有節點的值都大於根節點的值
  3. 它的左右子樹也分別為二叉搜尋樹

這裡寫圖片描述

基本操作

插入

向二叉搜尋樹中插入新元素時,必須先檢測這個元素是否在樹中已經存在。如果已經存在,則不進行插入,如果元素不存在則將新元素插入到搜尋停止的時候,也就是每次插入都是一個葉子節點。

查詢

在一棵不為空的二叉搜尋樹中查詢元素時,如果要查詢的元素與根節點的值相等,則返回true 或根節點,如果小於根節點的值,則在左子樹查詢,如果大於根節點的值,在其右子樹中查詢。否則,返回false或者NULL。

刪除

相對查詢和插入操作來說,刪除算是二叉搜尋樹中最複雜的一個操作。我們需要分情況討論。

  • 首先判斷是否是一顆空樹,是空樹則直接返回false,表示刪除失敗,否則進行下一步
  • 判斷當前樹是否只有一個結點,且比較要刪除的值與根節點的值是否相同,相同則刪除成功,不相同,表示刪除失敗
  • 以上條件都不滿足,可分以下三步

    1. 找到要刪除的結點
    2. 分情況討論節點的情況,根據所在位置的不同,進行值變換,和指標調整(下面有具體描述)
    3. 刪除該節點

    注意:這裡我們將葉子結點進行了歸併,總共分為下面三種情況:

        // 第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為根節點的情況:

BST1_1

(2)、pCur不為根節點的情況
BST1_2

二、只有右孩子

(1)、pCur為根節點的情況:

BST2_1

(2)、pCur不為根節點的情況

BST2_2

三、左右孩子都有

  1. 有兩種實現方式,第一種是在當前結點的左子樹中找到最大的節點,第二種是在當前結點的右子樹中找到最小的結點(這裡是用了第二種)

  2. 找到右子樹中最小的結點後有如下兩種情況

    • d 找到的結點為pCur的右孩子

BST_3_1_0

BST_3_1_2

  • d 找到的結點不是pCur的右孩子

BST3_2_0

BST3_2_1

實現程式碼

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;
}