1. 程式人生 > >二叉排序樹(二叉查詢樹)

二叉排序樹(二叉查詢樹)

1.相關概念
節點的度:一個節點擁有子樹的數目。
樹的高度:也稱為樹的深度,樹中節點的最大層次。
二叉樹或者為空集,或者由一個根節點和兩棵互不相交的、分別稱為左子樹和右子樹的二叉樹組成。從定義可以看出一棵二叉樹:
二叉樹是有序樹,區分左子樹與右子樹,不可以隨意交換子樹位置。
二叉樹共有5種形態
所有節點都只有左子樹的二叉樹叫做左斜樹,所有節點都只有右子樹的二叉樹叫做右斜樹。左斜樹和右子樹統稱為斜樹。
斜樹已經退化成線性結構,二叉樹在查詢上表現出來優異效能在斜樹得不到體現。

完全二叉樹
在一棵二叉樹中,只有最下兩層的度可以小於2,並且最下一層的葉子節點集中出現在靠左的若干位置上。
或者這樣定義:對一棵具有n個節點的二叉樹按層序從左到右編序,二叉樹樹某個節點的編序與同樣位置的滿二叉樹節點的編序相同如果所有節點都滿足這個條件,則二叉樹為完全二叉樹。

滿二叉樹
滿二叉樹要滿足兩個條件:
所有的節點都同時具有左子樹和右子樹。
所有的葉子節點都在同一層上。
在同樣深度的二叉樹中,滿二叉樹的節點數目是最多的,葉子數也是最多的。

二叉查詢樹
二叉查詢樹也稱為二叉搜尋樹或二叉排序樹。二叉排序樹的節點包含鍵值key。二叉排序樹或者是一棵空樹,否則要求:
若它的左子樹不為空,那麼左子樹上所有節點的key都小於根節點的key
若它的右子樹不為空,那麼右子樹上所有節點的key都大於根節點的key
它的左右子樹也分別為二叉排序樹

根據定義,二叉查詢樹中沒有重複key的節點。

二叉樹的性質
性質一:在二叉樹的第i層上至多有2^(i-1)個節點(i>=1)
性質二:深度為k的二叉樹至多有2^k-1個節點
性質三:對任何一棵二叉樹T,如果終端節點數為n0,度為2的節點數為n2 ,那麼 n0 = n2 +1
證明:二叉樹節點度數最大為2,則 : n = n0 + n1 + n2 (等式一)
從孩子個數角度出發: 度為0的節點沒有孩子, 度為1的節點沒有1個孩子,度為2的節點有2個孩子,孩子總數為 n00 + n11 +n2 2 = n1+2n2;
樹的所有節點中,只有根不是任何節點的孩 子,因此有 n -1 = n1 + 2* n2 ,即 n = n1 + 2* n2 + 1. (等式二)
由等式一等式而可以推出 n0 = n2 +1

性質四: 具有n個節點的完全二叉樹的高度為至少為log2(n+1)
證明:高度為h的二叉樹最多有2{h}–1個結點。反之,對於包含n個節點的二叉樹的高度至少為log2(n+1)。

性質五:如果對一棵有n個節點的完全二叉樹的節點按層序編號(從第一層開始到最下一層,每一層從左到右編號),
對任一節點i有:
如果i=1 ,則節點為根節點,沒有雙親。
如果2 * i > n ,則節點i沒有左孩子 ;否則其左孩子節點為2*i . (n為節點總數)
如果2 * i+1>n ,則節點i沒有右孩子;否則其右孩子節點為2*1+1

2.程式碼實現
BSNode.h


#ifndef DATASTRUCTURE_BSNODE_H
#define DATASTRUCTURE_BSNODE_H

//二叉查詢樹的節點結構
template <typename T>
class BSNode {
public:
    BSNode(T t): value(t), lchild(nullptr), rchild(nullptr){}


    BSNode() = default;

    T value;

    BSNode<T>* lchild;
    BSNode<T>* rchild;
    BSNode<T>* parent;
};

//        value:節點的值,也即是上文的key,型別由模板引數決定
//        lchild :指向節點的左孩子
//        rchild:指向節點的右孩子
//        parent: 指向節點的雙親
#endif //DATASTRUCTURE_BSNODE_H

