數據結構之樹篇2——二叉排序(查找,搜索)樹

引入

基本性質:

二叉排序樹(又叫二叉搜索、查找樹) 是一種特殊的二叉樹,定義以下:數據結構

  1. 若左子樹不空,則左子樹上全部結點的值均小於它的根結點的值;
  2. 若右子樹不空,則右子樹上全部結點的值均大於它的根結點的值;
  3. 左、右子樹也分別爲二叉排序樹。
  4. 不容許有鍵值相同結點。【若是真的出現了,那麼放在左子樹,右子樹是無所謂的】

二分查找與二叉排序樹

​ 二分查找也稱爲折半查找,要求原線性表有序,它是一種效率很高的查找方法。若是在須要進行頻繁修改的表中採用二分查找,其效率也是很是低下的,由於順序表的修改操做效率低。若是隻考慮頻繁的修改,咱們能夠採用鏈表。然而,鏈表的查找效率又很是低。綜合這兩種數據結構的優點,二叉查找樹(Binary Sort/Search Tree)登場了。函數

給定一串數\((65,32,87,46,71,98,39)\),使用插入構造法,步驟以下:ui

二叉排序樹的基本操做

結構體定義

typedef struct BTNode
{
    char data;
    struct BTNode* Left;
    struct BTNode* Right;
}*BTree;

typedef BTNode BSTNode, *BSTree;

查找節點

若是是普通二叉樹的查找操做,由於沒法肯定二叉樹的具體特性,所以只能對其左右子樹都進行遍歷。但二叉排序樹的特性,則會有一條肯定的路線。spa

//找不到返回NULL,找到返回該節點。
//非遞歸
BSTNode* BSTreeFind(BSTree t, int x) {
    if (!t)return NULL;
    if (t->data == x) return t;
    if (x < t->data) return BSTreeFind(t->Left, x);
    if (x > t->data) return BSTreeFind(t->Right, x);
}
//非遞歸
BSTNode* BSTFind(BSTree T,int x) {
    BSTree p = T;
    while (p) {
        if (x == p->data)
            return p;
        p = x > p->data ? p->Right : p->Left;
    }
    return NULL;
}

插入節點

對於一顆二叉排序樹來講,若是查找某個元素成功,說明該結點存在;若是查找失敗,則查找失敗的地方必定就是該結點應該插入的地方。指針

BSTree InsertBStree(BSTree BST, int x) {
    if (BST == NULL) {
        BST = new BSTNode;
        BST->data = x;
        return BST;
    }
    if (x < BST->data)
        BST->Left = InsertBStree(BST->Left, x);
    else
        BST->Right = InsertBStree(BST->Right, x);
    return BST;
}

二叉排序樹的創建

創建一顆二叉排序樹,就是前後插入\(n\)個結點的過程,與通常二叉樹的創建是徹底同樣的。code

BSTree BuildBSTree(int* a, int length) {
    BSTree BST = NULL;
    for (int i = 0; i < length; i++)
        BST = InsertBStree(BST, a[i]);
    return BST;
}

二叉排序樹的刪除

​ 二叉查找樹的刪除操做通常有兩種常見作法,複雜度都是\(O(h)\)\(O(log(n))\),其中 \(h\) 爲樹的高度,\(n\) 爲結點個數。blog

​ 以下圖的二叉排序樹,若是要刪除結點 \(5\),則有兩種辦法,一種辦法是以樹中比 \(5\) 小的最大結點(結點 \(4\) )覆蓋結點 \(5\) ,另外一種是用樹中比 \(5\) 大的最小結點(結點 \(6\) )覆蓋結點 \(5\)排序

​ 在二叉排序樹中把比該結點權值小的最大結點稱爲該結點的前驅,把比該結點權值大的最小結點稱爲該結點的後繼。結點的前驅是左子樹的最右結點,結點的後繼是右子樹的最左結點。用下面兩個函數用來尋找以root爲根的樹中最大、最小權值的結點,後面會使用到。遞歸

//得到以root爲根結點的樹中的最大值結點
int GetMax(BSTree root) {
    while (root->Right != NULL)
        root = root->Right;
    return root->data;
}

//得到以root爲根結點的樹中的最小值
int GetMin(BSTree root) {
    while (root->Left != NULL) {
        root = root->Left;
    }
    return root->data;
}

假設決定使用結點\(N\)的前驅 \(P\) 來替換 \(N\) ,因而就把問題轉換爲,先用 \(P\) 的值去覆蓋 \(N\) 的權值,再刪除結點\(P\),因而刪除操做的實現以下:(寫法1好理解,寫法2更簡潔)class

