现在的位置: 首页 > 综合 > 正文

二叉查找树

2013年10月26日 ⁄ 综合 ⁄ 共 6179字 ⁄ 字号 评论关闭

最近看《算法导论》,发现里面短短几页篇幅却包含着很多内容。要完全理解它可以试试不看书自己总结出来。如果可以不看书写出来才算真的"吃进去"了,否则都不算掌握了。大家可以检测下!借这篇文章来稍微总结下,便于以后查阅。

下面说说二叉查找树( Binary search tree)。

1、二叉查找树和二叉堆的区别(前者简称 bstree ,后者简称 heap)

区别一:性质不同。heap(此处默认说的是最小堆)的左右孩子节点都比父亲大,而bstree 的左孩子中的所有节点比父亲小,而右孩子中的所有节点比父亲大。

区别二:排序方式不同。heap 的根节点永远是最小节点,所以只需要依次弹出root节点,由此组成的序列即是;而 bstree 的中序遍历才得到的是排序结果。

区别三:堆有特殊性质,一般可用数组实现,index[parent] 等于 index[child]/2 。

2、二叉树操作中的难点

查找二叉树的前驱、后继,以及插入、删除操作!

------------------------------------------------------------------------------------------------------------------------------------

下面依次列出各种操作的伪码:

遍历

1、递归实现中序遍历

遍历整棵树可以调用 PRINT-BSTREE(root[T])。

PRINT-BSTREE(x)
    if x ≠ NIL  then    //只能这么表示"不等于"了,"NIL"相当于"NULL指针"
       PRINT-BSTREE(left[x])
       print key[x]
       PRINT-BSTREE(right[x])

2、非递归中序遍历(需要借助Stack来实现)

PRINT-BSTREE(x)
    S <---- {NIL}   //无法输入书中那种集合空,暂用"{NIL}"来替代   
    while TRUE do        
        if left[x] ≠ NIL then
           x <--- left[x]
           PUSH(S, x)  //将该节点压入栈
        else 
           print key[x]   // 如果没有左孩子了,则输出该节点
           if right[x] ≠ NIL then
              x <---- right[x]
           else
              if S == {NIL} then
                 break   // 循环退出条件:到达叶子节点,且栈为空,即已经输出最后那个节点
             else  x <----  POP(S) //到达叶子节点了,该节点没有孩子

查找

1、递归查找某个元素

如果要从根节点开始查找,则调用 FIND-RECURSION(root[T], k)。

FIND-RECURSION(x,k)
    if x ≠ NIL and key[x] ≠ k then
       if k < key[x] then
          return FIND-RECURSION(left[x], k)
       else
          return FIND-RECURSION(right[x], k)

    return x //返回指向这个节点的指针,或者没找到,返回空

2、非递归查找某个元素

FIND(x,k)
    while x ≠ NIL and key[x] ≠ k do
       if k < key[x] then
          x <--- left[x]
       else
         x <--- right[x]
     return x

3、查找树的最小值(在查找前驱和后继的时候有用)

FIND-MIN(x)
    y <--- NIL
    while left[x] ≠ NIL do
        y <---- x
        x <---- left[x]  
    return y

4、查找树的最大值

FIND-MAX(x)
    y <--- NIL
    while right[x] ≠ NIL do
        y <---- x
        x <---- right[x]  
    return y

5、查找某个元素的前驱

如果知道元素的值,比如 k,可以先用 p = FIND(x,k) 得到指向该节点的指针,然后再调用下面的函数 FIND-PREV(p)

FIND-PREV(x)   //暂且用prev这个词来代表 "前驱"
    if left[x]  ≠  NIL then
       return FIND-MAX(left[x]) //如果该节点有左孩子,则左子树中最大的节点就是它的前驱
    else 
       y <--- p[x]   //p[x]表示x节点的父节点
       while y ≠ NIL and right[y] ≠ x  do
           x <--- y
           y <--- p[y]
      return y  //如果x没有左孩子,则它的前驱肯定是它的某个父亲,满足这个条件:某个父亲P 的右子树包含了x(或者说x所在的子树) ,则P 就是x的前驱。

