1. 程式人生 > >《算法導論》— Chapter 12 二叉查找樹

《算法導論》— Chapter 12 二叉查找樹

左右 表示 每次 期望 sdn print public 隨機構造 keyword

查找樹是一種數據結構,它支持多種動態集合操作。包含Search、Minimum、Maximum、PreDecessor、Successor、Insert、Delete等。它既能夠用作字典,也能夠用作優先級隊列;在二叉查找樹(Binary Search Tree)上執行基本操作的時間與樹的高度成正比,對於一顆含有n個結點的全然二叉樹,基本操作的最壞情況執行時間為floor(logn)


本章討論二叉查找樹的基本性質以及上面提及的基本操作的實現。

GitHub 程序實現代碼

1 二叉查找樹

1.1 性質

例如以下圖所看到的。一顆二叉查找樹是依照二叉樹結構來組織的。這種樹一般用鏈表結構表示,每個結點是一個對象,包含關鍵字key、父親結點parent、左兒子結點left以及右兒子結點right四個屬性。
技術分享


明顯的,對於二叉查找樹中關鍵字的存儲方式總是滿足這種性質:
x為二叉查找樹中的一個結點。假設yx左子樹中的一個結點,則y?>key<=x?>key,假設yx右子樹中的一個結點,則y?>key>=x?>key

1.2 基本操作

1.2.1 遍歷

依據二叉查找樹的性質。能夠用一個遞歸算法依照排列順序依次輸出全部關鍵字,這就是中序遍歷,遍歷順序為根、左子樹、右子樹。相同的,有前序遍歷,根的關鍵字在左右子樹之前輸出。後序遍歷。根的關鍵字在其左右子樹之後輸出。

1.2.2 查找

對於二叉查找樹,最常見的操作就是查找樹中的某個關鍵字。

查找操作相同採用遞歸的形式實現,其復雜度等於樹的高度。
操作步驟例如以下圖所看到的:
技術分享

1.2.3 求最大、最小關鍵字

對於用作優先級隊列的結構。求最大最小關鍵字是不可缺少的操作。
要查找二叉查找樹中的最小關鍵字,依據樹的性質,僅僅要從根節點開始,沿著各個結點的left指針查找下去,直到遇到NULL為止。


同理,要查找二叉查找樹中的最大關鍵字,依據樹的性質。僅僅要從根節點開始,沿著各個結點的right指針查找下去,直到遇到NULL為止。
這兩個操作的執行時間都是O(h)

1.2.4 求前驅、後繼

我們知道。中序遍歷二叉查找樹得到的是一組有序序列,有時候須要求指定結點的前驅與後繼。對於給定結點

x的後繼結點是具有大於或等於x?>key中關鍵字的最小結點;同理,對於給定結點x的前驅結點是具有小於x?>key中關鍵字的最大結點。依據二叉查找樹的性質,不用對關鍵字做不論什麽比較就能夠得到給定結點的前驅和後繼結點。

1.2.5 插入

插入和刪除操作會引起整個二叉查找樹表示的集合的動態變化。要反應出這種變化。就要改動數據結構,可是在改動的同一時候。還要保持整棵樹的性質不變。


將新值插入到一顆二叉查找樹中的步驟例如以下:

1.2.6 刪除

相對於插入操作。刪除更加復雜一些。下圖具體展示了刪除不同結點須要的步驟:
技術分享
對高度為h的二叉查找樹,動態集合操作Insert與Delete的執行時間都是O(n)

2 二叉查找樹程序實現

以下給出的程序實現。包含了全部以上提及的基本操作:
(1)BinarySearchTree.h

#ifndef _BINARYSEARCHTREE_H_
#define _BINARYSEARCHTREE_H_

#include <iostream>

typedef struct BSTNode{
    BSTNode *left;
    BSTNode *right;
    BSTNode *parent;

    int key;

    BSTNode(int data) : left(NULL), right(NULL), parent(NULL), key(data){}
};

class BinarySearchTree{
public:
    BinarySearchTree();
    ~BinarySearchTree();

    //插入刪除操作
    void Insert(int data);
    BSTNode *Delete(int data);
    BSTNode *root;
};

//查找操作
BSTNode *Search(BSTNode * node, int data);

//遍歷操作
void InOrderWalk(BSTNode * node);
void PreOrderWalk(BSTNode * node);
void PostOrderWalk(BSTNode * node);

//查詢最大最小值
BSTNode *Maximum(BSTNode * node);
BSTNode *Minimum(BSTNode * node);

//查找前驅與後繼
BSTNode *PreDecessor(BSTNode *node);
BSTNode *Successor(BSTNode *node);

#endif

(2)BinarySearchTree.cpp

#include "BinarySearchTree.h"
#include <iostream>

BinarySearchTree::BinarySearchTree()
{
    root = NULL;
}

BinarySearchTree::~BinarySearchTree()
{
    delete root;
}

//向二分查找樹中插入數據data
void BinarySearchTree::Insert(int data)
{
    BSTNode *node = new BSTNode(data);
    BSTNode *p = root, *q = NULL;
    while (p != NULL)
    {
        q = p;
        if (p->key > data)
            p = p->left;
        else
            p = p->right;
    }
    node->parent = q;
    if (q == NULL)
        root = node;
    else if (q->key > data)
        q->left = node;
    else
        q->right = node;
}

