二叉搜索樹作用、原理和實現(xiàn)(C和Python)

二叉搜索樹(Binary Search Tree)是干什么用的?

我知道的主要作用是搜索和動態(tài)排序,二叉樹進行插入/查詢/刪除的時間復雜度為O(log(n))。但是實際使用的時候通常不會有這么快,因為你插入順序所用的middle通常不是那么準,尤其是在插入數(shù)據(jù)的順序是有序或者基本有序的時候,這顆二叉樹會嚴重的不平衡,最糟糕的情況下會下降到和鏈表一樣。而解決這個問題的辦法是平衡二叉樹,但是今天這篇文章不會討論它,只會關(guān)注于基本的二叉搜索樹。

二叉搜索樹的基本原理

如果你不想看下面這些話,只想一句話明白搜索二叉樹怎么實現(xiàn):(key比自身小放左面,key比自身大放右邊),如果你看完這句話就明白了,那恭喜你,因為這個事情本來就是這么簡單,我之所以寫這篇文章其實就是想節(jié)省一些不喜歡看長篇大論的人的寶貴時間。。。

  1. 簡單來說搜索二叉樹的思想和二分插入排序的思想是一致的,就是用一個數(shù)做middle,然后將數(shù)據(jù)分成兩份,一份是比自己大的,一份是比自己小的。并且在插入和搜索時遵循相同的規(guī)則,就可以達到一個快速查找和排序的目的。
  2. 典型的二叉樹的數(shù)據(jù)結(jié)構(gòu)有3個部分:(查找鍵、左子節(jié)點指針、右子節(jié)點指針)。不過使用的時候通常會再加上:(數(shù)據(jù)指針、父節(jié)點指針)。
  3. 數(shù)據(jù)指針:通常在有key-value-store的需求時使用。(在我的示例中簡化了,數(shù)據(jù)簡單的用int來表示)
  4. 父節(jié)點指針:這不是必須的,但是加上這個指針會讓你在刪除節(jié)點等操作時更方便,空間換時間。
  5. 通常搜索二叉樹遵循如下原則:
  6. 左子節(jié)點小于自身。 右子節(jié)點大于自身。
  7. 所有子節(jié)點均遵從第一條規(guī)則。

如何實現(xiàn)一個二叉搜索樹

插入:

  1. 插入操作是比較簡單的,就跟上面說的一樣,比自己小就插到左邊,比自己大就插入到右邊,相等就更新自身。
  2. 我是使用遞歸實現(xiàn)的,因為我覺得這比較美觀。但這不是必須,而且從效率上來講可能不用遞歸會好一些,因為遞歸會帶來很高的函數(shù)調(diào)用開銷以及棧的瘋狂提高。(但是在現(xiàn)代的編譯器中這兩個問題可能不是問題,因為這些是可以被尾遞歸優(yōu)化的)
  3. 具體實現(xiàn)看nAdd函數(shù)。(這是不是一句廢話?如果是請告訴我,我會嘗試盡量不說廢話)

查找:

  1. 查找操作和插入操作是幾乎一樣。
  2. 實現(xiàn)請看nSearch.

刪除

  1. 刪除操作就稍微麻煩一些,而且我這段代碼寫的很丑,但是我懶得在寫了。(如果傳進去的是Node**類型,那么刪除時就會直觀一些,因為那樣就可以直接修改主函數(shù)棧上的根節(jié)點指針)
  2. 刪除節(jié)點時分三種情況:
  3. 沒有子節(jié)點:這個時候,直接將父節(jié)點的指針置空就可以了。并刪除自身。
  4. 有一個子節(jié)點,這個時候,需要將父節(jié)點和子節(jié)點互相指向。并刪除自身。
  5. 有兩個子節(jié)點時,需要挑選一個繼承者(后繼節(jié)點),然后將繼承者復制到要刪除的幾點。然后刪除繼承者原來在的節(jié)點。
  6. 前驅(qū)節(jié)點和后繼節(jié)點。
  7. 前驅(qū)節(jié)點:比當前節(jié)點小的節(jié)點中最大的節(jié)點。
  8. 后繼節(jié)點:比當前節(jié)點大的節(jié)點中最小的節(jié)點。
  9. 至于怎么找到他們你可能已經(jīng)想到了。
    1. 根據(jù)BST的特性,最大的節(jié)點肯定在“樹的最右邊”,最小的節(jié)點肯定在“樹的最左邊”。
    2. 查找前驅(qū)節(jié)點:就在當前節(jié)點的左子樹中查找最大的。
    3. 查找后繼節(jié)點:就在當前節(jié)點的右子樹中查找最小的。(實現(xiàn)請看nSuccessor)
  10. 實現(xiàn)請看nRemove