6、查找某个元素的后继(和前驱类似)

FIND-NEXT(x)   //暂且用next这个词来代表 "后继"
    if right[x] ≠  NIL then
       return FIND-MIN(right[x]) //如果该节点有右孩子,则右子树中最小的节点就是它的后继
    else 
       y <--- p[x]   //p[x]表示x节点的父节点
       while y ≠ NIL and left[y] ≠ x  do
           x <--- y
           y <--- p[y]
      return y  //如果x没有右孩子,则它的前驱肯定是它的某个父亲,满足这个条件:某个父亲P 的左子树包含了x(或者说x所在的子树) ,则P 就是x的后继。

插入

向二叉树中插入节点,先需要生成节点m,然后调用 INSERT(T, m)。

INSERT(T,m)  
    x <---- root[T] 
    y <--- NIL    //用来记录要插入节点位置的父亲
    while x ≠ NIL do //寻找要插入的位置
        y <--- x
        if key[m] < key[x]  then
           x <--- left[x]
        else
           x <--- right[x]
    p[m] <--- y   //对该节点的parent指针赋值
    if y == NIL  then
       root[T] <--- m
    else if key[m] < key[y] then
              left[y] <--- m
    else    right[y] <--- m

删除

删除操作是最复杂的,该节点分三种情况:没有孩子,有一个孩子,有两个孩子。对于两个孩子的情况,我们采用"逻辑删除",也就是删除掉它的"后继"节点,把这个节点的值赋给要删除的节点!

先要通过 m = FIND(root[T],k) 来找到这个要删除节点的指针,而后调用 DELETE(T, m)来删除

DELETE(T,m)
     x <--- NIL 
     y <--- NIL
     if left[m]==NIL or right[m]==NIL then
        x <--- m   //如果至多只有一个孩子,则x指向该节点本身
     else
        x <--- FIND-NEXT(m)  // 如果有两个孩子,则x指向m的后继节点,由于后继节点没有左孩子,所以可以将删除后继的操作和上面结合起来
     
     if left[x] ≠ NIL then    //x指向的是y节点的孩子节点(y最多有一个孩子),如果没有则y为NULL
        y <--- left[x]
     else
        y <--- right[x]

     if y ≠  NIL then  //修改孩子节点的父节点
        p[y] <--- p[x]
   
     if p[y] == NIL then
        root[T] <---- y         //删除的刚好是根节点
     else  if left[p[x]] == x then
        left[p[x]] <--- y
     else 
        right[p[x]] <--- y

     if x ≠ m then
        key[m] <----- key[x]  //把x节点的内容copy给m节点,以实现逻辑上删除m节点

     delete x     

C语言的源代码如下,仅做参考:

#include<stdio.h>
#include<stdlib.h>
#include<assert.h>

//二叉树节点
typedef struct node{
	int data;
	struct node *left,*right,*parent;
}bsnode;

//遍历二叉树(中序)
void print_bstree(bsnode *tree){
	if(tree!=NULL){
		print_bstree(tree->left);//递归
		printf("%d,",tree->data);
		print_bstree(tree->right);
	}
}

//查找元素(非递归)
bsnode * find(bsnode *tree,int k){
	while(tree!=NULL && tree->data !=k){
		if(k < tree->data){
			tree = tree->left;
		}else{
			tree = tree->right;
		}
	}
	return tree;//返回指向data域等于k的节点的指针!
}

//查找元素(递归)
bsnode * re_find(bsnode *tree,int k){
	if(tree==NULL ||  tree->data ==k){
		return tree;	
	}else if(k < tree->data){
		return re_find(tree->left,k);
	}else{
		return re_find(tree->right,k);
	}
}