BSTree.h

#ifndef DATASTRUCTURE_BSTREE_H
#define DATASTRUCTURE_BSTREE_H

#include <iostream>
#include "BSNode.h"
using namespace std;

//這裡我們定義了二叉排序樹的型別BSTree。它包含了:
//
//BSTree的根節點root,這是唯一的資料成員
//        操作的外部介面與內部實現介面。
//例如 preOrder()為提供給使用者使用的介面,介面宣告為public;
//而preOrder(AVLTreeNode* pnode)是類內部為了遞迴操作所使用的介面,介面宣告為private。
//提供的其他介面都有相應的備註說明。

//二叉查詢樹
template <typename  T>
class BSTree {
public:
    BSTree();
    ~BSTree();

    void preOrder();                 //前序遍歷
    void inOrder();                //中序遍歷
    void postOrder();            //後序遍歷
    void layerOrder();          //層次遍歷

    BSNode<T>* search_recursion(T key);//遞迴地進行查詢
    BSNode<T>* search_Iterator(T key);        //迭代地進行查詢

    T search_minimum();//查詢最小元素
    T search_maximum();////查詢最大元素

    BSNode<T> * successor (BSNode<T> * x);     //查詢指定節點的後繼節點
    BSNode<T> * predecessor(BSNode<T> * x);    //查詢指定節點的前驅節點

    void insert(T key);    //插入指定值節點
    void remove(T key);    //刪除指定值節點
    void destory();        //銷燬二叉樹
    void print();        //列印二叉樹


    BSNode <T> * root;//根節點
    BSNode<T>* search(BSNode<T>* & p, T key);
    void remove(BSNode<T>*  p, T key);
    void preOrder(BSNode<T>* p);
    void inOrder(BSNode<T>* p);
    void postOrder(BSNode<T>* p);
    T search_minimun(BSNode<T>* p);
    T search_maximum(BSNode<T>* p);

    void destory(BSNode<T>* &p);


};

/*預設建構函式*/
template <typename T>
BSTree<T>::BSTree() :root(nullptr){}

template <class T>
BSTree<T>::~BSTree() {
    destory(root);
}

template <class  T>
void BSTree<T>::insert(T key) {
    BSNode<T> * pparent= nullptr;//記錄合適的插入位置
    BSNode<T> * pnode=root;////尋找合適的插入位置

    while (pnode!=nullptr){//尋找
        //nullptr是c++中空指標型別的關鍵字。
        pparent=pnode;
        if(key> pnode->value){
            pnode=pnode->rchild;
        }else if(key<pnode->value){
            pnode=pnode->rchild;
        } else
            break;
    }

    pnode=new BSNode<T> (key);//以元素的值構建新節點


    //插入
    if(pparent== nullptr){ //如果是空樹
        root=pnode;  //則新節點為根
    } else{
        if(key >pparent->value){
            pparent->rchild=pnode;//否則新節點為其父節點的左孩
        } else
            pparent->lchild=pnode;//或右孩
    }

    pnode->parent=pparent; //指明新節點的父節點
}




//前序遍歷
//若二叉樹為空,則空操作返回,否則先訪問根節點,然後前序遍歷左子樹,再前序遍歷右子樹。(簡記為:VLR)


//preOrder(AVLTreeNode* pnode)是類內部為了遞迴操作所使用的介面,介面宣告為private。
template <typename  T>
void BSTree<T>::preOrder() {
    preOrder(root);
}

template <class  T>
void BSTree<T>::preOrder(BSNode<T> *p) {
    if(p!= nullptr)//不為空節點
    {
        cout<<p->value<<endl;
        preOrder(p->lchild);
        preOrder(p->rchild);
    }
}


//rchild中序遍歷
//若二叉樹為空,則空操作返回,否則從根節點開始,中序遍歷根節點的左子樹,然後訪問根節點,最後中序遍歷右子樹。(簡記為:LVR)
template<typename  T>
void BSTree<T>::inOrder() {
    inOrder(root);
}

