二叉排序树查找-动态查找

二叉排序树查找-动态查找

更新:2013-11-20

//


从这篇开始记录动态查找的学习,下一篇是二叉排序平衡树查找。


名称:二叉排序树查找

关键字:动态查找,二叉排序树

二叉排序树的性质:树为空树;树不为空树,如果其左子树不为空,则左
子树上所有的结点的值均小于它的根结点,如果其右子树不为空,则右子
树上所有的结点均大于它的根结点的值;其左右子树均为二叉排序树。

对树的查找类似于二分法。从树的根结点开始,用要查找的键值和树的
结点元素键值比较,如果查找的键值小于树结点元素的键值,那么往
结点的左子树查找,否则往右子树查找,用同样的方法在子树中查找。

插入:
    在查找的过程中遇到空树时,插入一新结点替代空树就可以。

删除:
    对树进行中序遍历,就可以得到结点的从小到大的排序,
    如果被删结点只有一棵子树,直接用子树取代被删除结点即可。
    如果被删结点有两棵子树,那么可以考虑用其前驱或后继来替代该结点。

    假设有这么一棵树:

                        a
                      /   \
                    b       c
                  /   \
                d       e
              /   \   /   \
             f     g h     i
                   /  \
                  j    k
 

    现在要删除的结点是b,对该树的中序遍历顺序为fdjgbhkeiac,
    b的前驱为g,后继为h。g不可能有右子树,因为那样的话b的前驱就在g
    的右子树里了,同理h不可能有左子树。那样我们操作的思路就清晰了。

    如果用b的前驱结点g取代b时,g的父母结点d要接管g的左孩子j,
    如果用b的后继结点h取代b时,h的父母结点e就要接管h的右孩子k。

    因为树是带空的头结点的,所以为了方便编程,采用前驱取代删除结点
    的操作。
    这时又要思考一下g点的父母d的合法性了。如果d没有右孩子,如下:
                        a
                      /   \
                    b       c
                  /   \
                d       e
              /
             f
    那么d便是b的前驱,但b同时又是d的父母结点,这就要特殊处理了。
    非特殊情况下,g要接管b的两个孩子。

    为了方便讨论,下面的g或d结点被叫做替换结点,b为删除结点


附源代码:

#include <malloc.h>
//树的结点位置标识常量
#define LSZ_SEARCH_PARENTS 0
#define LSZ_SEARCH_LCHILD 1
#define LSZ_SEARCH_RCHILD 2
//删除结点的宏
#define LSZ_SEARCH_DESTROYNODE(node) (free((node)->element),free(node))
//元素的关键字类型
typedef int LSZ_SearchK; //key

//元素类型
typedef struct LSZ_SearchE{ //element
	LSZ_SearchK key; //元素的键值,用于唯一标识
}LSZ_SearchE;
//非顺序存储的查找树的结构元素类型
typedef struct LSZ_SearchBTNode{ //binary tree
	LSZ_SearchE *element;
	struct LSZ_SearchBTNode *lChild;
	struct LSZ_SearchBTNode *rChild;
}LSZ_SearchBTNode;

//动态查找时标记是否进行操作常量
#define LSZ_SEARCH_ONLY 0 //仅作查找
#define LSZ_SEARCH_INSERT 1 //没有则插入
#define LSZ_SEARCH_DELETE 2 //找到则删除

