算法导论--二叉搜索树

二叉搜索树

   每个节点除关键字外,还包含属性left,right,p,分别指向其左孩子,右孩子,和双亲

且对任何节点x,其左子树的最大关键字不超过x.key,其右子树中的关键字最小不低于x.key。

本程序中的结点定义:

typedef struct Node
{
  struct Node * p;
  struct Node * left;
  struct Node * right;
  int key;
  
}Node;


中序遍历

  中序递归遍历二叉搜索树,能够按序(小到大)输出二叉树中的所有关键字,根的关键字位于左右孩子关键字之间。

  中序遍历耗费的是一个线性的时间。

void Inorder_Tree_Walk(Node * T)  //中序遍历树T,输出
{
	if (T != NULL)
	{
           Inorder_Tree_Walk(T->left);   //递归其左孩子
	   printf("%d\n",T->key);        //输出根的关键字
	   Inorder_Tree_Walk(T->right);  //递归其右孩子
	}
}

查询

 查询的几个操作都能在O(h)内完成,h为二叉树的高度。

查找指定元素

  在二叉树中查找指定元素,若存在,返回其结点地址,若不存在,返回空;   T为树根  
Node * Tree_Search(Node *T ,int k)  //寻找数k是否在T树中,且返回数k的结点地址
{	
    while(T !=NULL && T->key != k)  //按大小遍历树
	{
		if ( k < T->key)    //若小于其T的关键字,则遍历其左孩子
			T=T->left;
		else
	        T=T->right;
	}
	if ( T == NULL)            //没找到数k,返回空
	{     
		return NULL;
	}
	
   else 
	{
		return T;
	}
	
}

查找最大/小关键字元素

 查找二叉树中最大或最小的元素,按照二叉树的定义:
最大元素一定是沿着right孩子指针不断向下查找,直到遇到第一个NULL;
同理最小元素一定是沿着left孩子指针不断向下查找,直到遇到第一个NULL;
Node * Tree_Minimum(Node * T)  //给定树根T,查找树的最小元素结点,并返回其地址
{
	while(T->left != NULL)
		T=T->left;
	return T;
}
int Tree_Maximum(Node * T)   //给定树根T,查找树的最大元素结点,并返回其值
{
	while(T->right != NULL)
		T=T->right;
	return T->key;
}

查找指定元素的后继和前驱

1.后继:即查找指点元素按序的下一个结点位置,即下一个比它大的数。
  按照搜索二叉树的定义,若其存在右孩子,则它的下一个结点一定在其右孩子的子树中最小的一个,即查找Tree_Minimum(x->right);若不存在右孩子,则位于它的一个祖先结点上,且这个祖先一定是最底层有左孩子的祖先,且这个左孩子也是它的一个祖先...(好晦涩) 。可以这样简单理解,要查x的后继,既然它不存在右孩子,那么说明它是这个子树的最大数,那么它的后继一定要往祖先上找,那就去往上层层找祖先,这个祖先必须满足有左孩子,且这个x必须也要位于祖先的左子树中。这样x就为祖先的左子树中最大的一个了。
int Tree_Successor(Node * T,int t)  //找数t的后继,T为树根 
{
	Node * x=Tree_Search(T,t);  //找到元素t的位置
	Node * y=NULL;
	if ( x->right != NULL)
	{
		y=Tree_Minimum(x->right);
		return y->key;
	}
	y = x->p;
	while( y!=NULL && x==y->right)
	{
       x=y;
	   y=y->p;
	}
	return y->key;
    
}
2.前驱:同上去理解。
int Tree_Predecessor(Node * T,int t)  //找数t的前驱,T为树根 
{
	Node * x=Tree_Search(T,t);    //找到元素t的位置
	Node * y=NULL;
	if ( x->left!= NULL)
	{
		return Tree_Maximum(x->left);
	}
	y = x->p;
	while( y!=NULL && x==y->left)
	{
		x=y;
		y=y->p;
	}
	return y->key;
}

