算法与数据结构系列之[二分搜索树-上]
前几篇介绍了二叉树以及二叉树的遍历,接下来三篇介绍下二分搜索树。
1.什么是二分搜索树
二分搜索树(Binary Search Tree)是一种特殊的二叉树,也叫二叉排序树或二叉查找树,简称:BST。顾名思义,二分搜索树可以通过二分法快速实现元素的查找的,而且,二分搜索树插入和删除元素的效率也很高。
二分搜索树或者树一棵空树,获取满足下面性质:
- 若左子树不为空,则左子树上所有节点的值均小于其根节点的值
- 若有子树不为空,则右子树上所有节点的值均大于其根节点的值
- 左 右子树也分别为二分搜索树
如下图是一棵二分搜索树:

2.添加元素
二分搜索树添加元素时可以采用递归算法,也可以用非递归的方法,我参考的大多数书籍都是非低递归的方法,这里我习惯于采用递归方法。添加一个元素时,先看树是否为空,如果为空,就将要添加的元素添加到根节点的位置。如果树不为空,先和根节点进行比较,如果小于根节点的左子节点比较,若是根节点的左子节点为空,就插入到根节点左子节点的位置,不为空就接着向下递归。如果大于根节点就在根节点的右子树递归,直到找到空的位置插入。
比如要插入的元素为28,28先和根节点比较,小于根节点,所以要的根节点的左子树上递归,又28小于58,再和58的左子节点46比较,小于46,再和46的左子节点30比较,小于30,和30的左子节点比较,又30的左子节点为空,所以28添加到30的左子节点的位置。

代码实现:
BinarySearchTree add(BinarySearchTree bst,int e){
if(!bst){
size++;
bst = malloc(sizeof(BinarySearchNode));
bst->e = e;
bst->left = bst->right = NULL;
return bst;
}
if(e < bst->e)
bst->left = add(bst->left,e);
else if (e > bst->e)
bst->right = add(bst->right,e);
return bst;
}
void Insert(int e){
root = add(root,e);
}
3.二分搜索树的查找操作
首先,我们看如何在二叉查找树中查找一个节点。我们先取根节点,如果它等于我们要查找的数据,那就返回。如果要查找的数据比根节点的值小,那就在左子树中递归查找;如果要查找的数据比根节点的值大,那就在右子树中递归查找。
代码实现:
int FindBST(BinarySearchTree bst,int e){
if(bst == NULL)
return 0;
if(e == bst->e)
return 1;
else if(e < bst->e)
return FindBST(bst->left,e);
else
return FindBST(bst->right,e);
}
int Contains(int e){
return FindBST(root,e);
}
4.二分搜索树的删除操作
二分搜索树的添加和查找操作都相对简单,但删除操作就相对复杂了。针对要删除节点的子节点的个数,我们需要考虑如下三种情况:(当然在代码实现层面,一二两种情况可以合并,没有子节点可以理解为左右子节点为空)
第一种情况是,如果要删除节点没有子节点,只需要将该节点直接删除即可,也就是直接将父节点中指向该节点的指针删除掉即可。比如下图中删除节点66.
第二种情况是,要删除的节点只有一个子节点,我们只需要更新父节点中指向删除节点的指针,让它指向要删除节点的子节点即可。比如删除节点30.
第三种情况是,如果要删除的节点左右子节点都有,这时就比较复杂,我们需要找到要删除的这个节点的后继,即这个节点的右子树中的最小节点,把它替换到要删除的节点上。比如删除下图中的88