遍歷

  1. 前面又說BST可以用來動態(tài)排序,一般的排序方式都是一次性的排序,對于一直有動態(tài)插入的數(shù)據(jù)就不是一個類型的問題了。而BST用來解決這個問題是很好的,因為你只需要中序遍歷就可以得到一個有序的數(shù)據(jù)。
  2. 實現(xiàn)請看nWalk。

C實現(xiàn)(修改后的版本)

#include "StdAfx.h"

struct Node {
    int key;
    int value;
    // Node* parent;
    Node* left;
    Node* right;
};

Node* NewNode(int key, int value) {
    Node* p = (Node*)malloc(sizeof(Node));   // 刪除節(jié)點時要注意放掉內(nèi)存
    p->key = key;
    p->value = value;
    // p->parent = parent;
    p->left = NULL;
    p->right = NULL;
    return p;
}

Node** nSearch(Node** n, int key) {
    if (*n == NULL) {
        return n;
    }
    else if (key == (*n)->key) {
        return n;
    }
    else if (key < (*n)->key) {
        return nSearch(&(*n)->left, key);
    }
    else {
        return nSearch(&(*n)->right, key);
    }
}

void nAdd(Node** r, int key, int value) {
    Node** n = nSearch(r, key);
    if (*n == NULL) {
        *n = NewNode(key, value);
    }
    else {
        (*n)->value = value;
    }
}

int nGet(Node** n, int key) {
    Node** result = nSearch(n, key);
    if ((*result) == NULL) {
        return NULL;
    }
    else {
        return (*result)->value;
    }
}

void nWalk(Node* n) {
    if (n == NULL) {
        return;
    }
    if (n->left != NULL) {
        nWalk(n->left);
    }
    printf("%d\n", n->key);
    if (n->right != NULL) {
        nWalk(n->right);
    }
}

Node** nMin(Node** n) {
    if ((*n)->left == NULL) {
        return n;
    }
    else {
        return nMin(&(*n)->left);
    }
}

Node** nSuccessor(Node** n) {
    if ((*n)->right == NULL) {
        return NULL;
    }
    else {
        return nMin(&(*n)->right);
    }
}

int nRemove(Node** n, int key) {
    Node** d = nSearch(n, key);
    if ((*d) == NULL) {
        return -1; // 要刪除的幾點不存在
    }


    if ((*d)->left != NULL && (*d)->right != NULL) {
        Node** suc = nSuccessor(d);
        (*d)->key = (*suc)->key;
        (*d)->value = (*suc)->value;
        return nRemove(suc, (*suc)->key);
    }
    else {
        // 因為使用二級指針,不在需要判斷要刪除的節(jié)點本身是處在左邊還是右邊,因為指針中已經(jīng)指向了原始我們要修改的位置。
        Node* d_child;
        if ((*d)->left != NULL) {
            d_child = (*d)->left;
            // d_child->parent = (*d)->parent;
        }
        else if ((*d)->right != NULL) {
            d_child = (*d)->right;
            // d_child->parent = (*d)->parent;
        }
        else {
            d_child = NULL;
        }

        free(*d); 
        // 同樣的,這里也不再需要判斷是不是根節(jié)點
        *d = d_child;
        return 0;
    }
}

int main() {
    int testData[20][2] = {
        {61, 6161},
        {30, 3030},
        {98, 9898},
        {3, 33},
        {36, 3636},
        {30, 3030},
        {6, 66},
        {54, 5454},
        {63, 6363},
        {93, 9393},
        {93, 9393},
        {76, 7676},
        {84, 8484},
        {16, 1616},
        {13, 1313},
        {76, 7676},
        {78, 7878},
        {29, 2929},
        {9, 99},
        {76, 7676}
    };

    Node* root = NULL;
    Node** rootP = &root;

    int i;
    for (i = 0; i < 20; i++) {
        nAdd(rootP, testData[i][0], testData[i][1]);
    }
    nWalk(root);

    for (i = 0; i < 20; i++) {
        nRemove(rootP, testData[i][0]);
    }
    printf("\n\n");
    nWalk(root);

    return 0;
}