插入结点

 即向一个二叉搜索树中插入一个x结点,只需要不断比较x->key与当前结点z->key的大小,若小于,则肯定向z的左子树插入,否则向z的右子树插入,循环比较,直到遇到当前结点的左/右子树为空为止。
Node *Tree_Insert(Node *Root,Node * z)   //二叉搜索树插入,返回树的根
{
   Node * y=NULL;
   Node * x=Root;
   while( x != NULL)                 
   {
	   y=x;
	   if (z->key < x->key)
		   x = x->left;
	   else
		   x = x->right;
   }
   z->p = y;
   if ( y == NULL)    //建立第一个根节点
   {   
	   Root = z;
   }
   else if (z->key < y->key)
   {
	   y->left = z;
   }
   else 
   {
	   y->right = z;
   }
   return Root; 
}

删除结点

删除的情况稍复杂,因为要保证二叉搜索树的性质:

没有孩子节点

若要删除的结点z没有孩子节点,则可以直接删除z结点,只要令其z->p->left\right = NULL即可;

只有一个孩子节点

若结点z只有一个孩子,无论是其左孩子还是右孩子,直接替代z结点位置即可;
举个例子:假如z只有一个右孩子,且z是其双亲的左孩子:
z->p->left = z->right;
z->right->p = z->p   ;

有两个孩子节点

找出要删除结点z的后继结点y,这个结点y一定位于结点z的右子树中且是z的右子树中最小的一个元素,所以结点y一定没有左孩子。
分两种情况:
1.结点y是结点z的右孩子:这时用y替换z的位置,结点y之前的右子树还是它的右子树,结点z之前的左子树作为y的左子树。

2.结点y不是结点x的右孩子:先用y的右子树x替换y位置,接着用y替换z的位置。


例程

#include <STDIO.H>
#include <STDLIB.H>
typedef struct Node
{
  struct Node * p;
  struct Node * left;
  struct Node * right;
  int key;
  
}Node;
Node *Tree_Insert(Node *Root,Node * z)   //二叉搜索树插入,返回树的根
{
   Node * y=NULL;
   Node * x=Root;
   while( x != NULL)                 
   {
	   y=x;
	   if (z->key < x->key)
		   x = x->left;
	   else
		   x = x->right;
   }
   z->p = y;
   if ( y == NULL)    //建立第一个根节点
   {   
	   Root = z;
   }
   else if (z->key < y->key)
   {
	   y->left = z;
   }
   else 
   {
	   y->right = z;
   }
   return Root; 
}
Node * Establish_Tree(int *A,int Length) //建立一个节点数为Length的二叉搜索树
{
	int i;
	Node * node,*Root=NULL;
	for (i=0;i<Length;i++)
	{
		node = (Node *)malloc(sizeof(Node));
		node->key=A[i];
		node->p=NULL;
		node->left=NULL;
		node->right=NULL;
		Root=Tree_Insert(Root,node);        //调用插入函数建立树
	}
  return Root;
}
void Inorder_Tree_Walk(Node * T)  //中序遍历树T,输出
{
	if (T != NULL)
	{
       Inorder_Tree_Walk(T->left);
	   printf("%d\n",T->key);
	   Inorder_Tree_Walk(T->right);
	}
}
Node * Tree_Minimum(Node * T)
{
	while(T->left != NULL)
		T=T->left;
	return T;
}
int Tree_Maximum(Node * T)
{
	while(T->right != NULL)
		T=T->right;
	return T->key;
}
Node * Tree_Search(Node *T ,int k)  //寻找数k是否在树中,且返回数k的地址
{	
    while(T !=NULL && T->key != k)
	{
		if ( k < T->key)
			T=T->left;
		else
	        T=T->right;
	}
	if ( T == NULL)
	{     
		return NULL;
	}
	
   else 
	{
		return T;
	}
	
}

