二叉搜索树(BST)——基本概念及基本实现代码
1.二叉搜索树概念
二叉搜索树是以一棵二叉树来组织的。每个节点是一个对象,包含的属性有left,right,p和key,其中,left指向该节点的左孩子,right指向该节点的右孩子,p指向该节点的父节点,key是它的值,如果某节点的孩子节点或父节点不存在,则相应属性设置为NULL。根节点是树中唯一一个父节点为NULL的节点。
BST中的关键字有如下特征:
假设x为BST里的一个节点:
(1)如果y为x的左子树里的一个节点,那么y.key<=x.key;
(2)如果y为x的右子树里的一个节点,那么y.key>=x.key.
下面是一个二叉搜索树的例子:
2.二叉搜索树的几个实现函数
2.1 自定义二叉搜索树的数据结构
//树节点
typedef struct Treenode{
int key;
Treenode *p;
Treenode *left;
Treenode *right;
Treenode(int val):key(val),left(NULL),right(NULL),p(NULL) {
}
};
//树
typedef struct Tree{
Treenode* root; //指向根节点
};
2.2 查询二叉搜索树
输入一个指向树根的指针和一个关键字k,如果这个节点存在,则返回一个指向关键字为k的节点的指针;否则返回NULL。
Treenode* TREE_SEARCH(Treenode *x, int k) {
if(x==NULL || x->key==k) {
return x;
}
if(k < x->key) {
TREE_SEARCH(x->left, k);
}
if(k > x->key) {
TREE_SEARCH(x->right, k);
}
}
2.3 查找最小关键字元素
要求:
给定一棵树的根节点,返回指向这棵树中最小元素的指针。
思路:
若一个节点X没有左子树,那么由于其右子树里的节点都比它大,因此该节点在以其为根节点的这棵树里是最小的;若节点X有左子树那么由于右子树里的关键字都大于或等于X.key,而左子树里的节点的关键字都小于等于X.key,所以这棵树的最小元素一定在以X.left为根节点的子树里。
Treenode* TREE_MINIMUM(Treenode *x) {
if(x->left == NULL) {
return x;
}
return TREE_MINIMUM(x->left);
}
2.4 查找最大关键字元素
要求
给定一棵树的根节点,返回指向这棵树中最大元素的指针。
思路:
与查找最小关键字元素的思路一样。
Treenode* TREE_MAXIMUM(Treenode *x) {
if(x->right == NULL) {
return x;
}
return TREE_MAXIMUM(x->right);
}
2.5 查找后继
要求:
给定一个节点x,若该节点的后继存在,则返回一棵二叉搜索树里的节点x的后继节点;若节点x是该树中的最大关键字节点,则返回NULL。
思路:
一个节点的后继节点是关键字比它大的节点,如果该节点的关键字是树中的最大关键字,则其没有后继;如果不是最大关键字,那它一定有后继节点,这时分2种情况来考虑:
(1)节点x有右子树,那么后继节点就是右子树中关键字最小的节点,可以通过调用TREE_MINIMUM(x->right)获得;
(2)节点x没有右子树,假设其后继节点为y,那么y一定是x的低层祖先,且x在y的左子树里。例如下面这个例子,节点3没有右子树,那就从它的父节点开始,一直找父节点,直到找到一个祖先节点,x在其左子树里,那么这个节点就是其后继节点。
Treenode* TREE_SUCCESSOR(Treenode *x) {
if(x->right != NULL) {
return TREE_MINIMUM(x->right);
}
Treenode* p = x->p;
Treenode* y = x;
while(p != NULL) {
if(p->left == y) {
break;
}
y = p;
p = p->p;
}
return p;
}
2.6 查找前驱
查找前驱与查找后继是对称的。
Treenode* TREE_PREDECESSOR(Treenode *x) {
if(x->left != NULL) {
return TREE_MAXIMUM(x->left);
}
Treenode* p = x->p;
Treenode* y = x;
while(p != NULL) {
if(p->right == y) {
break;
}
y = p;
p = p->p;
}
return p;
}
2.7 插入
要求:
给一颗树及一个新节点的key,将新节点插入到树里的相应位置。
void TREE_INSERT(Tree* t, int insert_number) {
/*构造新节点*/
Treenode* new_node = (Treenode*)malloc(sizeof(Treenode));
new_node->key = insert_number;
new_node->left = new_node->right = new_node->p = NULL;
Treenode* temp = t->root;
//如果是一棵空树
if(temp == NULL) {
t->root = new_node;
return;
}
Treenode* temp1;
while(temp != NULL) {
temp1 = temp;
if(temp->key > insert_number) {
temp = temp->left;
}
else if(temp->key < insert_number) {
temp = temp->right;
}
}
new_node->p = temp1;
if(new_node->key < temp1->key) {
temp1->left = new_node;
}
else {
temp1->right = new_node;
}
}
2.8 删除
要求:
从一棵二叉搜索树T中删除一个给定节点z。
思路:
要分一下几种情况考虑:
(1)z没有左孩子
此时,用z的右孩子来替换z,其右孩子可以是NULL,也可以不是。
(2)z只有一个孩子,且为左孩子节点
此时用z的左孩子来替换z。
(3)z既有一个左孩子节点,又有一个右孩子节点
此时应该查找z的后继节点y,并用y来替换z。在上面已经介绍过,后继节点y一定在z的右子树里,且y没有左孩子。
此时又要分2种情况:
1)y是z的右孩子
此时应该用y替换z,并留下y的右子树。
2)y不是z的右孩子
此时应该先用y的右孩子替换y,然后再用y来替换z。
实现:
(1)子树移动
在上面的分析中可以看到,实现过程中需要移动子树,因此需要先实现一个函数TREE_TRANSPARENT,该函数实现了用一棵子树来替换另一棵子树并成为其父节点的孩子节点。
void TREE_TRANSPARENT(Tree* t, Treenode* x, Treenode* y) {
Treenode* p = x->p;
if(p == NULL) {
t->root = y;
}
if(p != NULL) {
if(p->left == x) {
p->left = y;
}
else {
p->right = y;
}
}
if(y != NULL) {
y->p = x->p;
}
}
(2)节点删除
void TREE_DELETE(Tree* t, Treenode* x) {
if(x->left == NULL) {
TREE_TRANSPARENT(t, x, x->right);
}
else if(x->right == NULL) {
TREE_TRANSPARENT(t, x, x->left);
}
else {
Treenode* temp;
temp = TREE_MINIMUM(x->right);
if(x->right != temp) {
TREE_TRANSPARENT(t, temp, temp->right);
temp->right = x->right;
x->right->p = temp;
}
TREE_TRANSPARENT(t, x, temp);
temp->left = x->left;
x->left->p = temp;
}
}