C语言-树表的查找(二叉排序树)

  1. 二叉排序树的搜索(SearchBST)
  2. 二叉排序树的插入(InsertBST)
  3. 二叉排序树的删除(Delete)
  4. 中序遍历(inorder)
  5. 先序遍历(preorder)

什么是二叉排序树

1.二叉排序树要么是空二叉树,要么具有如下特点:

  • 如果结点有左孩子,那么左孩子的值小于根结点的值
  • 如果结点有右孩子,那么右孩子的值大于根结点的值
  • 二叉排序树中的任意一个结点均满上述两点
  1. 对二叉排序树执行中序遍历得到的是一组由小到大排好的元素序列

二叉排序树有何特点

  1. 二叉排序树的结构类似于二分查找的判定树。比根结点大,往右边找;比根节点小,往左边找。
  2. 二叉排序树与二分查找的判定树本质上不同之处在于,二叉排序树是动态生成的不是静态的,每次插入都会根据当前情况,确定插入位置。
  3. 一个无序序列可以通过构建一棵二叉排序树,从而变成一个有序序列:对二叉排序树进行中序遍历(inorder)会得到一组由小到大已排好序的数据元素
  4. 一组数据元素可以构造出多种二叉排序树,不同形态的二叉排序树查找效率也不相同,形态越接近于平衡二叉树(AVL树)的查找效率越高

二叉排序树的构造

  1. 查找:因为二叉排序树可以看成是一个有序表,所以在二叉排序树上进行查找和折半查找类似,也是一个逐步缩小查找范围的过程。 此处我们采用递归查找。
  • 无论是否查找成功,我们都将查找的最终地址返回,查找成功返回查到的元素的地址;查找失败则返回空地址NULL
  • 函数参数中的Bstree *f,Bstree **p指的其实是同一个东西——最后查找元素的前驱结点,整个递归的过程,用f去进入层层递归当中,找到最后一个查找节点的前驱节点(双亲结点),最后通过二级指针*p带出函数
//查找成功返回地址,查找失败返回NULL;
//T指向当前顶点,f指向T的双亲(前驱节点),最后用*p将f带出函数的层层递归 
Bstree *SearchBST(Bstree *T,dataType key,Bstree *f,Bstree **p)
{
	if((!T)||T->data==key)//查找完毕,查找成功返回坐标,失败返回NULL 
	{	
	    *p=f;//最后将前驱存入*p带出SearchBST函数 
		return T;
	}
	else if(T->data>key)//继续查找左子树 
	{
		return SearchBST(T->lchild,key,T,p);
	}
	else
	{
		return SearchBST(T->rchild,key,T,p);//继续查找右子树 
	}	
} 
  1. 插入:此处值得一提的地方在if(SearchBST((*T),elem,NULL,&p)==NULL)//在查找失败的情况下插入
    此处调用SearchBST函数有两个作用:
    ① 判断当前插入的元素elem是否已经存在
    ② 若插入元素elem在树中不存在,此时我们的指针p在搜索的过程中保留了查找的最后位置的前驱节点,那么此时,我们要插入到元素elem肯定是p的孩子,那么我们只需要判断elemp->data的大小关系,判断elemp的左孩子还是右孩子即可.
void InsertBST(Bstree **T,dataType elem)
{
	Bstree *p=NULL;//p用于存储SearchBST最终搜索位置的前驱结点(无论搜索成功或者失败,p都将存入最后搜索位置的前一个结点) 
	Bstree *s;
	
    if(SearchBST((*T),elem,NULL,&p)==NULL)//在查找失败的情况下插入 
    {
    	s=(Bstree *)malloc(sizeof(Bstree));
    	s->data=elem;
    	s->lchild=NULL;
    	s->rchild=NULL;
    	
    	if(p==NULL)//p为空说明该二叉排序树为空树,此时插入的结点为整棵树的根结点
    	{
    		*T=s;
		}
		else if(p->data>elem)//p->data>elem;说明elem对应的结点应当成为前驱p的左孩子 
		{
			p->lchild=s;
		}
		else if(p->data<elem)//p->data<elem;说明elem对应的结点应当成为前驱p的右孩子 
		{
			p->rchild=s;
		}
	} 
	return;
}
  1. 删除:二叉排序的删除情况最为复杂:
  • 当要删除的结点DelNode没有孩子时,可直接删除:
    ① DelNode是根节点,且只有一个结点
    ② DelNode是前驱节点p的左孩子
    ③ DelNode是前驱节点p的右孩子
