1. 二叉搜索树的定义及特点
二叉搜索树(英语:Binary Search Tree),也称二叉查找树、有序二叉树、排序二叉树。它具有如下性质:
- 若任意结点的左子树不为空,则左子树上所有结点的值均小于其根节点上的值。
- 若任意结点的右子树不为空,则右子树上所有结点的值均大于其根节点上的值。
- 任意结点的左右子树也称为二叉搜索树。
- 二叉搜索树中没有关键字相同的结点。
二叉搜索树支持许多动态集合操作,包括SEARCH
、MINIMUM
、MAXIMUM
、PREDECESSOR
、SUCCESSOR
、INSERT
和DELETE
等。当二叉搜索树是一棵完全二叉树的时候,其树高度为
lg
(
n
)
\lg (n)
lg(n),因此上述操作的最坏情况运行时间为
Θ
(
lg
n
)
\Theta(\lg n)
Θ(lgn)。当二叉搜索树为一条长度为
n
n
n的链表时,则相同的操作则需要
Θ
(
n
)
\Theta(n)
Θ(n)的最坏情况运行时间。
下图说明了两种情况:
![](https://i-blog.csdnimg.cn/blog_migrate/98cc3c1366af4991b1294725ac80fda1.png)
二叉搜索树通常由二叉链表实现,且结点的结构如下图所示,由三个指针域、一个关键字以及其他卫星数据构成。
![](https://i-blog.csdnimg.cn/blog_migrate/da690fc587cd7d69243c4c3b26e6aea3.png)
其中,三个指针分别指向父结点、左孩子结点和右孩子结点。
2. 二叉搜索树的建立及基本操作
二叉树的建立过程实际上就是向一棵空树中插入结点的过程,因此下面先介绍二叉搜索树的插入操作,然后再介绍二叉搜索树的建立,随后介绍一些如遍历、查找、删除等相关操作。
2.1 插入
下面的伪代码描述了向二叉搜索树中插入一个值的过程:
TREE_INSERT(T, z)
y = NIL
X = T.root
while x != NIL
y = x
if z.key < x.key
x = x.left
else x = x.right
z.p = y
if y == NIL
T.root = z
elseif z.key < y.key
y.left = z
else y.right = z
上述代码从树的根节点开始,通过一个while循环找到一个满足二叉搜索树性质的插入位置,其中x是要插入的位置,y是该位置的父结点。当父结点y是空结点时,说明该树为空树,因此将z作为根节点即可;当z小于y的值时,将z作为y的左孩子结点,否则作为y的右孩子结点。
2.2 建树
建树实际上就是一个迭代插入的过程,伪代码如下:
TREE_BUILD(T, A)
for i = 1 to A.length
TREE_INSERT(T, A[i])
注:如果A是有序序列时,上述建树的过程会得到一个长度为A.length的链表。因此,可以将上述过程随机化来使得所建的树的高度不会过高。
2.3 遍历
树的常见遍历方法有先序遍历、中序遍历和后序遍历三种。以中序优先遍历为例,其遍历过程是:①递归遍历结点的左子树;②访问当前结点;③递归遍历节点的右子树。注意该过程是一个递归的过程
![](https://i-blog.csdnimg.cn/blog_migrate/e1a44d0340f34dcbcb5c973c6533eafd.png)
中序遍历二叉搜索树的伪代码如下:
INORDER_TREE_WALK(x)
if x != NIL
INORDER_TREE_WALK(x.left)
print(x)
INORDER_TREE_WALK(x.right)
注:二叉搜索树的遍历时间为 Θ ( n ) \Theta(n) Θ(n)。
2.4 查找
根据二叉搜索树的性质可知,任意节点的左子树中的所有结点均小于等于该结点,右子树的所有结点均大于等于该结点。于是在二叉搜索树中查找关键字的伪代码如下:
TREE_SEARCH(key)
x = root;
while x != NIL
if key == x.key
return x
else if key < x.key
x = x.left;
else x = x.right
return NIL
注:如果查找到关键字key,则返回该结点,否则返回NIL。
2.5 删除
在二叉搜索树上进行删除操作比上述几种操作要复杂许多,因为在删除结点的同时我们不能破坏二叉搜索树的性质。下面将在二叉搜索树中删除结点的情况分为两类共五种情况:
![](https://i-blog.csdnimg.cn/blog_migrate/9bdbefbd259345d8eb567a7261382f2f.png)
第一类情况为该结点至多存在一个孩子结点的情况,这种情况很好解决,只需要用孩子结点直接替代该结点即可。
第二类情况为该结点存在两个孩子结点的情况,这个时候需要先找到该结点的后继结点y(注意该后继结点肯定没有左孩子),也就是上图中深灰色的结点。
该后继结点y有两种可能性:①后继结点y是x的右孩子(情况d);②后继结点y不是x的有孩子(情况e)。针对这两种情况,采取的方法如下:
情况d:用y替换x,并仅留下y的右孩子。
![](https://i-blog.csdnimg.cn/blog_migrate/5d678fe526bf82b67fc2a4ff11856a07.png)
情况e:用y的右孩子替换y,然后用y替换x。
![](https://i-blog.csdnimg.cn/blog_migrate/57c40144287cba16f2a327aecdcefcbc.png)
上述删除的过程中有多处进行了替换操作,因此先定义下面的替换子过程:
TRANSPLANT(T, u, v)
if u.p == NIL
T.root = v
elseif u == u.p.left
u.p.left = v
else u.p.right = v
if v != NIL
v.p = u.p
于是,完整的删除操作的伪代码如下:
TREE_DELETE(T, x)
if x.left == NIL
TRANSPLANT(T, x, x.right)
elseif x.right == NIL
TRANSPLANT(T, x, x.left)
else
y = TREE_MINIMUM(x.right)
if y.p != x
TRANSPLANT(T, y, y.right)
y.right = x.right
y.right.p = y
TRANSPLANT(T, x, y)
y.left = x.left
y.left.p = y
注:上述伪代码的前两个if分支为上图中的(b)、(c)两种情况,这其实也包含了情况(a);最后一个else分支就是为了处理(d)和(e)两种情况。
3. 附录(代码)
#include <iostream>
#include <vector>
using namespace std;
struct Node {
Node *p = nullptr;
Node *r = nullptr;
Node *l = nullptr;
int key;
};
class BSTree {
private:
void transplant(Node* u, Node* v);
public:
// constructor & destructor
~BSTree() {};
// typedef
typedef void(*callback)(Node*);
// root
Node *root = nullptr;
// User function
void build(vector<int> data);
void traverse(Node* T, callback visit);
Node* search(int key);
Node* successor(Node * node);
Node* min(Node* node);
Node* max(Node* node);
void insert(int key);
void del(int key);
};
void BSTree::insert(int key) {
// insert a node into the tree
Node* x = root;
Node* y = nullptr;
Node* node = new Node{ nullptr, nullptr, nullptr, key };
while (x != nullptr) {
y = x;
if (key == x->key) return;
else if (key < x->key) x = x->l;
else x = x->r;
}
if (y == nullptr)
root = node;
else if (node->key < y->key)
y->l = node;
else y->r = node;
node->p = y;
}
void BSTree::build(vector<int> data) {
// build a tree by vector<int>
for (int i : data)
insert(i);
}
Node* BSTree::successor(Node* node) {
// return the successor
Node* x = node;
Node* y = nullptr;
if (x->r) return min(x->r);
y = x->p;
while (y != nullptr && x == y->r) {
x = y;
y = y->p;
}
return y;
}
void BSTree::transplant(Node* u, Node* v) {
// replace u with v
if (u->p == nullptr) root = u;
else if (u == u->p->l) u->p->l = u;
else u->p->r = v;
if (v != nullptr) v->p = u->p;
}
void BSTree::del(int key) {
// delete a node by the key
Node* node = search(key);
if (node->l == nullptr)
transplant(node, node->r);
else if (node->r == nullptr)
transplant(node, node->l);
else {
Node* y = min(node->r);
if (y->p != node) {
transplant(y, y->r);
y->r = node->r;
y->r->p = y;
}
transplant(node, y);
y->l = node->l;
y->l->p = y;
}
delete node;
}
void BSTree::traverse(Node* T, callback visit) {
// inorder traversal
Node* x = T;
if (x != nullptr) {
traverse(x->l, visit);
visit(x);
traverse(x->r, visit);
}
}
Node* BSTree::search(int key) {
// search in the tree
Node* x = root;
while(x != nullptr) {
if (key == x->key) return x;
else if (key < x->key) x = x->l;
else x = x->r;
}
return nullptr;
}
Node* BSTree::min(Node* node) {
// return a pointer of the min node
Node* x = node;
Node* y = node;
if (x == nullptr) {
cout << "Empty tree!" << endl;
return nullptr;
}
while (x != nullptr) {
y = x;
x = x->l;
}
return y;
}
Node* BSTree::max(Node* node) {
// return a pointer of the max node
Node* x = node;
Node* y = node;
if (x == nullptr) {
cout << "Empty tree!" << endl;
return nullptr;
}
while (x != nullptr) {
y = x;
x = x->r;
}
return y;
}
void visit(Node* node) {
cout << node->key << " ";
}
int main(int argc, char* argv[]) {
vector<int> data = { 7, 2, 8, 3, 6, 4, 3, 1, 5, 9, 0 };
BSTree T;
T.build(data);
T.traverse(T.root, visit);
cout << endl;
T.del(5);
T.traverse(T.root, visit);
cout << endl;
if (T.search(3)) cout << "success!" << endl;
cout << T.min(T.root)->key << " " << T.max(T.root)->key << endl;
return 0;
}
运行结果如下:
0 1 2 3 4 5 6 7 8 9
0 1 2 3 4 6 7 8 9
success!
0 9