动态查找—二叉排序树

动态查找的大多借助于树类型的结构,这里只介绍最简单的一种——二叉排序树。

二叉排序树是这样一种树:它的要么是空的;如果它的左子树不为空,那么左子树上所有节点的值均小于根节点的值;如果右子树不为空,那么右子树上的值均大于根节点上的值,并且它的左右子树还是二叉排序树。二叉排序树有一个重要的性质:当你中序遍历该树时,得到的遍历结果是有序的。
但这一切跟动态查找查找有什么关系呢?使用线性数据结构,也能较好地完成查找工作,比如之前提过的折半查找法。但是如果我想完成的是:如果找不到这个元素,就把它插入或者如果找到这个元素,就把它删除,那么就比较麻烦了。原因出在两个方面,如果使用类似于数组的结构,那么插入、删除都会导致大量元素的移动;如果使用类似于链表的结构,由于链表不支持随机访问特性,只能从头开始一个挨着一个查找,不能发挥折半查找的效率优势。此时,如果我们使用二叉排序树就能很好解决这两个问题:首先,它可以较方便的插入和删除元素,其次沿着跟向任意路径的遍历恰好就是折半查找!每次遇到根节点,如果待查找的值小于根节点的值,则访问根节点的左子树;否则访问根节点的右子树。
下面我们看看程序的实现:
数据结构和函数的声明如下:

#include <stdio.h>
#include <malloc.h>


typedef struct BinarySortTree
{
	int key;
	//父节点
	BinarySortTree* parent;
	//左孩子
	BinarySortTree* lchild;
	//右孩子
	BinarySortTree* rchild;
}BST,*pBST;

//初始化指向一个节点的指针
BST* initNode(int k);
//给parent节点增加一个名为child的孩子节点,lr=0为左孩子,否则为右孩子
bool addBranch(pBST parent,pBST child,int lr);
//中序遍历树
void InOrderTraverse(pBST t);
//查找key是否在t中,f为待查子树的父节点,找到的位置由p带出
bool searchBST(pBST t,int key,pBST f,pBST* p);
//向t中插入key
bool insertBST(BST* t,int key);
//执行删除的实际函数
void Delete(pBST t);
//从t中删除key
bool deleteBST(pBST t,int key);
//删除整个树
void destroyBST(pBST t);

函数的定义如下:函数的定义如下:

#include "BinarySortTree.h"

pBST initNode(int k)
{
	pBST p = (BST*)malloc(sizeof(BST));
	p->key = k;
	p->lchild = p->parent = p->rchild = NULL;
	return p;
}

bool addBranch(pBST parent,pBST child,int lr)
{
	//如果节点已经满了,则不能插入
	if(parent->lchild != NULL &&parent->rchild != NULL)
		return false;
	if(0 == lr)
		parent->lchild = child;
	else
		parent->rchild = child;
	child->parent = parent;
	return true;
}

//中序遍历一棵树,遍历结果是有序的
void InOrderTraverse(pBST t)
{
	if(NULL == t)
		return;
	else
	{
		InOrderTraverse(t->lchild);
		printf("%d ",t->key);
		InOrderTraverse(t->rchild);
	}
}

//二叉排序树的查找
//输入参数为待查找的树根、key,当前节点的父节点(第一次调用时为NULL),查找结果通过p返回出来
bool searchBST(pBST t,int key,pBST father,pBST* p)
{
	//如果没有找到,p返回查找路径上指向的最后一个节点,返回false
	if(NULL == t)
	{
		*p = father;
		return false;
	}
	//若找到,则p返回指向该节点的指针,返回true
	if(t->key == key)
	{
		*p = t;
		return true;
	}
	if(t->key > key)
		return searchBST(t->lchild,key,t,p);
	else
		return searchBST(t->rchild,key,t,p);

}

//二叉排序树的插入,插入后依然是二叉排序树
//如果找到该元素,返回false,否则插入并返回true
bool insertBST(pBST t,int key)
{
	//查找结果的返回值从result带出去
	pBST result;
	//如果找到了,返回假
	if(searchBST(t,key,NULL,&result))
		return false;
	else
	{
		//新分配一个节点
		pBST pnew = initNode(key);
		//如果插入位置是头结点
		if(NULL == result)
			pnew = t;
		//如果key小于节点的key,插到节点的左孩子
		else if(result->key > key)
			addBranch(result,pnew,0);
		else
			//插到节点的右孩子
			addBranch(result,pnew,1);
		return true;
	}
}

//从二叉排序树中删除元素,删除以后还是二叉排序树
bool deleteBST(pBST t,int key)
{
	if(NULL == t)
		return false;
	//如果找到对应的节点,进行具体的删除操作
	if(t->key == key)
		Delete(t);
	else if(t->key > key)
		return deleteBST(t->lchild,key);
	else
		return deleteBST(t->rchild,key);
	return true;
}