1.当要删除的结点DelNode没有孩子时,可直接删除 
		
		if(!(DelNode->lchild)&&!(DelNode->rchild)) 
		{   
		    // [情况1] DelNode是根节点,且只有一个结点
			if(!p)
			{
				(*T)=NULL; 
			}

			// [情况2] DelNode是前驱节点p的左孩子 
			else if(DelNode->data<p->data)
			{
				p->lchild=NULL; 
			} 
			
			// [情况3] DelNode是前驱节点p的右孩子 
			else if(DelNode->data>p->data)
			{
				p->rchild=NULL;
			}
			free(DelNode);
            return;
		}
  • 当要删除节点DelNode只有一个孩子时(左孩子或右孩子),其孩子直接继承DelNode的位置
    ④ DelNode是根节点,有右孩子
    ⑤ DelNode是根节点,有左孩子
    ⑥ DelNode是前驱节点p的左孩子,有左孩子
    ⑦ DelNode是前驱节点p的右孩子,有左孩子
    ⑧ DelNode是前驱节点p的左孩子,有右孩子
    ⑨ DelNode是前驱节点p的右孩子,有右孩子
2.当要删除节点DelNode只有一个孩子时(左孩子或右孩子),其孩子,直接继承DelNode的位置
		
		if((!DelNode->lchild)||(!DelNode->rchild))
		{
			if(!p)//只有一个结点,且是根节点的情况 
			{
				// [情况4] DelNode是根节点,有右孩子 
				if(!DelNode->lchild)
				{
					(*T)=DelNode->rchild;
					free(DelNode);
					return;
				}
				
				// [情况5] DelNode是根节点,有左孩子
				if(!DelNode->rchild)
				{
					(*T)=DelNode->lchild;
					free(DelNode);
					return;
				}
			}
			
			
			if(!DelNode->rchild)//只有左子树时
		    {
		    	// [情况6] DelNode是前驱节点p的左孩子,有左孩子
                if(DelNode->data<p->data) 
                {
                	p->lchild=DelNode->lchild; 
			    }
			    
			    // [情况7] DelNode是前驱节点p的右孩子,有左孩子
		    	else
                {
                	p->rchild=DelNode->lchild; 
			    }
			    free(DelNode);
			    return;
		    } 
			
			if(!DelNode->lchild)//只有右子树时
            {
            	// [情况8] DelNode是前驱节点p的左孩子,有右孩子
        	    if(DelNode->data<p->data)
                {
            	    p->lchild=DelNode->rchild; 
			    }
			    
			    // [情况9] DelNode是前驱节点p的右孩子,有右孩子
			    else
                {
                	p->rchild=DelNode->rchild; 
		    	}
		    	free(DelNode);
			    return;
		    }
		}
  • 当要删除的结点DelNode左右子树都存在时:
3.当要删除的结点DelNode左右子树都存在时

		if(DelNode->lchild&&DelNode->rchild)
		{
			
			Bstree *s;
			Bstree *q;//q存储s的前驱节点 
			
			q=DelNode; 
			s=DelNode->lchild;

			while(s->rchild)
			{
				q=s; 
				s=s->rchild;//找到的s一定是叶子节点,即无左右孩子 
			}	
			DelNode->data=s->data;
			
			if(q==DelNode)//说明上述while循环一次都未执行 
			{
				//q->lchild=NULL; 考虑过于片面,未考虑到s可能存在左孩子 
				q->lchild=s->lchild; 
			}
			else //说明while循环找到了最右边的s 
			{
				//q->rchild=NULL;考虑过于片面,未考虑到s可能存在左孩子 
				q->rchild=s->lchild; 
			}
			free(s);
			return;
		}

完整源代码:

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

typedef char dataType;

typedef struct BstNode
{
	dataType data;
	struct BstNode *lchild;
	struct BstNode *rchild;
}Bstree;

//查找成功返回地址,查找失败返回NULL;
//T指向当前顶点,f指向T的双亲(前驱节点),最后用*p将f带出函数的层层递归 
Bstree *SearchBST(Bstree *T,dataType key,Bstree *f,Bstree **p)
{
	if((!T)||T->data==key)//查找完毕,查找成功返回坐标,失败返回NULL 
	{	
	    *p=f;//最后将前驱存入*p带出SearchBST函数 
		return T;
	}
	else if(T->data>key)//继续查找左子树 
	{
		return SearchBST(T->lchild,key,T,p);
	}
	else
	{
		return SearchBST(T->rchild,key,T,p);//继续查找右子树 
	}	
} 