寫法1:

使用 BSTree root,若是 root 爲要刪除的結點, \(P\) 爲父節點,刪除狀況有如下3種:

  1. root 結點是葉子,將 \(P\) 節點的 leftright 指針域置爲NULL

  2. root 結點只有左子樹,將 root 節點的 left 從新到結點 \(P\) 上。和刪除單鏈表節點相似。(只有右子樹時狀況類似。第一種狀況,寫的時候能夠和第二種合併起來)

  3. root 結點既有左子樹,也有右子樹,尋找結點的前驅 pre(或者後繼),用 pre 的值去覆蓋 root 的權值,再刪除結點 pre​,而這個 pre 的刪除必定屬於狀況 1 或者 2 。

​ 這個寫法更適合JAVA,代碼裏的那些 delete 還能夠省去,可是必需要注意的是:函數的返回值不是多餘的,刪除只有一個(或沒有)子樹的根節點的時候,必須用到這個返回值。

實現方法1:

//刪除結點左子樹的最大值結點
BSTree DelMax(BSTree root) {
    if (root->Right == NULL) {
        BSTNode *t= root->Left;
        delete root;
        return t;
    }
    root->Right = DelMax(root->Right);
    return root;
}

BSTree BSTDel(BSTree root,int x) {
    if (root == NULL) 
        return NULL;
    if (root->data == x) {
        //狀況1和2,被刪除的結點只有左子樹或右子樹,或沒有子樹
        if (! root->Right || !root->Left) {
            BSTNode *t = !root->Right ? root->Left : root->Right;
            delete root;
            return t;
        }
        //刪除既有左子樹,也有右子樹的結點
        else{
            root->data = GetMax(root->Left);
            root->Left = DelMax(root->Left);
        }
    }
    else if (x > root->data) 
        root->Right = BSTDel(root->Right, x);
    else  
        root->Left = BSTDel(root->Left, x);
    return root;
}

實現方法2:

//刪除子樹中的最大值
void DelMax(BSTree &root,int &value) {
    if (!root)return;
    if (root->Right == NULL) {
        BSTNode *t= root;
        value = root->data;
        root = root->Left;
        delete t;
    }
    else
        DelMax(root->Right, value);
}

void BSTDel(BSTree &root,int x) {
    if (root == NULL) 
        return;
    BSTree p = root;
    if (p->data == x) {
        if (! p->Right || !p->Left) {
            root = !p->Right ? p->Left : p->Right;
            delete p;
        }
        else
            //由於使用的方法是值替換,並無把原來的root覆蓋掉,因此delete p不能像和下面方法2同樣寫在整個大if裏
            DelMax(root->Left, root->data);
    }
    else if (x > p->data) BSTDel(p->Right, x);
    else  BSTDel(p->Left, x);
}

寫法2:

使用 BSTree *root,和上面的BSTree &root是等價的,若是 root 爲要刪除的結點,刪除狀況也是如下3種:

  1. 當前 root 結點是葉子,將 root 置空。
  2. root 結點只有左子樹,將 root 置爲 root->Right(只有右子樹時狀況類似。第一種狀況,寫的時候能夠和第二種合併起來)
  3. 被刪除的結點既有左子樹,也有右子樹,尋找結點的前驅 pre(或者後繼),用 root 的指針域覆蓋 pre 的指針域,再把結點 root 刪除。(形象化來講就是刪除 root,把 pre 移動到 root 的位置)

void BSTDel(BSTree *root, int x){
    if (!*root) return; 
    BSTree p = *root;
    if (p->data == x) {
        if (!p->Right || !p->Left)
            *root = !p->Right ? p->Left : p->Right;
        else{
                BSTNode *parent = p->Left, *q = p->Left;
                if (!q->Right) q->Right = p->Right;
                else {
                    while (q->Right) {
                        parent = q;
                        q = q->Right;
                    }
                    parent->Right = q->Left;
                    q->Left = p->Left;
                    q->Right = p->Right;
                }
                *root = q;
        }
        delete p;
    }
    else if (x > p->data) //向右找
        BSTDel(&(p->Right), x);
    else if (x < p->data) //向左找
        BSTDel(&(p->Left), x);
}

二叉排序樹的性質

二叉排序樹最基本的性質:二叉排序樹的中序序列是有序的。

若是合理調整二叉排序樹的形態,使得樹上的每一個結點都儘可能有兩個子結點,這樣整個二叉樹的高度就會大約在\(log(n)\) 左右,其中 \(n\) 爲結點個數。實現這個要求的一種樹就是下一篇平衡二叉樹(AVL Tree)。

相關文章
相關標籤/搜索