//二叉排序树查找
//
//执行成功返回值>=0:
//原树中查找成功,有插入操作或删除操作,返回1,
//原树中查找成功,无插入操作,返回0,
//原树中查找失败,无删除操作,返回0,
//出错返回-1。
//
//tree:其左孩子指向树的根结点。
//element:当往树中插入结点时,其指向空间存放传入值,
//			其它情况指向空间用于记录查找到元素的值。
//mode:仅查找,或带插入的查找,或带删除的查找。
int LSZ_search_treeBinarySorted(LSZ_SearchBTNode *tree,
								LSZ_SearchE *element,
								int mode)
{
	LSZ_SearchBTNode *header = tree, *nodePrevious = tree;
	int where; //插入根结点或者是左孩子或者是右孩子

	tree = tree->lChild; //获取根结点
	while(tree != NULL){
		if(tree->element->key == element->key){ //查找成功
			*element = *(tree->element);
			if(mode != LSZ_SEARCH_DELETE){
				return 0; //查找成功后,插入或仅查,到此返回0
			}
			break;
		}
		else{ //继续查找
			nodePrevious = tree;
			tree = element->key < tree->element->key
				? tree->lChild : tree->rChild;
		}
	}
	if(tree == NULL){
		if(mode == LSZ_SEARCH_ONLY){
			return -1; //查找失败
		}
		if(mode == LSZ_SEARCH_DELETE){
			return 0; //查找失败,不用删除
		}
	}
	//至此,下面代码要么查找成功做删除请求,要么查找失败做插入
	if(nodePrevious != header){ //插入左孩子或右孩子
		where = nodePrevious->element->key > element->key
			? LSZ_SEARCH_LCHILD : LSZ_SEARCH_RCHILD;
	}
	else{ //插入树的根结点
		where = LSZ_SEARCH_PARENTS;
	}
	if(mode == LSZ_SEARCH_INSERT){
		return LSZ_search_insertBST(nodePrevious, element, where);
	}
	return LSZ_search_deleteBST(nodePrevious, tree, where);
}
//对二叉排序树进行结点插入
//
//nodePrevious:指向插入结点的前驱结点。
//element:指向空间存放要插入结点的值,或已存在结点的传出值。
//where:插入位置,根,左孩子,右孩子。
int LSZ_search_insertBST(LSZ_SearchBTNode *nodePrevious,
							LSZ_SearchE *element,
							int where)
{
	LSZ_SearchBTNode *treeNew;

	if((treeNew = (LSZ_SearchBTNode*)malloc(
			sizeof(LSZ_SearchBTNode))) == NULL){
		return -1;
	} //申请树结点空间
	if((treeNew->element = (LSZ_SearchE*)malloc(
			sizeof(LSZ_SearchE))) == NULL){
		free(treeNew); //记得释放树结点
		return -1;
	} //申请数据空间
	*(treeNew->element) = *element; //设置结点值
	treeNew->lChild = treeNew->rChild = NULL; //叶子结点
	if(where == LSZ_SEARCH_PARENTS){ //作为根结点
		nodePrevious->lChild = nodePrevious->rChild = treeNew;
	}
	else{ //作为孩子结点
		if(where == LSZ_SEARCH_LCHILD){
			nodePrevious->lChild = treeNew;
		}
		else{
			nodePrevious->rChild = treeNew;
		}
	}
	return 1; //成功插入操作
}
//删除
//对二叉排序树进行删除
//
//nodePrevious:指向删除结点的前驱结点。
//nodeDelete:指向要删除的结点。
//where:删除的结点的位置,根,左孩子,右孩子。
int LSZ_search_deleteBST(LSZ_SearchBTNode *nodePrevious,
							LSZ_SearchBTNode *nodeDelete,
							int where)
{
	LSZ_SearchBTNode **nodeLink, *nodeFind;

	nodeLink = where == LSZ_SEARCH_LCHILD ?
		&(nodePrevious->lChild) : &(nodePrevious->lChild); //取指针变量的地址
	if(nodeDelete->lChild == NULL){ //左子树空,只需重连接其右子树
		*nodeLink = nodeDelete->rChild;
	}
	else if(nodeDelete->rChild == NULL){ //右子树空,只需重连接其左子树
		*nodeLink = nodeDelete->lChild;
	}
	else{ //左右子树均不为空
		nodeFind = nodeDelete->lChild; //被删结点的左孩子
		if(nodeFind->rChild == NULL){ //这是特殊情况,被删结点的驱刚好为其左子树的根
			*nodeLink = nodeFind; //取代结点替换被删除结点
		}
		else{
			while(nodeFind->rChild != NULL){
				if(nodeFind->rChild->rChild == NULL){
					break; //为了方便记录前驱和节省空间,需要预判一下
				}
				nodeFind = nodeFind->rChild;
			}
			*nodeLink = nodeFind->rChild; //取代结点替换被删除结点
			nodeFind->rChild = (*nodeLink)->lChild; //取代结点的左孩子被接管

			(*nodeLink)->lChild = nodeDelete->lChild; //取代结点接管删除结点的左孩子
		}
		(*nodeLink)->rChild = nodeDelete->rChild; //取代结点接管删除结点的右孩子
	}
	//释放要删除结点的空间
	LSZ_SEARCH_DESTROYNODE(nodeDelete);
	return 1;
}
//销毁二叉排序树
//
//tree:指向树的头结点的指针,树的销毁用后续遍历。
//      这里用递归实现后根遍历。
void LSZ_search_destroyBST(LSZ_SearchBTNode *tree)
{
	if(tree->lChild != NULL) //防止多次销毁错误
		LSZ_search_doDestroyBST(tree->lChild);
	tree->lChild = tree->rChild = NULL;
}
void LSZ_search_doDestroyBST(LSZ_SearchBTNode *tree)
{
	if(tree->lChild != NULL){ //先销毁左子树
		LSZ_search_doDestroyBST(tree->lChild);
	}
	if(tree->rChild != NULL){ //再销毁右子树
		LSZ_search_doDestroyBST(tree->rChild);
	}
	//最后销毁根结点
	LSZ_SEARCH_DESTROYNODE(tree);
}

本博客的版权声明点击打开链接


  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值