//求最小值(给定一个二叉查找树的根snode,求这颗树的最小节点)
bsnode * find_min(bsnode *snode){
	bsnode *p=NULL;
	while(snode!=NULL){
		p=snode;
		snode=snode->left;
	}
	return p;//树的最左子树叶节点
}

//求最大值
bsnode * find_max(bsnode *snode){
	bsnode *p=NULL;
	while(snode!=NULL){
		p=snode;
		snode=snode->right;
	}
	return p;
}

//查找前驱
bsnode * find_prev(bsnode *m){
	assert(m!=NULL);
	if(m->left != NULL){
		return find_max(m->left);	
	}else{
		//寻找m的祖先中满足这样关系的点:righ[p[m]] == m
		bsnode *p = m->parent;
		bsnode *child = m;
		while(p!=NULL && p->right != child){
			child=p;
			p=child->parent;
		}
		return p;
	}
}

//查找后继
bsnode * find_next(bsnode *m){
	assert(m==NULL);
	if(m->right !=NULL){
		return find_min(m->right);
	}else{
		bsnode *p=m->parent;
		bsnode *child=m;
		while(p!=NULL && p->left != child){
			child=p;
			p=p->parent;
		}
		return p;
	}
}

//插入元素(递归)
bsnode *re_insert(bsnode *tree,bsnode *newer){
	if(tree==NULL){
		tree=newer;
	}else{
		if(newer->data < tree->data){
			tree->left=re_insert(tree->left,newer);
		}else{
			tree->right=re_insert(tree->right,newer);
		}
	}
	return tree;
}

//插入元素(非递归)
bsnode *insert(bsnode *tree,bsnode *newer){
	bsnode *root=tree;
	bsnode *p= NULL;
	while(root!=NULL){
		p=root;
		if(newer->data < root->data){//往左子树
			root=root->left;
		}else{
			root=root->right;
		}
	}//p指针就是要插入节点的父亲

	newer->parent = p;//修改parent指针,让其指向父亲

	if(p==NULL){//只有一个节点
		tree = newer;
	}else{
		if(newer->data < p->data){
			p->left=newer;
		}else{
			p->right=newer;
		}
	}

	return tree;
}

//删除元素
bsnode * delete(bsnode *tree,int k){
	bsnode *delnode = find(tree,k);
	if(delnode ==NULL){
		printf("要删除的节点不存在!请检查\n");
	}else{
		bsnode *xnode = NULL;
		bsnode *ynode = NULL; 
		if(delnode->left == NULL || delnode->right == NULL){
			xnode = delnode;//delnode有一个孩子或者没有,则xnode表示它本身
		}else{
			xnode = find_next(delnode);//delnode有两个孩子,xnode则表示它的后继节点
		}

		//得到xnode的孩子节点
		if(xnode->left != NULL){
			ynode = xnode->left;
		}else{
			ynode = xnode->right;
		}

		if(ynode != NULL){
			ynode->parent = xnode->parent;
		}

		if(xnode->parent == NULL){
			tree = ynode;	
		}else{
			if(xnode->parent->left == xnode){
				xnode->parent->left = ynode;
			}else{
				xnode->parent->right = ynode;
			}
		}

		if(xnode != delnode){//两个孩子的情况,得将后继节点的值赋给delnode,以实现逻辑删除
			delnode->data = xnode->data;
		}

		//删除掉节点
		free(xnode);
	}

	return tree;
}

int main(){
	bsnode *tree=NULL;
	int data[]={10,2,11,3,8,6,1,7};
	int i;
	for(i=0;i<sizeof(data)/sizeof(int);i++){
		bsnode *newer = (bsnode *)malloc(sizeof(bsnode));
		newer->data=data[i];
		newer->left=newer->right=newer->parent=NULL;
		tree = insert(tree,newer);//插入
	}
	print_bstree(tree);//中序遍历
	printf("\n");
	
	tree = delete(tree,2);//删除节点
	print_bstree(tree);
	printf("\n");
	return 0;
}

如果转载,请标明出处 blog.csdn.net/whuslei

(全文完)

抱歉!评论已关闭.