C實現(xiàn)(一開始的版本,留在這里為了讓大家對比看看修改后的)

#include "StdAfx.h"


struct Node {
    int key;
    int value;
    Node* parent;
    Node* left;
    Node* right;
};

Node* NewNode(int key, int value, Node* parent) {
    Node* p = (Node*)malloc(sizeof(Node));
    p->key = key;
    p->value = value;
    p->parent = parent;
    p->left = NULL;
    p->right = NULL;
    // 刪除節(jié)點時要注意free掉內(nèi)存
    return p;
}

void nAdd(Node* n, int key, int value) {
    if (key == n->key) {
        n->value = value;
    }
    else if (key < n->key) {
        if (n->left == NULL) {
            n->left = NewNode(key, value, n);
        }
        else {
            nAdd(n->left, key, value);
        }
    }
    else {
        if (n->right == NULL) {
            n->right = NewNode(key, value, n);
        }
        else {
            nAdd(n->right, key, value);
        }
    }

}

Node* nSearch(Node* n, int key) {
    if (key == n->key) {
        return n;
    }
    else if (key < n->key) {
        if (n->left == NULL) {
            return NULL;
        }
        else {
            return nSearch(n->left, key);
        }
    }
    else {
        if (n->right == NULL) {
            return NULL;
        }
        else {
            return nSearch(n->right, key);
        }
    }
}


int nGet(Node* n, int key) {
    Node* result = nSearch(n, key);
    if (result == NULL) {
        return NULL;
    }
    else {
        return result->value;
    }
}

void nWalk(Node* n) {
    if (n->left != NULL) {
        nWalk(n->left);
    }
    printf("%d\n", n->key);
    if (n->right != NULL) {
        nWalk(n->right);
    }
}

Node* nMin(Node* n) {
    if (n->left == NULL) {
        return n;
    }
    else {
        return nMin(n->left);
    }
}

Node* nSuccessor(Node* n) {
    if (n->right == NULL) {
        return NULL;
    }
    else {
        return nMin(n->right);
    }
}


int nRemove(Node* n, int key) {
    Node* d = nSearch(n, key);
    if (d == NULL) {
        return -1;
    }

    if (d->left != NULL && d->right != NULL) {
        Node* suc = nSuccessor(d);
        d->key = suc->key;
        d->value = suc->value;
        d = suc;
    }
    
    Node* d_child;
    if (d->left != NULL) {
        d_child = d->left;
        d_child->parent = d->parent;
    }
    else if (d->right != NULL) {
        d_child = d->right;
        d_child->parent = d->parent;
    }
    else {
        d_child = NULL;
    }

    if (d->parent != NULL) {
        if (d->parent->left == d) {
            d->parent->left = d_child;
        }
        else {
            d->parent->right = d_child;
        }

        free(d);
    }
    else {
        if (d_child == NULL) {
            // 這是最后一個節(jié)點,這個時候就不能free掉內(nèi)存了,否則main中root將指向野地址
            n->key = 0;
            n->value = 0;
            return -2;
        }
        else {
            // 當被刪除的是根節(jié)點的時候,要把繼承者的數(shù)據(jù)復制到根節(jié)點上。
            // 因為在此函數(shù)內(nèi)部無法改變main作用于中root的指向,如果刪除掉根節(jié)點,mian中的root將指向野地址
            *n = *d_child;
            free(d_child);
            return 0;
        }
        
    }
    return 0;
}

int main() {
    int testData[20][2] = {
        {61, 6161},
        {30, 3030},
        {98, 9898},
        {3, 33},
        {36, 3636},
        {30, 3030},
        {6, 66},
        {54, 5454},
        {63, 6363},
        {93, 9393},
        {93, 9393},
        {76, 7676},
        {84, 8484},
        {16, 1616},
        {13, 1313},
        {76, 7676},
        {78, 7878},
        {29, 2929},
        {9, 99},
        {76, 7676}
    };
    Node* root = NewNode(50, 10000, NULL);
    int i;
    for (i = 0; i < 20; i++) {
        nAdd(root, testData[i][0], testData[i][1]);
    }

    nWalk(root);

    nRemove(root, 50);
    for (i = 0; i < 20; i++) {
        nRemove(root, testData[i][0]);
    }
    nWalk(root);
    return 0;
}

