二叉搜索树Binary Search Tree (BST)
今天数据结构学习了BST。BST是基于二叉树创建的一种数据结构,也叫二叉查找树。其他很多的数据结构,如AVL树,Splay树等,都是基于BST的升级版本。对BST拥有全面的理解,是学好更高级别的查找树的基础。
- 功能:用来查找特定的元素是否存在。 操作:对于特定的元素,可以有查询,插入,删除等操作。对于整棵BST,可以查找最大、最小值。
- 实现基础:最简单的BST建立在集合上,也就是说,没有重复元素。这也是集合的实现方式之一。而建立在序列上的BST则可以含有重复元素。元素之间必须可以比较大小,或者由使用者自行定义比较大小的方式。BST的节点含有键值,存储的就是对象。比如1-100的正整数集合,数之间可以比较大小,元素有限,键值存储的就是1-100的正整数。
- 实现原理:利用递归定义的方法。BST中的某个节点node,其键值为q。那么,键值小于q的节点被存储在node的左子树中,大于q的节点被存储在node的右子树中。
查找
BST find( BST T, element_type value ){ //返回查找到的节点。如果没有就返回NULL
if( T == NULL ){
printf("We can't find it.\n") ;
return NULL ;
}
if( T->key == value ){
return T ;
}
else if( T->key < value ){
return find( T->right, value ) ;
}
else return find( T->left, value ) ;
}
了解BST的实现原理之后,想要查找一个元素m是否存在,就比较容易了:从根节点开始,比较键值q和m。如果q==m,那么正好就找到了。如果m更小,就去左子树里面找;反之就去右子树里面找。
插入
BST Insert( BST T, element_type value ){
//要返回变化后的树,这样才能保证修改的信息能传递上去
if( T == NULL ){
T = (BST) malloc ( sizeof(BST) ) ;
T->key = value ;
T->left = T->right = NULL ;
}
if( T->key < value ) {
T->right = Insert( T->right, value ) ;
}
if( T->key > value ) {
T->left = Insert( T->left, value ) ;
}
return T ; //返回变化后的树
}
一样的利用大小比较的查找原理。最终会找到元素应该在的位置。这时候要申请一块内存,存储这样一个新的元素。记住:无论是创建了新的元素,还是修改了左右子树,最后都一定要返回变化过的树!
删除
删除的操作比较复杂。不过我们最先要这个元素做的依然是找到要删除的元素。在找到元素时候,问题就来了:怎么删除呢?
- 如果要删除的节点是叶子结点,那么,恭喜你!直接删掉就可以了,不需要任何其他的操作。
- 如果这个节点有一颗子树(无论左右),那么把这棵子树链接到这个节点的父节点上即可。(想想为什么,可以分情况讨论一下)
- 节点有两颗子树,这种情况是比较麻烦的。我们可以找比要删除的元素——设为p——大的最小的元素——设为q。也就是在有序的升序序列中,找p的下一个元素q来代替p的位置。那么在BST中,q在哪里呢?回忆BST的构建原理,我们可以知道,q在p的右子树中,并且是右子树中的最小元素。这样,类我们把q拿过来,放到p的位置上。对于原来p的左子树来说,q比p还要大,自然没有问题;对于p的父节点来说,p和它的子树都是同一种类型,也没有问题;只剩下右子树了。q被拿走了,右子树怎么办?这下你会发现,只要先从右子树里删掉q不就行了吗!这样,第3类删除节点就变成了一个递归型的操作,最后总会归结到前两类中。
不过,这个递归并不会持续很长,因为q一定是没有左子树的(所以是个假递归 )。思考:如果q有左子树,那么就是说有着比q还要小的元素,并且这些元素也在p的右子树中。这样q又怎么能成为p的右子树中最小的元素呢?所以,找到节点之后,最多递归一次,我们就可以完成删除了。
要用到的找最小元素的代码
BST find_min( BST T ){ //循环版本好像也挺好理解的...还比较安全
if( is_empty( T ) ) {
printf("Wrong! The tree is empty.\n") ;
return NULL ;
}
while( T->left != NULL ) T = T->left ;
return T ;
}
一直往左子树上找就可以了。
真正的删除节点的代码
BST Delete( BST T, element_type value ){
//1.先找元素
//2.找到后删除,分情况讨论
if( T == NULL ){
printf("Wrong! The value is not included.\n") ;
return NULL ;
}
if( value < T->key ) {
T->left = Delete( T->left, value ) ;
}
else if( value > T->key ) {
T->right = Delete( T->right, value ) ;
}
else {
BST child = NULL, tmp_cell ;
if( T->left && T->right ){ //这个点被保留了, 但是更改了键值和子树信息,返回T
tmp_cell = find_min( T->right ) ;
T->key = tmp_cell->key ;
T->right = Delete( T->right, tmp_cell->key ) ;
}
else { //T这个点被扔掉了,返回了T的子树
if( T->left ) child = T->left ;
if( T->right ) child = T->right ;
free(T) ;
return child ;
}
}
return T ; //!!!!
}
小问题:代码中的free(T)调试的时候会造成错误,还不知道为什么…
附上完整的程序:
//试图写一个没有重复元素的BST(Binary Search Tree)——二叉搜索树
//左子树小,右子树大
//可以任意规定元素类型,但是元素之间必须可以比较(或者自行定义)
#include<stdio.h>
#include<stdlib.h>
typedef int element_type ;
typedef struct tree_node *tree_ptr ;
struct tree_node{
element_type key ;
tree_ptr left ;
tree_ptr right ;
};
typedef tree_ptr BST ;
int is_empty( BST T ){
return ( T == NULL ) ;
}
BST Build(){ //创建BST,其实就是空树
return NULL ;
}
BST find( BST T, element_type value ){ //返回查找到的节点。如果没有就返回NULL
if( T == NULL ){
printf("We can't find it.\n") ;
return NULL ;
}
if( T->key == value ){
return T ;
}
else if( T->key < value ){
return find( T->right, value ) ;
}
else return find( T->left, value ) ;
}
BST find_max( BST T ){ //尾递归版本,容易爆栈,不过简单好理解
if( is_empty( T ) ) {
printf("Wrong! The tree is empty.\n") ;
return NULL ;
}
if( T->right == NULL ) {
return T ;
}
return find_max( T->right ) ;
}
BST find_min( BST T ){ //循环版本好像也挺好理解的...还比较安全
if( is_empty( T ) ) {
printf("Wrong! The tree is empty.\n") ;
return NULL ;
}
while( T->left != NULL ) T = T->left ;
return T ;
}
BST Insert( BST T, element_type value ){
if( T == NULL ){
T = (BST) malloc ( sizeof(BST) ) ;
T->key = value ;
T->left = T->right = NULL ;
}
if( T->key < value ) {
T->right = Insert( T->right, value ) ;
}
if( T->key > value ) {
T->left = Insert( T->left, value ) ;
}
return T ; //返回变化后的树
}
BST Delete( BST T, element_type value ){
//1.先找元素
//2.找到后删除,分情况讨论
if( T == NULL ){
printf("Wrong! The value is not included.\n") ;
return NULL ;
}
if( value < T->key ) {
T->left = Delete( T->left, value ) ;
}
else if( value > T->key ) {
T->right = Delete( T->right, value ) ;
}
else {
BST child = NULL, tmp_cell ;
if( T->left && T->right ){ //这个点被保留了, 但是更改了键值和子树信息,返回T
tmp_cell = find_min( T->right ) ;
T->key = tmp_cell->key ;
T->right = Delete( T->right, tmp_cell->key ) ;
}
else { //T这个点被扔掉了,返回了T的子树
if( T->left ) child = T->left ;
if( T->right ) child = T->right ;
free(T) ;
return child ;
}
}
return T ; //!!!!
}
void infix_traversal( BST T ){ //中序遍历BST就能从小到大输出序列
if( T == NULL ) return ;
infix_traversal( T->left ) ;
printf("%d ", T->key ) ;
infix_traversal( T->right ) ;
}
int main(){
int a[]={1,2,3,5,6,8,9} ;
int n=7, i=0 ;
BST T = Build() ;
for(i=0 ; i<n ; i++ ){
T = Insert( T, a[i] ) ;
}
T = Delete( T, 5 ) ;
infix_traversal(T) ;
return 0 ;
}