//從二分查找樹中刪除數據
BSTNode *BinarySearchTree::Delete(int data)
{
    BSTNode *node = Search(root, data);
    BSTNode *ret , *tmp;

    if (node == NULL)
        return node;

    //假設目標結點僅僅有一個子女則刪除該結點,否則刪除其後繼結點
    if (node->left == NULL || node->right == NULL)
        ret = node;
    else
        ret = Successor(node);

    //假設被刪結點有左右孩子,將其鏈接到被刪結點的父節點
    if (ret->left != NULL)
        tmp = ret->left;
    else
        tmp = ret->right;

    if (tmp != NULL)
        tmp->parent = ret->parent;
    //假設被刪結點的父節點為空,則說明要刪的是根節點
    if (ret->parent == NULL)
        root = tmp;
    else if (ret == ret->parent->left)
        ret->parent->left = tmp;
    else
        ret->parent->right = tmp;

    if (ret != node)
        node->key = ret->key;

    return ret;
}

//查找以node結點為根的子樹中值為data的結點
BSTNode *Search(BSTNode * node, int data)
{
    if (node == NULL || node->key == data)
        return node;
    if (data < node->key)
        return Search(node->left, data);
    else
        return Search(node->right, data);
}

//遍歷操作
void InOrderWalk(BSTNode * node)
{
    if (node != NULL)
    {
        InOrderWalk(node->left);
        std::cout << node->key << "\t";
        InOrderWalk(node->right);
    }
}

void PreOrderWalk(BSTNode * node)
{
    if (node != NULL)
    {
        std::cout << node->key << "\t";
        InOrderWalk(node->left);
        InOrderWalk(node->right);
    }
}

void PostOrderWalk(BSTNode * node)
{
    InOrderWalk(node->left);
    InOrderWalk(node->right);
    std::cout << node->key << "\t";
}

//查詢最大最小值
BSTNode *Maximum(BSTNode * node)
{
    while (node->left != NULL)
        node = node->left;
    return node;
}

BSTNode *Minimum(BSTNode * node)
{
    while (node->right != NULL)
        node = node->right;
    return node;
}

//查找前驅與後繼
BSTNode *PreDecessor(BSTNode *node)
{
    if (node->left != NULL)
        return Maximum(node->left);

    BSTNode *p = node->parent;
    while (p != NULL && node == p->left)
    {
        node = p;
        p = node->parent;
    }

    return p;
}


BSTNode *Successor(BSTNode *node)
{
    if (node->right != NULL)
        return Minimum(node->right);
    BSTNode *p = node->parent;
    while (p != NULL && node == p->right)
    {
        node = p;
        p = node->parent;
    }

    return p;
}

(3)main.cpp

#include "BinarySearchTree.h"
#include <iostream>
#include <cstdlib>
#include <ctime>

using namespace std;

const int MAX = 101;
const int N = 10;

int main()
{
    BinarySearchTree *bst = new BinarySearchTree();

    //設置隨機化種子,避免每次產生相同的隨機數   
    srand(time(0));

    //構造一個隨機數組成的二分查找樹
    for (int i = 0; i < N; i++)
        bst->Insert(rand() % MAX);

    //遍歷查找樹
    cout << "先序遍歷二分查找樹bst:" << endl;
    PreOrderWalk(bst->root);

    //遍歷查找樹
    cout << endl << "中序遍歷二分查找樹bst:" << endl;
    InOrderWalk(bst->root);

    //遍歷查找樹
    cout << endl << "後序遍歷二分查找樹bst:" << endl;
    PostOrderWalk(bst->root);

    int x = 45;
    BSTNode *node = Search(bst->root, x);
    if (node)
    {
        cout << endl << "二分查找樹bst中存在結點x = " << x << endl;
        bst->Delete(x);

        cout << endl << "刪除二分查找樹中結點x = " << x << "成功!" << endl;

        //遍歷查找樹
        cout << endl << "先序遍歷二分查找樹bst:" << endl;
        PreOrderWalk(bst->root);

        //遍歷查找樹
        cout << endl << "中序遍歷二分查找樹bst:" << endl;
        InOrderWalk(bst->root);

        //遍歷查找樹
        cout << endl << "後序遍歷二分查找樹bst:" << endl;
        PostOrderWalk(bst->root);
        cout << endl << endl;
    }
    else{
        cout << endl << "二分查找樹bst中不存在結點x = " << x << endl;
        cout << endl << endl;
    }

    system("pause");
    return 0;
}

測試結果(查找失敗的情況):
技術分享
測試結果(查找成功並刪除):
技術分享

3 隨機構造的二叉查找樹

在本章的最後介紹了一種隨機構造二叉查找樹的理論方法,這主要是針對普通二叉查找樹基本操作執行時間O(h)考慮的。這種方式能夠使得:
一棵在n個關鍵字上隨機構造的二叉查找樹的期望高度為O(logn)
這一部分感覺應用不多吧,木有細致看,^||^~~

《算法導論》— Chapter 12 二叉查找樹