Python實現(xiàn)

寫到這里其實有些困了,Python版本的刪除我記得好像是有一些bug,因為我真的是懶得把一份不直覺的代碼改對。而且本質(zhì)上來講這兩個刪除函數(shù)都是無可救藥的crap code, 請原諒我將這些垃圾發(fā)布上來,改天我會盡量重寫一份不辣眼睛的版本來贖罪,請原諒。 C語言重寫后的版本我已經(jīng)貼了上來,大家可以對比一下原來的版本,雖然是小修改,但是很滿意很美觀的改動。

有"tree"的版本:

# Python code:
class Node(object):
    def __init__(self, key, data, parent=None):
        self.key = key
        self.data = data
        self.parent = parent
        self.left = None
        self.right = None

    def add_child(self, key, data):
        if key == self.key:
            self.data = data
            return
        elif key < self.key:
            if self.left is None:
                self.left = Node(key, data, self)
            else:
                self.left.add_child(key, data)
        else:
            if self.right is None:
                self.right = Node(key, data, self)
            else:
                self.right.add_child(key, data)

    def get(self, key):
        if key == self.key:
            return self
        elif key < self.key:
            if self.left is None:
                return None
            else:
                return self.left.get(key)
        else:
            if self.right is None:
                return None
            else:
                return self.right.get(key)

    def get_data(self, key):
        r = self.get(key)
        if r is None:
            return None
        else:
            return r.data

    def in_order_walk(self):
        if self.left is not None:
            self.left.in_order_walk()
        print(self.key)
        if self.right is not None:
            self.right.in_order_walk()

    def get_max(self):
        if self.right is None:
            return self
        else:
            return self.right.get_max()

    def get_min(self):
        if self.left is None:
            return self
        else:
            return self.left.get_min()

    def predecessor(self):
        if self.left is not None:
            return self.left.get_max()
        else:
            return None

    def successor(self):
        if self.right is not None:
            return self.right.get_min()
        else:
            return None

    def remove(self, key):
        d = self.get(key)

        if not d.left and not d.right:
            if d.parent is None:
                # self = None
                return
            elif d.parent.left == d:
                d.parent.left = None
            else:
                d.parent.right = None
        elif bool(d.left) is not bool(d.right):
            if d.left:
                if d.parent.left == d:
                    d.parent.left = d.left
                    d.left.parent = d.parent
                else:
                    d.parent.right = d.left
                    d.left.parent = d.parent
            else:
                if d.parent.right == d:
                    d.parent.right = d.right
                    d.right.parent = d.parent
                else:
                    d.parent.left = d.right
                    d.right.parent = d.parent
        else:
            successor = d.successor()
            d.key = successor.key
            d.data = successor.data
            successor.remove(successor.key)


class Tree(object):
    def __init__(self, root):
        self.root = root

    def remove(self, key):
        d = self.root.get(key)
        if d.left and d.right:
            successor = d.successor()
            d.key = successor.key
            d.data = successor.data
            d = successor
        else:
            pass

        if d.left:
            d_child = d.left
            d_child.parent = d.parent
        elif d.right:
            d_child = d.right
            d_child.parent = d.parent
        else:
            d_child = None

        if d.parent:
            if d.parent.left == d:
                d.parent.left = d_child
            else:
                d.parent.right = d_child
        else:
            self.root = None

    def in_order_walk(self):
        if self.root:
            self.root.in_order_walk()
        else:
            print(None)

    def __getattr__(self, item):
        return getattr(self.root, item)


def main():
    data_list = [
        [20, 2222],
        [30, 3333],
        [40, 4444],
        [50, 5555],
        [60, 6666],
        [70, 7777],
        [80, 8888],
        [90, 9999],
    ]
    middle = data_list[5]
    tree = Tree(Node(middle[0], middle[1]))
    for i in data_list:
        tree.root.add_child(i[0], i[1])
    tree.add_child(1, 111)
    tree.remove(70)
    tree.in_order_walk()


if __name__ == '__main__':
    main()

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內(nèi)容