int Tree_Successor(Node * T,int t)  //找数t的后继,T为树根 
{
	Node * x=Tree_Search(T,t);
	Node * y=NULL;
	if ( x->right != NULL)
	{
		y=Tree_Minimum(x->right);
		return y->key;
	}
	y = x->p;
	while( y!=NULL && x==y->right)
	{
       x=y;
	   y=y->p;
	}
	return y->key;
    
}
int Tree_Predecessor(Node * T,int t)  //找数t的前驱,T为树根 
{
	Node * x=Tree_Search(T,t);
	Node * y=NULL;
	if ( x->left!= NULL)
	{
		return Tree_Maximum(x->left);
	}
	y = x->p;
	while( y!=NULL && x==y->left)
	{
		x=y;
		y=y->p;
	}
	return y->key;
}
void Transplant(Node **T,Node *u,Node *v)  //用节点v替换节点u
{
	
	if (u->p == NULL)               //若u为根节点,赋值给T
	   *T = v;
	else if (u == u->p->left)
       u->p->left = v;
	else 
	   u->p->right = v;
	if ( v!= NULL)
	   v->p = u->p;	
}
Node* Tree_Delete(Node *T,int x)  //删除元素 x
{
   Node * z=Tree_Search(T,x);
   Node * y=NULL;
   if (z->left == NULL)
      Transplant(&T,z,z->right);
   else if (z->right == NULL)
      Transplant(&T,z,z->left);
   else
   {
      y = Tree_Minimum(z->right);
	  if ( y->p != z)
	  {
         Transplant(&T,y,y->right);
         y->right = z->right;
		 y->right->p = y;
	  }
	  Transplant(&T,z,y);
	  y->left = z->left;
	  y->left->p = y;
   }
   
   return T;
}
int main(void)
{
	int A[]={3,10,2,5,9,7,4,1,6,8};
    int Length = sizeof(A)/sizeof(A[0]);
	Node * T;
	T = Establish_Tree(A,Length);                    //建立二叉搜索树
	Inorder_Tree_Walk(T);                            //中序遍历输出 
	printf("4 Successor is %d\n",Tree_Successor(T,4));//后继函数
    printf("7 Predecessor is %d\n",Tree_Predecessor(T,7));//前驱函数
	printf("_____________________________________________\n");
	T = Tree_Delete(T,3);    //删除节点函数
	Inorder_Tree_Walk(T);	
	return 0;
}


  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: 算法6-1:二叉链表存储的二叉树的结构定义 二叉链表存储的二叉树是一种常见的二叉树存储方式,其结构定义如下: typedef struct BiTNode{ TElemType data; // 数据域 struct BiTNode *lchild, *rchild; // 左右孩子指针 }BiTNode, *BiTree; 其,TElemType为二叉树节点的数据类型,可以根据实际情况进行定义。 算法6-2:二叉链表存储的二叉树的创建 二叉链表存储的二叉树的创建可以通过递归方式实现,具体算法如下: void CreateBiTree(BiTree *T){ TElemType ch; scanf("%c", &ch); // 输入节点的值 if(ch == '#'){ // 如果输入的是#,表示该节点为空 *T = NULL; }else{ *T = (BiTree)malloc(sizeof(BiTNode)); // 创建新节点 (*T)->data = ch; // 节点赋值 CreateBiTree(&((*T)->lchild)); // 递归创建左子树 CreateBiTree(&((*T)->rchild)); // 递归创建右子树 } } 其,#表示空节点,输入#时,该节点为空。 算法6-3:二叉链表存储的二叉树的遍历 二叉链表存储的二叉树的遍历可以通过递归方式实现,具体算法如下: void PreOrderTraverse(BiTree T){ // 先序遍历 if(T != NULL){ printf("%c ", T->data); // 访问根节点 PreOrderTraverse(T->lchild); // 遍历左子树 PreOrderTraverse(T->rchild); // 遍历右子树 } } void InOrderTraverse(BiTree T){ // 序遍历 if(T != NULL){ InOrderTraverse(T->lchild); // 遍历左子树 printf("%c ", T->data); // 访问根节点 InOrderTraverse(T->rchild); // 遍历右子树 } } void PostOrderTraverse(BiTree T){ // 后序遍历 if(T != NULL){ PostOrderTraverse(T->lchild); // 遍历左子树 PostOrderTraverse(T->rchild); // 遍历右子树 printf("%c ", T->data); // 访问根节点 } } 其,先序遍历是先访问根节点,再遍历左子树和右子树;序遍历是先遍历左子树,再访问根节点和右子树;后序遍历是先遍历左子树和右子树,再访问根节点。 算法6-4:二叉链表存储的二叉树的销毁 二叉链表存储的二叉树的销毁可以通过递归方式实现,具体算法如下: void DestroyBiTree(BiTree *T){ if(*T != NULL){ DestroyBiTree(&((*T)->lchild)); // 销毁左子树 DestroyBiTree(&((*T)->rchild)); // 销毁右子树 free(*T); // 释放节点空间 *T = NULL; // 节点指针置空 } } 其,递归销毁左子树和右子树,最后释放节点空间并将节点指针置空。 ### 回答2: 二叉链表存储的二叉树是一种常见的二叉树存储结构,它是由一个节点数组和一个指向根节点的指针组成的。每个节点都包含一个值、一个左孩子指针和一个右孩子指针。这种存储结构可以非常方便地进行二叉树的遍历和修改操作,是二叉树基本操作的重要实现方式。 算法6-1是建立二叉链表存储结构的算法,它通过前序遍历和序遍历来构造一棵二叉树。具体而言,算法6-1会先遍历前序序列找到根节点,并在序序列找到根节点的位置,然后递归地建立左子树和右子树。该算法时间复杂度为O(n^2),其n为二叉树的节点数。算法6-1的缺点是在处理大规模的二叉树时效率较低。 算法6-2是先序遍历的递归算法,它通过递归实现先序遍历。具体而言,先访问根节点,然后递归访问左子树和右子树。该算法时间复杂度为O(n),其n为二叉树的节点数。 算法6-3是序遍历的递归算法,它通过递归实现序遍历。具体而言,先递归访问左子树,然后访问根节点,最后递归访问右子树。该算法的时间复杂度为O(n),其n为二叉树的节点数。 算法6-4是后序遍历的递归算法,它通过递归实现后序遍历。具体而言,先递归访问左子树和右子树,最后访问根节点。该算法的时间复杂度为O(n),其n为二叉树的节点数。 总之,二叉链表存储的二叉树是一种非常方便实用的数据结构,可以方便地进行各种遍历和修改操作。同时,基于递归实现的三种遍历算法也非常简洁高效,应用广泛。 ### 回答3: 算法6-1至6-4描述了二叉链表存储的二叉树。二叉链表存储的二叉树是一种基于线性链式结构存储的二叉树,它通过指针关系将每个结点的左右子树联系起来。下面我们将分别对每个算法进行详细解释。 算法6-1:二叉链表存储结构定义。该算法定义了二叉树的结构,主要是通过指针关系分别指向左右子树和父节点。这样的结构便于对二叉树的操作和遍历。 算法6-2:二叉链表存储的建立。该算法通过输入有序序列,依次插入二叉树结点,先从根结点开始比较大小,插入到左右子树。当插入到空节点时,创建新的结点,通过指针关系连接起来。递归地进行插入操作,直到序列的所有元素插入完毕。 算法6-3:二叉链表存储的遍历。该算法通过对二叉树的先序、序和后序遍历进行递归实现。先序遍历需要先访问根节点,然后再对左右子树进行遍历;序遍历需要先访问左子树,再访问根节点,最后再访问右子树;后序遍历需要先访问左右子树,最后访问根节点。 算法6-4:二叉链表的基本操作。该算法主要包括插入、删除、查找和修改等操作。其,插入和删除操作需要先定位到相应的结点,然后通过指针关系进行连接或删除。查找操作需要按照二叉树的规律进行查找,找到目标结点后返回其对应的指针。修改操作类似于删除操作,先找到需要修改的结点,然后进行相应的修改操作。 综上所述,二叉链表存储的二叉树是一种便于操作和遍历的数据结构,它通过指针关系将每个结点的左右子树联系起来。该结构的建立、遍历和操作都可以通过递归实现,不仅提高了程序的可读性,还方便了程序员的开发。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值