//具体删除操作的函数
void Delete(pBST p)
{
	//如果被删除的是叶子节点
	if(NULL == p->rchild && NULL == p->lchild)
	{
		
		if(p->key < p->parent->key)
			p->parent->lchild = NULL;
		else
			p->parent->rchild = NULL;
		free(p);
		p = NULL;
		return ;
	}

	//如果被删除节点的右子树为空,只需要重接左子树
	if(NULL == p->rchild && NULL != p->lchild)
	{
		
		
		//先判断p是p->parent的左孩子还是右孩子
		//如果是左孩子
		if(p->parent->key >= p->key)
			p->parent->lchild = p->lchild;
		else
			p->parent->rchild = p->lchild;
		p->lchild->parent = p->parent;
		free(p);
		p = NULL;
		return;
	}

	//如果p的左子树为空,则只需要重接右子树
	if(NULL == p->lchild && NULL != p->rchild)
	{
		p->rchild->parent = p->parent;
		//先判断p是p->parent的左孩子还是右孩子
		//如果是左孩子
		if(p->parent->lchild->key == p->key)
			p->parent->lchild = p->rchild;
		else
			p->parent->rchild = p->rchild;
		free(p);
		p = NULL;
		return ;
	}

	//如果左右子树都不为空:
	//寻找p的左子树的右链中的最大值
	//如果左子树的右链为空,那么直接将左子树替换到最大值上
	//如果不为空将这个值作为p的值
	//将最大值删去,就是将最大值的左孩子与最大值的父亲相连

	pBST q = p;	
	pBST s = p->lchild;
	//找到p左子树的最大值
	while(s->rchild)
	{
		q = s;
		s = s->rchild;
	}
	//循环完成以后
	//如果左子树的右链为空,则不执行循环,q指向p,s为p的左孩子
	//否则s记录的最大值的位置,q记录的是s的父节点


	//左子树的右链为空
	if(q == p )
	{
		p->key = s->key;
		//等价于p->lchild = NULL;
		p->lchild = s->lchild;
		free(s);
	}
	//走到了右链的最后一个元素
	else
	{
		p->key = s->key;
		//如果右链有左孩子
		if(s->lchild != NULL)
		{
			//最大值的左孩子的父亲指向最大值的父亲
			s->lchild->parent = q;
			//最大值的父节点的右孩子指向最大值的左孩子
			q->rchild = s->lchild;
		}
		//如果右链没有左孩子
		else
			q->rchild = NULL;
		free(s);
	}
	return ;

}
//删除整个树
//通过后序遍历完成
void destroyBST(pBST t)
{
	if(NULL == t)
		return ;
	destroyBST(t->lchild);
	destroyBST(t->rchild);
	free(t);
	t = NULL;
}

 

这些函数中,searchBST中的father函数看似多余,但是由于它的存在,在执行插入元素操作时,result能够获取被插入元素的“应该的”父节点。然后方便操作。删除一个节点就相对比较麻烦了,你得考虑3种情况:
1.被删除的节点是叶子节点,那么只需要把它删除并把它的父节点的lchild或者rchild设为NULL就行了。
2.如果被删除的节点有一个孩子节点,那么只需要把这个孩子节点接到被删除节点的父节点下面就行了。
3.如果被删除节点的左右孩子都存在,就比较麻烦了,因为你得保证删除之后重新拼起来的树还是二叉排序树。具体的说,你得在这个节点的左子树中找一个最大的,将它值放到这个节点,再把那个最大值的节点删除。具体的找法,如果左子树的右链为空,那么就把左子树这个节点的值放上去;否则沿着左子树的右链寻找到头。这个元素肯定是最大的。最后删除这个最大值。这时还分两种情况:这个最大值有左孩子与最大值没有左孩子。此时的处理对应于前两种情况中的一种。

在主函数中,需要通过类似于如下的代码建立一个树:

	pBST root = initNode(45);
	pBST node1 = initNode(12);
	addBranch(root,node1,0);
	pBST node2 =  initNode(53);
	addBranch(root,node2,1);
	pBST node3 = initNode(3);
	addBranch(node1,node3,0);
	pBST node4 = initNode(37);
	addBranch(node1,node4,1);
	pBST node5 = initNode(100);
	addBranch(node2,node5,1);
	pBST node6 = initNode(24);
	addBranch(node4,node6,0);
	pBST node7 = initNode(61);
	addBranch(node5,node7,0);
	pBST node8 = initNode(90);
	addBranch(node7,node8,1);
	pBST node9 = initNode(78);
	addBranch(node8,node9,0);

然后才能进行各种操作。
最后简单的提一下这种查找的效率:咋看之下,这种查找类似于折半查找,但实际上却依赖于二叉排序树的形状:极端情况下,假如所有的节点节点依次为前一个节点的左子树,那么这个二叉排序树退化为一个链表,那么它的查找效率就会大大降低。由此可见,我们希望数据在整个树上的分布是左右平衡的,这样查找效率才会更高。于是,人们提出了一种长相更加苛刻树,平衡二叉树,(AVL树)这种树的左子树与右子树高度的绝对值之差不超过1。如果使用这种树来做二叉排序树,那么可以提高二叉排序树的效率。

    原文作者:二叉查找树
    原文地址: https://blog.csdn.net/thefutureisour/article/details/7925898
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