void InsertBST(Bstree **T,dataType elem)
{
	Bstree *p=NULL;//p用于存储SearchBST最终搜索位置的前驱结点(无论搜索成功或者失败,p都将存入最后搜索位置的前一个结点) 
	Bstree *s;
	
    if(SearchBST((*T),elem,NULL,&p)==NULL)//在查找失败的情况下插入 
    {
    	s=(Bstree *)malloc(sizeof(Bstree));
    	s->data=elem;
    	s->lchild=NULL;
    	s->rchild=NULL;
    	
    	if(p==NULL)//p为空说明该二叉排序树为空树,此时插入的结点为整棵树的根结点
    	{
    		*T=s;
		}
		else if(p->data>elem)//p->data>elem;说明elem对应的结点应当成为前驱p的左孩子 
		{
			p->lchild=s;
		}
		else if(p->data<elem)//p->data<elem;说明elem对应的结点应当成为前驱p的右孩子 
		{
			p->rchild=s;
		}
	} 
	return;
}

void Delete(Bstree **T,dataType elem)
{
	//先找到要删除的结点位置 
	Bstree *p=NULL;//p用于存储SearchBST最终搜索位置的前驱结点
	Bstree *DelNode;
	DelNode=SearchBST((*T),elem,NULL,&p);
	
	if(DelNode)
	{
		//1.当要删除的结点DelNode没有孩子时,可直接删除 
		if(!(DelNode->lchild)&&!(DelNode->rchild)) 
		{   
		    // [情况1] DelNode是根节点,且只有一个结点
			if(!p)
			{
				(*T)=NULL; 
			}

			// [情况2] DelNode是前驱节点p的左孩子 
			else if(DelNode->data<p->data)
			{
				p->lchild=NULL; 
			} 
			
			// [情况3] DelNode是前驱节点p的右孩子 
			else if(DelNode->data>p->data)
			{
				p->rchild=NULL;
			}
			
			free(DelNode);
            return;
		}
		
		//2.当要删除节点DelNode只有一个孩子时(左孩子或右孩子),其孩子,直接继承DelNode的位置
		if((!DelNode->lchild)||(!DelNode->rchild))
		{
			if(!p)//只有一个结点,且是根节点的情况 
			{
				// [情况4] DelNode是根节点,有右孩子 
				if(!DelNode->lchild)
				{
					(*T)=DelNode->rchild;
					free(DelNode);
					return;
				}
				
				// [情况5] DelNode是根节点,有左孩子
				if(!DelNode->rchild)
				{
					(*T)=DelNode->lchild;
					free(DelNode);
					return;
				}
			}
			
			
			if(!DelNode->rchild)//只有左子树时
		    {
		    	// [情况6] DelNode是前驱节点p的左孩子,有左孩子
                if(DelNode->data<p->data) 
                {
                	p->lchild=DelNode->lchild; 
			    }
			    
			    // [情况7] DelNode是前驱节点p的右孩子,有左孩子
		    	else
                {
                	p->rchild=DelNode->lchild; 
			    }
			    free(DelNode);
			    return;
		    } 
			
			if(!DelNode->lchild)//只有右子树时
            {
            	// [情况8] DelNode是前驱节点p的左孩子,有右孩子
        	    if(DelNode->data<p->data)
                {
            	    p->lchild=DelNode->rchild; 
			    }
			    
			    // [情况9] DelNode是前驱节点p的右孩子,有右孩子
			    else
                {
                	p->rchild=DelNode->rchild; 
		    	}
		    	free(DelNode);
			    return;
		    }
		}

		//3.当要删除的结点DelNode左右子树都存在时
		if(DelNode->lchild&&DelNode->rchild)
		{
			
			Bstree *s;
			Bstree *q;//q存储s的前驱节点 
			
			q=DelNode; 
			s=DelNode->lchild;

			while(s->rchild)
			{
				q=s; 
				s=s->rchild;//找到的s一定是叶子节点,即无左右孩子 
			}	
			DelNode->data=s->data;
			
			if(q==DelNode)//说明上述while循环一次都未执行 
			{
				//q->lchild=NULL; 考虑过于片面,未考虑到s可能存在左孩子 
				q->lchild=s->lchild; 
			}
			else //说明while循环找到了最右边的s 
			{
				//q->rchild=NULL;考虑过于片面,未考虑到s可能存在左孩子 
				q->rchild=s->lchild; 
			}
			free(s);
			return;
		}
	}	
}

void inorder(Bstree *T)
{
	if(!T)
	{
		return;
	}
	inorder(T->lchild);
	printf("%c ",T->data);
	inorder(T->rchild);
}

void preorder(Bstree *T)
{
	if(!T)
	{
		return; 
	}
	printf("%c ",T->data);
	preorder(T->lchild);
	preorder(T->rchild);
}

int main()
{
    Bstree *T=NULL;
}
  • 2
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Attract1206

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值