二叉搜索樹(shù)(Binary Search Tree)是干什么用的?
我知道的主要作用是搜索和動(dòng)態(tài)排序,二叉樹(shù)進(jìn)行插入/查詢/刪除的時(shí)間復(fù)雜度為O(log(n))。但是實(shí)際使用的時(shí)候通常不會(huì)有這么快,因?yàn)槟悴迦腠樞蛩玫膍iddle通常不是那么準(zhǔn),尤其是在插入數(shù)據(jù)的順序是有序或者基本有序的時(shí)候,這顆二叉樹(shù)會(huì)嚴(yán)重的不平衡,最糟糕的情況下會(huì)下降到和鏈表一樣。而解決這個(gè)問(wèn)題的辦法是平衡二叉樹(shù),但是今天這篇文章不會(huì)討論它,只會(huì)關(guān)注于基本的二叉搜索樹(shù)。
二叉搜索樹(shù)的基本原理
如果你不想看下面這些話,只想一句話明白搜索二叉樹(shù)怎么實(shí)現(xiàn):(key比自身小放左面,key比自身大放右邊),如果你看完這句話就明白了,那恭喜你,因?yàn)檫@個(gè)事情本來(lái)就是這么簡(jiǎn)單,我之所以寫(xiě)這篇文章其實(shí)就是想節(jié)省一些不喜歡看長(zhǎng)篇大論的人的寶貴時(shí)間。。。
- 簡(jiǎn)單來(lái)說(shuō)搜索二叉樹(shù)的思想和二分插入排序的思想是一致的,就是用一個(gè)數(shù)做middle,然后將數(shù)據(jù)分成兩份,一份是比自己大的,一份是比自己小的。并且在插入和搜索時(shí)遵循相同的規(guī)則,就可以達(dá)到一個(gè)快速查找和排序的目的。
- 典型的二叉樹(shù)的數(shù)據(jù)結(jié)構(gòu)有3個(gè)部分:(查找鍵、左子節(jié)點(diǎn)指針、右子節(jié)點(diǎn)指針)。不過(guò)使用的時(shí)候通常會(huì)再加上:(數(shù)據(jù)指針、父節(jié)點(diǎn)指針)。
- 數(shù)據(jù)指針:通常在有key-value-store的需求時(shí)使用。(在我的示例中簡(jiǎn)化了,數(shù)據(jù)簡(jiǎn)單的用int來(lái)表示)
- 父節(jié)點(diǎn)指針:這不是必須的,但是加上這個(gè)指針會(huì)讓你在刪除節(jié)點(diǎn)等操作時(shí)更方便,空間換時(shí)間。
- 通常搜索二叉樹(shù)遵循如下原則:
- 左子節(jié)點(diǎn)小于自身。 右子節(jié)點(diǎn)大于自身。
- 所有子節(jié)點(diǎn)均遵從第一條規(guī)則。
如何實(shí)現(xiàn)一個(gè)二叉搜索樹(shù)
插入:
- 插入操作是比較簡(jiǎn)單的,就跟上面說(shuō)的一樣,比自己小就插到左邊,比自己大就插入到右邊,相等就更新自身。
- 我是使用遞歸實(shí)現(xiàn)的,因?yàn)槲矣X(jué)得這比較美觀。但這不是必須,而且從效率上來(lái)講可能不用遞歸會(huì)好一些,因?yàn)檫f歸會(huì)帶來(lái)很高的函數(shù)調(diào)用開(kāi)銷以及棧的瘋狂提高。(但是在現(xiàn)代的編譯器中這兩個(gè)問(wèn)題可能不是問(wèn)題,因?yàn)檫@些是可以被尾遞歸優(yōu)化的)
- 具體實(shí)現(xiàn)看nAdd函數(shù)。(這是不是一句廢話?如果是請(qǐng)告訴我,我會(huì)嘗試盡量不說(shuō)廢話)
查找:
- 查找操作和插入操作是幾乎一樣。
- 實(shí)現(xiàn)請(qǐng)看nSearch.
刪除
- 刪除操作就稍微麻煩一些,而且我這段代碼寫(xiě)的很丑,但是我懶得在寫(xiě)了。(如果傳進(jìn)去的是Node**類型,那么刪除時(shí)就會(huì)直觀一些,因?yàn)槟菢泳涂梢灾苯有薷闹骱瘮?shù)棧上的根節(jié)點(diǎn)指針)
- 刪除節(jié)點(diǎn)時(shí)分三種情況:
- 沒(méi)有子節(jié)點(diǎn):這個(gè)時(shí)候,直接將父節(jié)點(diǎn)的指針置空就可以了。并刪除自身。
- 有一個(gè)子節(jié)點(diǎn),這個(gè)時(shí)候,需要將父節(jié)點(diǎn)和子節(jié)點(diǎn)互相指向。并刪除自身。
- 有兩個(gè)子節(jié)點(diǎn)時(shí),需要挑選一個(gè)繼承者(后繼節(jié)點(diǎn)),然后將繼承者復(fù)制到要?jiǎng)h除的幾點(diǎn)。然后刪除繼承者原來(lái)在的節(jié)點(diǎn)。
- 前驅(qū)節(jié)點(diǎn)和后繼節(jié)點(diǎn)。
- 前驅(qū)節(jié)點(diǎn):比當(dāng)前節(jié)點(diǎn)小的節(jié)點(diǎn)中最大的節(jié)點(diǎn)。
- 后繼節(jié)點(diǎn):比當(dāng)前節(jié)點(diǎn)大的節(jié)點(diǎn)中最小的節(jié)點(diǎn)。
- 至于怎么找到他們你可能已經(jīng)想到了。
- 根據(jù)BST的特性,最大的節(jié)點(diǎn)肯定在“樹(shù)的最右邊”,最小的節(jié)點(diǎn)肯定在“樹(shù)的最左邊”。
- 查找前驅(qū)節(jié)點(diǎn):就在當(dāng)前節(jié)點(diǎn)的左子樹(shù)中查找最大的。
- 查找后繼節(jié)點(diǎn):就在當(dāng)前節(jié)點(diǎn)的右子樹(shù)中查找最小的。(實(shí)現(xiàn)請(qǐng)看nSuccessor)
- 實(shí)現(xiàn)請(qǐng)看nRemove
遍歷
- 前面又說(shuō)BST可以用來(lái)動(dòng)態(tài)排序,一般的排序方式都是一次性的排序,對(duì)于一直有動(dòng)態(tài)插入的數(shù)據(jù)就不是一個(gè)類型的問(wèn)題了。而B(niǎo)ST用來(lái)解決這個(gè)問(wèn)題是很好的,因?yàn)槟阒恍枰行虮闅v就可以得到一個(gè)有序的數(shù)據(jù)。
- 實(shí)現(xiàn)請(qǐng)看nWalk。
C實(shí)現(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é)點(diǎn)時(shí)要注意放掉內(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; // 要?jiǎng)h除的幾點(diǎn)不存在
}
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 {
// 因?yàn)槭褂枚?jí)指針,不在需要判斷要?jiǎng)h除的節(jié)點(diǎn)本身是處在左邊還是右邊,因?yàn)橹羔樦幸呀?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é)點(diǎn)
*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實(shí)現(xiàn)(一開(kāi)始的版本,留在這里為了讓大家對(duì)比看看修改后的)
#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é)點(diǎn)時(shí)要注意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) {
// 這是最后一個(gè)節(jié)點(diǎn),這個(gè)時(shí)候就不能free掉內(nèi)存了,否則main中root將指向野地址
n->key = 0;
n->value = 0;
return -2;
}
else {
// 當(dāng)被刪除的是根節(jié)點(diǎn)的時(shí)候,要把繼承者的數(shù)據(jù)復(fù)制到根節(jié)點(diǎn)上。
// 因?yàn)樵诖撕瘮?shù)內(nèi)部無(wú)法改變main作用于中root的指向,如果刪除掉根節(jié)點(diǎn),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實(shí)現(xiàn)
寫(xiě)到這里其實(shí)有些困了,Python版本的刪除我記得好像是有一些bug,因?yàn)槲艺娴氖菓械冒岩环莶恢庇X(jué)的代碼改對(duì)。而且本質(zhì)上來(lái)講這兩個(gè)刪除函數(shù)都是無(wú)可救藥的crap code, 請(qǐng)?jiān)徫覍⑦@些垃圾發(fā)布上來(lái),改天我會(huì)盡量重寫(xiě)一份不辣眼睛的版本來(lái)贖罪,請(qǐng)?jiān)彙?/del> C語(yǔ)言重寫(xiě)后的版本我已經(jīng)貼了上來(lái),大家可以對(duì)比一下原來(lái)的版本,雖然是小修改,但是很滿意很美觀的改動(dò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()