二叉搜索樹(Binary Search Tree)是干什么用的?
我知道的主要作用是搜索和動態(tài)排序,二叉樹進行插入/查詢/刪除的時間復雜度為O(log(n))。但是實際使用的時候通常不會有這么快,因為你插入順序所用的middle通常不是那么準,尤其是在插入數(shù)據(jù)的順序是有序或者基本有序的時候,這顆二叉樹會嚴重的不平衡,最糟糕的情況下會下降到和鏈表一樣。而解決這個問題的辦法是平衡二叉樹,但是今天這篇文章不會討論它,只會關(guān)注于基本的二叉搜索樹。
二叉搜索樹的基本原理
如果你不想看下面這些話,只想一句話明白搜索二叉樹怎么實現(xiàn):(key比自身小放左面,key比自身大放右邊),如果你看完這句話就明白了,那恭喜你,因為這個事情本來就是這么簡單,我之所以寫這篇文章其實就是想節(jié)省一些不喜歡看長篇大論的人的寶貴時間。。。
- 簡單來說搜索二叉樹的思想和二分插入排序的思想是一致的,就是用一個數(shù)做middle,然后將數(shù)據(jù)分成兩份,一份是比自己大的,一份是比自己小的。并且在插入和搜索時遵循相同的規(guī)則,就可以達到一個快速查找和排序的目的。
- 典型的二叉樹的數(shù)據(jù)結(jié)構(gòu)有3個部分:(查找鍵、左子節(jié)點指針、右子節(jié)點指針)。不過使用的時候通常會再加上:(數(shù)據(jù)指針、父節(jié)點指針)。
- 數(shù)據(jù)指針:通常在有key-value-store的需求時使用。(在我的示例中簡化了,數(shù)據(jù)簡單的用int來表示)
- 父節(jié)點指針:這不是必須的,但是加上這個指針會讓你在刪除節(jié)點等操作時更方便,空間換時間。
- 通常搜索二叉樹遵循如下原則:
- 左子節(jié)點小于自身。 右子節(jié)點大于自身。
- 所有子節(jié)點均遵從第一條規(guī)則。
如何實現(xiàn)一個二叉搜索樹
插入:
- 插入操作是比較簡單的,就跟上面說的一樣,比自己小就插到左邊,比自己大就插入到右邊,相等就更新自身。
- 我是使用遞歸實現(xiàn)的,因為我覺得這比較美觀。但這不是必須,而且從效率上來講可能不用遞歸會好一些,因為遞歸會帶來很高的函數(shù)調(diào)用開銷以及棧的瘋狂提高。(但是在現(xiàn)代的編譯器中這兩個問題可能不是問題,因為這些是可以被尾遞歸優(yōu)化的)
- 具體實現(xiàn)看nAdd函數(shù)。(這是不是一句廢話?如果是請告訴我,我會嘗試盡量不說廢話)
查找:
- 查找操作和插入操作是幾乎一樣。
- 實現(xiàn)請看nSearch.
刪除
- 刪除操作就稍微麻煩一些,而且我這段代碼寫的很丑,但是我懶得在寫了。(如果傳進去的是Node**類型,那么刪除時就會直觀一些,因為那樣就可以直接修改主函數(shù)棧上的根節(jié)點指針)
- 刪除節(jié)點時分三種情況:
- 沒有子節(jié)點:這個時候,直接將父節(jié)點的指針置空就可以了。并刪除自身。
- 有一個子節(jié)點,這個時候,需要將父節(jié)點和子節(jié)點互相指向。并刪除自身。
- 有兩個子節(jié)點時,需要挑選一個繼承者(后繼節(jié)點),然后將繼承者復制到要刪除的幾點。然后刪除繼承者原來在的節(jié)點。
- 前驅(qū)節(jié)點和后繼節(jié)點。
- 前驅(qū)節(jié)點:比當前節(jié)點小的節(jié)點中最大的節(jié)點。
- 后繼節(jié)點:比當前節(jié)點大的節(jié)點中最小的節(jié)點。
- 至于怎么找到他們你可能已經(jīng)想到了。
- 根據(jù)BST的特性,最大的節(jié)點肯定在“樹的最右邊”,最小的節(jié)點肯定在“樹的最左邊”。
- 查找前驅(qū)節(jié)點:就在當前節(jié)點的左子樹中查找最大的。
- 查找后繼節(jié)點:就在當前節(jié)點的右子樹中查找最小的。(實現(xiàn)請看nSuccessor)
- 實現(xiàn)請看nRemove
遍歷
- 前面又說BST可以用來動態(tài)排序,一般的排序方式都是一次性的排序,對于一直有動態(tài)插入的數(shù)據(jù)就不是一個類型的問題了。而BST用來解決這個問題是很好的,因為你只需要中序遍歷就可以得到一個有序的數(shù)據(jù)。
- 實現(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()