接下来我们分成三步来实现二分搜索树的删除操作
第一步:删除二分搜索树的最小元素
BinarySearchTree RemoveMinBST(BinarySearchTree bst){
if(bst->left == NULL){ //只有右子节点,没有左子节点的情况
BinarySearchTree rightNode = bst->right;
bst->right = NULL;
size--;
return rightNode;
}
bst->left = RemoveMinBST(bst->left);
return bst;
}
int RemoveMin(){
int min = Minimum();//Minimum方法返回二分搜索树的最小元素
root = RemoveMinBST(root);
return min;
}
第二步:删除二分搜索树的最大元素
BinarySearchTree RemoveMaxBST(BinarySearchTree bst){
if(bst->right == NULL){ //只有左子节点没有右子节点的情况
BinarySearchTree leftNode = bst->left;
bst->left = NULL;
size--;
return leftNode;
}
bst->right = RemoveMaxBST(bst->right);
return bst;
}
int RemoveMaxmum(){
int max = Maxmum();//Maxmum方法返回二分搜索树的最大元素
root = RemoveMaxBST(root);
return max;
}
第三步:删除二分搜索树中的任意元素
BinarySearchTree RemoveBST(BinarySearchTree bst,int e){
if(bst == NULL)
return NULL;
if(e < bst->e){
bst->left = RemoveBST(bst->left,e);
return bst;
}
else if(e > bst->e){
bst->right = RemoveBST(bst->right,e);
return bst;
}
else{ //e == node.e
//待删除节点的左子树为空,右子树不为空
if(bst->left == NULL){ //只有右子节点,没有左子节点的情况
BinarySearchTree rightNode = bst->right;
bst->right = NULL;
size--;
return rightNode;
}
//待删除节点的右子树为空,左子树不为空
if(bst->right == NULL){ //只有左子节点没有右子节点的情况
BinarySearchTree leftNode = bst->left;
bst->left = NULL;
size--;
return leftNode;
}
//待删除节点的左右子树都不为空
BinarySearchTree successor = MinimumBST(bst->right); //successor 待删除节点的后继,也就是待删除节点右子树的最小节点
successor->right = RemoveMinBST(bst->right);//不用再size--,因为RemoveMinBST中已经执行了size
successor->left = bst->left;
bst->right = bst->left = NULL;
return successor;
}
}
void Remove(int e){
root = RemoveBST(root,e);
}
对待删除节点都不为空的图解:

5.二分搜索树的非递归前序遍历
在介绍二叉树的时候详细介绍了前、中、后序遍历的递归实现,代码相对简单,但是非递归实现相对复杂一些,尤其是中、后序遍历的非递归实现。这了我们只介绍一下前序遍历的非递归实现。
前序遍历的非递归实现依然是先输出当前节点,在遍历当前节点的左子树,在遍历当前节点的右子树,只是需要将要操作的元素依次放入栈,输出一个元素就相当于出栈一个元素。
如图,先将根节点28入栈,再输出28,然后依次将28的右子节点和左子节点入栈,接着16出栈,再依次将16的右孩子和左孩子入栈,接着13,22依次出栈,再将30的右孩子和左孩子依次入栈,接着29,42依次出栈,此时栈里只剩下30这个节点,将30出栈即可,之后栈为空,遍历结束。

代码实现(java代码):
public void preOrderNR(){
Stack<Node> stack = new ArrayStack<>();
stack.push(root);
while (!stack.isEmpty()){
Node cur = stack.pop();
System.out.print(cur.e);
if(cur.right != null)
stack.push(cur.right);
if(cur.left != null)
stack.push(cur.left);
}
}
6.二分搜索树的层序遍历
二分搜索树的层序遍历,也就是一层一层的遍历,是按广度优先策略遍历,而二分搜索树的前、中、后序遍历都是按深度优先策略遍历。二分搜索树的层序遍历可以用队列实现,每次遍历对应队列的入队和出队,与前序遍历非递归实现不同的是,层序遍历每次子节点入队时,先入队左子节点,再入队右子节点。

代码实现(java代码):
public void levelOrder(){
Queue<Node> queue = new LinkedList<>();
queue.add(root);
while (!queue.isEmpty()){
Node cur = queue.remove();
System.out.print(cur.e);
if(cur.left != null)
queue.add(cur.left);
if(cur.right != null)
queue.add(cur.right);
}
}
7.总结
这篇介绍了二分搜索树的概念,以及添加,查找,删除,遍历等操作。其中删除操作比较复发一些。接下来的两篇将二分搜索树的完整代码贴出来。