template <class T>
void BSTree<T>::inOrder(BSNode<T> *p) {
    if(p!= nullptr){

        inOrder(p->lchild);
        cout<<p->value<<endl;
        inOrder(p->rchild);
    }
}

//
//後序遍歷
//若樹為空,則返回空操作,否則從左到右先葉子後節點的方式遍歷訪問左右子樹,左右子樹都訪問結束,才訪問根節點。(簡稱LRV)
template <class T>
void BSTree<T>::postOrder() {
    postOrder(root);
}

template<class T>
void BSTree<T>::postOrder(BSNode<T> *p) {
    if(p!= nullptr){
        postOrder(p->lchild);
        postOrder(p->rchild);
        cout<<p->value<<endl;
    }
}

//對於一棵二叉排序樹,中序遍歷時剛好可以輸出一個非遞減的序列。
//一個節點的前驅節點有3種情況:
//它有左子樹,則其前驅節點為其右子樹的最右節點
//它沒有左子樹,且它本身為右子樹,則其父節點為其前驅節點
//它沒有左子樹,且它本身為左子樹,則它的前驅節點為“擁有右子樹的最近父節點”

/*尋找其前驅節點*/
template <class T>
BSNode<T> * BSTree<T>::predecessor(BSNode<T> *pnode) {
    if(pnode->lchild!= nullptr)//它有左子樹
    {
        pnode=pnode->lchild;
        while(pnode->rchild!= nullptr){
            pnode=pnode->rchild;
        }
        return pnode;
    }
    //以下均為無左子樹
    BSNode<T>* pparent=pnode->parent;
    while(pparent!= nullptr&&pparent->lchild==pnode){
        //如果進入迴圈,則是第三種情況:它本身為左子樹,
        // 否則為第二種情況
        //擁有右子樹的最近父節點
        pnode=pparent;
        pparent=pparent->parent;
    }
//、、、、、、、、、、、、、、、、、、、、、、、
    return pparent;
}
//一個節點的後繼節點也有三種情況:
//它有右子樹;則其後繼節點為其右子樹的最左節點
//同樣的,
// 它沒有右子樹,但它本身是一個左孩子,則後繼節點為它的雙親
//它沒有右子樹,但它本身是一個右孩子,則其後繼節點為“具有左孩子的最近父節點”

//尋找其後繼節點
template <class  T>
BSNode<T> * BSTree<T>::successor(BSNode<T> *x) {
    if(x->rchild!= nullptr){//右右子樹
        x=x->rchild;
        while(x!= nullptr)
            x=x->lchild;
        return x;
    }
    //沒有右子樹
    BSNode<T> * xparent=x->parent;
    while(xparent!= nullptr&&xparent->rchild==x)
    {
//具有左孩子的最近父節點
        x=xparent;
        xparent=xparent->parent;

//        if(xparent->lchild!= nullptr)
//            return xparent;
    }
    return xparent;
}

/*刪除二叉排序樹的某個節點有三種情況:
被刪除節點同時有左子樹與右子樹。
被刪除節點只有左子樹或只有右子樹。
被刪除節點沒有子樹。

對於第一種情況,我們的處理方式是將前驅節點的值儲存在當前結點,繼而刪除前驅節點。
對於第二種情況,我們直接用子樹替換被刪節點。
對於第三種情況,我們可以直接刪除節點。
 */

