binary search tree,中文翻译为二叉搜索树、二叉查找树或者二叉排序树。简称为BST
一:二叉搜索树的定义
他的定义与树的定义是类似的,也是一个递归的定义:
1、要么是一棵空树
2、如果不为空,那么其左子树节点的值都小于根节点的值;右子树节点的值都大于根节点的值
3、其左右子树也是二叉搜索树
在算法导论中的定义:
下图中是BST的两个例子:
其中(b)图中的树是很不平衡的(所谓不平衡是值左右子树的高度差比较大)
BST在数据结构中占有很重要的地位,一些高级树结构都是其的变种,例如AVL树、红黑树等,因此理解BST对于后续树结构的学习有很好的作用。同时利用BST可以进行排序,称为二叉排序,也是很重要的一种思想。
二:二叉搜索树的操作定义
在BST中有下列操作:
1、在树中插入一个节点:insert(tree *root, datatype t)
2、在将树中的某个节点删除: remove(tree *root, datatype t);
3、查找树中值最大/小的节点:find_max(tree root) / find_min(tree root);
4、查找树中的某个节点:search_tree(tree root, datatype t);
当然作为二叉树,还有对BST的各种遍历,主要是中序遍历,对BST的中序遍历其实就是二叉搜索的实现。
上面的几个操作中最复杂的一个操作是删除树中的节点,后面会详细介绍。
三:BST的具体操作实现
typedef struct node_t
{
datatype data;
struct node_t *lchild, *rchild;
}node, *tree;
下面就根据BST的定义和各个操作的含义,实现BST的相应操作。
1、插入操作insert(tree *root, datatype t)
插入操作是在指定的BST中插入一个节点,该节点的数据域为t。同时插入节点后需要保证BST仍然满足二叉搜索树的定义
插入操作的大概过程如下:
将root->data 与t进行比较,如果t < root->data,那么递归将t插入到root->lchild; 否则将t插入到root->rchild中。如果root是空的话,则新建一个节点。
void insert(tree *root, node *t)
{
if(*root == NULL)
{
*root =t;
return;
}
if(t->data < temp->data) //左子树
{
insert(&((*root)->lchid), t);
}
else //插入到右子树
{
insert(&((*root)->rchild), t);
}
}
上面是插入操作的一个简单实现,在图形化中的二叉搜索树的展示如下:
这里是插入节点13
从上面实现可以知道,插入操作的时间复杂度是O(lgn),其中n是节点的个数,也就是时间复杂度是树的高度。当然在最坏情况下时间复杂度是O(n)
2、二叉搜索树的删除操作
在介绍完二叉搜索树的插入操作后,下面介绍下二叉搜索树中最负责的操作,删除节点操作remove(tree *root, datatype t).二叉搜索树的删除操作是相对要复杂的,这是因为删除操作有多种情况需要考虑,下面分别一一介绍:
假设要删除的节点是p,该节点的父节点是q,那么对p有下面的一些考察:
p和q的关系如下图所示:其中p的两个子节点用虚线表示,表示可能有也可能没有子节点;同时p也有可能是q的右孩子
针对上面的描述,可能有下面的几种情况需要考虑
a、如果p节点没有孩子节点,也就是说p节点是叶子节点,那么直接删除p对二叉搜索树是没有影响的,这样的情况下可以直接将q相应的子节点指针设置为空,然后free掉p
b、如果p的孩子节点有一个为空,例如左孩子为空或者右孩子为空。
对于这样的情况也可以分成两种讨论
if(p的左孩子为空)
{
if(p是q的左孩子)
{
q->左孩子 = p->右孩子:
}
if(p是q的右孩子)
{
q->右孩子 = p->右孩子
}
}
同理
if(p的右孩子为空)
{
if(p是q的左孩子)
{
q->左孩子 = p->左孩子
}
if(p是q的右孩子)
{
q->右孩子 = p->左孩子
}
}
上面是p的一个孩子为空
c、p的左右孩子都不为空,这是最复杂的一种情况
在这种情况下需要考虑的比较多,如果要删除p节点的话,需要找到p的后继者(此时p的后继者肯定是p的右子树中最小的一个,因为p有右孩子),然后将其放在p的位置,这样才能保证维持BST的性质。因此这种情况下就可以先找到p节点的后继者,然后将其和p进行交换,然后将交换后的节点删除。
下图是《算法导论》中删除节点的三种情况描述:
其中z节点是要删除的节点。(c)中的y节点是z的后继者
对于p的左右孩子都不为空的伪代码如下:
node *successor; //指向p的后继者
successor = find_min(p->rchild); //找到p的后继者y
p->data = successor->data; //将后继者y的值copy到p的数据域中
successor->parent->lchild = successor->rchild;//这里successor一定是其父节点的左孩子,因为如果他是父节点的右孩子的话,那么p的后继者就不是successor了,而应该是successor的parent了,因为successor的parent比successor小。这是这条语句左边的原因;对于右边来说,因为successor是p的后继者,那么successor一定是没有左孩子的,如果有的话,p的后继者就不是successor了,而是successor的左孩子了,因此successor至多有一个右孩子。这是对这条语句的解释。
操作完后就可以将successor释放掉了。
这些基本就是二叉搜索树的操作了,比较复杂的是插入和删除操作。
二叉搜索树是一种很基础的树,后续的AVL、红黑树等都是在此基础上增加了一些其他限制条件形成的。