/*刪除指定節點*/
template <class  T>
void BSTree<T>::remove(T key) {
    remove(root,key);
}
/*刪除指定節點*/
/*內部使用函式*/
template <class T>
void BSTree<T>::remove(BSNode<T> *p, T key) {
    if(p!= nullptr){
        if(p->value==key){
            BSNode<T> * pdel= nullptr;//記錄要刪除的節點
            if(p->lchild== nullptr||p->rchild== nullptr)
                //情況二、三:被刪節點只有左子樹或右子樹,或沒有孩子
                pdel=p;
            else
                pdel=predecessor(p); //情況一:被刪節點同時有左右子樹,將前驅節點的值儲存在當前結點,繼而刪除前驅節點。

            //此時,被刪節點只有一個孩子(或沒有孩子),儲存該孩子指標
            BSNode<T> *pchild= nullptr;
            if(pdel->lchild!= nullptr)
                pchild=pdel->lchild;
            else
                pchild=pdel->rchild;

            //讓孩子指向被刪節點的父節點
            if(pchild!= nullptr)
                pchild->parent=pdel->parent;


            //讓被刪節點的父節點指向被刪節點的孩子節點
            //如果要刪除的節點是頭結點,注意更改4root值
            if(pdel->parent== nullptr)
                root=pchild;
                //如果要刪除的節點不是頭節點,要注意更改它的雙親節點指向新的孩子節點
            else if(pdel->parent->lchild==p)
                pdel->parent->lchild=pchild;
            else
                pdel->parent->rchild=pchild;


            if(p->value!=pdel->value)
                p->value=pdel->value;
            delete pdel;
        }
            //進行遞迴刪除
        else if(key>p->value)
            remove(p->rchild,key);
        else
            remove(p->lchild,key);
    }
}

/*查詢指定元素的節點(非遞迴)*/
template <class T>
BSNode<T>* BSTree<T>::search_Iterator(T key) {
    BSNode<T> * pnode=root;
    while(pnode!= nullptr){
        if(key==pnode->value)//找到
            return pnode;
        if(key>pnode->value)
            pnode=pnode->rchild; //關鍵字比節點值大,在節點右子樹查詢
        else
            pnode=pnode->lchild;//關鍵字比節點值小,在節點左子樹查詢
    }
    return pnode;
}
/*查詢指定元素的節點(遞迴)*/
template <class T>
BSNode<T> * BSTree<T>::search_recursion(T key) {
    return search(root,key);
}

/*private:search()*/
/*遞迴查詢的類內部實現*/
template <typename T>
BSNode<T>* BSTree<T>::search(BSNode<T> *&p, T key) {
    if(p== nullptr)
        return nullptr;
    if(p->value==key)
        return p;
    if(key>p->value)
        return search(p->rchild,key);
    return search(p->lchild,key);
}

/*尋找最小元素*/
template <typename T>
T BSTree<T>::search_minimum() {
    return search_minimun(root);
}
template<class T>
T BSTree<T>::search_minimun(BSNode<T> *p) {
    if(p->lchild!= nullptr)
        return search_minimun(p->lchild);
    return p->value;
}

/*尋找最大元素*/
template <typename T>
T BSTree<T>::search_maximum()
{
    return search_maximum(root);
}
template<class T>
T BSTree<T>::search_maximum(BSNode<T> *p) {
    if(p->rchild!= nullptr)
        return search_maximum(p->rchild);
    return p->value;
}

/*銷燬二叉樹*/
//使用後序遍歷遞迴銷燬二叉樹
template<class T>
void BSTree<T>::destory() {
    destory(root);
}

template <class T>
void BSTree<T>::destory(BSNode<T> *&p) {
    if(p!= nullptr){
        if(p->lchild!= nullptr)
            destory( p->lchild);
        if(p->rchild!= nullptr)
            destory(p->rchild);
        delete p;
        p=nullptr;
    }
}

#endif //DATASTRUCTURE_BSTREE_H

測試:main.cpp

#include <iostream>
#include "BSTree/BSTree.h"

using namespace std;
int main() {
    BSTree <int> t;
    t.insert(62);
    t.insert(58);
    t.insert(47);
    t.insert(51);
    t.insert(35);
    t.insert(37);
    t.insert(88);
    t.insert(73);
    t.insert(99);
    t.insert(93);
    t.insert(95);

    cout<<"中序遍歷:"<<endl;
    t.inOrder();

    cout<<"最大元素:"<<t.search_maximum()<<endl;
    cout<<"最小元素:"<<t.search_minimum()<<endl;

    cout<<"刪除元素99"<<endl;
    t.remove(99);
    cout << "最大元素:" << t.search_maximum() << endl;
    t.insert(111);
    cout<<"插入111"<<endl;
    cout << "最大元素:" << t.search_maximum() << endl;
    t.destory();

    return 0;
}

輸出:
中序遍歷:
37
62
73
88
95
99
最大元素:99
最小元素:37
刪除元素99
最大元素:95
插入111
最大元素:111