王道课后代码大题---树与二叉树

文章目录

一.二叉树的概念

1.求两结点的最近公共祖先结点(二叉树任意两个结点必然存在最近的公共祖先结点)

已知一颗二叉树按顺序存储结构进行存储,设计一个算法,求编号分别为i和j的两个结点的最近公共祖先结点

方法一

思想

最坏的情况是:最近公共祖先结点为根节点,而且从最近的公共祖先结点到根节点的全部祖先结点都是公共的。由二叉树的顺序存储的性质可知,任意结点i的双亲结点的编号为i/2,求解i和j最近公共祖先结点的算法步骤如下(设从数组下标1开始存储)
(1)若i>j,则结点i所在层次大于等于结点j所在层次。结点i的双亲结点为i/2,
若i/2=j,则结点i/2是原结点i和结点j的最近公共祖先结点。
若i/2!=j,则令i=i/2,即以i的双亲结点为起点,采用递归的方法继续查找。
(2)若i<j,同理于(1)

代码
int Common(SqTree T,int i,int j)
{
   
  //本算法在二叉树中查找结点i和结点j的最近公共祖先结点
  if(T[i]!='#'&&T[j]!='#')
  {
   
    while(i!=j)
    {
   
      //两个编号不同时,循环
      if(i>j)
          i=i/2;//向上找i的祖先
      else
          j=j/2;//向上找j的祖先
    }
    return T[i];
  }
}

方法二

思想:

在这里插入图片描述

后序遍历二叉树,根压在栈底,最后访问。设p在q的左边。采用后序非递归算法,栈中存放二叉树结点的指针,当访问到某结点时,栈s中所有元素均为该结点的祖先。

1.后序遍历先遍历到p
2.将栈复制到辅助栈s1
3.继续遍历到结点q时
4.s从栈顶开始逐个与s1中去匹配
5.第一个匹配的元素就是这俩结点的最近公共祖先。

代码:
//二叉树中求两个结点的祖父结点 
#include <iostream>
#include <cstdlib>
#include <cstring>
using namespace std;
#define MaxSize 50

typedef struct BiNode
{
   
	char data;
	struct BiNode *lchild,*rchild;
}BiNode,*BiTree;

typedef struct
{
   
	BiTree t;
	int tag;//tag=0表示左子女已被访问,tag=1表示右子女已被访问 
}Bistack;//栈内存放二叉树结点 

BiNode *FindBiNode(BiTree &T,char ch);
Bistack s[MaxSize],s1[MaxSize];
int top=0,top1;

BiNode *Ancestor(BiTree &T,BiNode *p,BiNode *q)
{
   
	BiNode *bt=T;
	while(bt!=NULL||top>0)
	{
   
		
		while(bt!=NULL)
		{
   
			if(bt!=p&&bt!=q)
			{
   
				s[++top].t=bt;
				s[top].tag=0;
				bt=bt->lchild;
			}
			else
			{
   
				s[++top].t=bt;
				s[top].tag=1;
				break;
			}
		}
		while(top!=0&&s[top].tag==1)
		{
   
			if(s[top].t==p)
			{
   
			   if(bt->rchild!=NULL &&bt->lchild!=NULL){
   
				   BiNode *temp = FindBiNode(bt,q->data);
				   if(temp !=NULL){
   
					  return bt;
				   }
			    }
				for(int i=1;i<=top;i++)
				{
   
					s1[i]=s[i];
					top1=top;
				}
			}
			else if(bt==q)
			{
   
				for(int i=top;i>0;i--)
				{
   
					for(int j=top1;j>0;j--)
					{
   
						if(s[i].t==s1[j].t)
						{
   
							return s[i].t;
						}
							
					}
				}
			}
			top--;
		}
		if(top!=0)
		{
   
			s[top].tag=1;
			bt=s[top].t->rchild;
		}
	}
	return NULL;
}

void CreatBiTree(BiTree &T)
{
   
	char ch;
	cout<<"输入元素,按#结束:"<<endl; 
	cin>>ch;
	if(ch=='#')
		T=NULL;
	else
	{
   
		T=new BiNode;
		T->data=ch;
		CreatBiTree(T->lchild);
		CreatBiTree(T->rchild);
	}
	
}

void PreOrder(BiTree &T)
{
   
	if(T!=NULL)
	{
   
		cout<<T->data<<" ";
		PreOrder(T->lchild);
		PreOrder(T->rchild);
	 } 
}

//按值查找二叉树中结点
BiNode *FindBiNode(BiTree &T,char ch)//**~~~~~~~按值查找**
{
   
	if(T==NULL)
		return NULL;
	if(T->data==ch)
		return T;
	BiNode *p=FindBiNode(T->lchild,ch);
	if(p!=NULL)
		return p;
	return FindBiNode(T->rchild,ch);
 } 

int main()
{
   
	BiTree T;
	CreatBiTree(T);
	cout<<"前序遍历结果:"<<endl;
	PreOrder(T);
	
	cout<<"输入结点值:"<<endl;
	char ch;
	cin>>ch;
	BiNode *p=FindBiNode(T,ch);
	if(p==NULL)
	{
   
		cout<<"无此结点"<<endl; 
		exit(0);
	}
	else
	{
   
		cout<<p->data<<endl;
		cout<<"查找成功"<<endl;
	}
		
	cout<<"输入结点值:"<<endl;
	char ch2;
	cin>>ch2;
	BiNode *q=FindBiNode(T,ch2);
	if(q==NULL)
	{
   
		cout<<"无此结点"<<endl; 
		exit(0);
	}else
	{
   
		cout<<q->data<<endl;
		cout<<"查找成功"<<endl;
	}
	
	BiNode *res=Ancestor(T,p,q);
	if(res!=NULL)
		cout<<res->data<<endl;
	else
		cout<<res->data<<endl;
	return 0;
}

二.二叉树的遍历和线索二叉树(默认都为二叉链表存储)

1.编写后序遍历二叉树的非递归算法

思想

后序:左右根的顺序.
1.沿着根的左孩子,依次入栈,直到左孩子为空。此时栈内元素依次为A,B,D
2.读栈顶元素:若其右孩子不为空且未被访问过,将右子树转执行1.
3.否则栈顶元素出栈并访问
每次出栈访问完一个结点就相当于遍历完以该结点为根的子树

代码

void PostOrder(BiTree T)
{
   
  InitStack(S);//初始化栈
  p=T; 
  r=NULL;//设定一个辅助指针r来指向最近访问过的结点
  while(p||IsEmpty(S))
  {
   
    if(p)
    {
   
      push(S,p);
      p=p->lchild;
    }
    else
    {
    //向右
      GetTop(S,p)//读栈顶结点非出栈
      if(p->rchild&&p->rchild!=r)
      {
   
        //若右子树存在,且未被访问过
        p=p->rchild;//转向右
        
  • 5
    点赞
  • 24
    收藏
    觉得还不错? 一键收藏
  • 4
    评论
### 回答1: 可以先将i和j的路径分别求出来,然后比较路径上最后一个相同的节点即可。具体步骤如下: 1. 根据i的编号,求出它在顺序存储结构中的下标index_i,假设根节点的下标为。 2. 根据下标index_i,可以求出i的父节点的下标(index_i-1)/2。 3. 重复步骤2,直到找到根节点为止,将路径上的节点存储在一个数组path_i中。 4. 同样地,根据j的编号求出它在顺序存储结构中的下标index_j,求出j的路径path_j。 5. 从path_i和path_j的末尾开始比较,找到最后一个相同的节点即为它们的最近公共祖先节点。 6. 返回最近公共祖先节点的值即可。 时间复杂度为O(logn),其中n为二叉树的节点数。 ### 回答2: 首先需要明确一下顺序存储结构是指二叉树的节点按层次顺序依次存放在数组中,根节点存储在下标为1的位置,其左子节点存储在下标为2的位置,右子节点存储在下标为3的位置,依次类推。如果节点不存在,数组中存储一个特定的值(如0)表示空节点。 接下来介绍一种求解最近公共祖先节点的算法,时间复杂度为O(logn)。假设节点i和节点j都存在于二叉树中,且节点i在节点j的上方(即节点i在节点j的祖先节点中),那么节点i和节点j的最近公共祖先节点一定是节点i或节点i的某个祖先节点。反过来,如果节点j在节点i的上方(即节点j在节点i的祖先节点中),那么节点i和节点j的最近公共祖先节点一定是节点j或节点j的某个祖先节点。因此,我们可以先查找节点i和节点j的深度,然后让深度较深的节点先向上移动,直到两个节点所在的层次相同。接着同时向上移动两个节点,最终相遇的节点就是它们的最近公共祖先节点。 具体实现过程如下: 1. 分别计算节点i和节点j的深度d_i、d_j。 2. 如果d_i>d_j,则节点i向上移动d_i-d_j步,使得节点i和节点j处于同一层次。 3. 如果d_i<d_j,则节点j向上移动d_j-d_i步,使得节点i和节点j处于同一层次。 4. 同时向上移动节点i和节点j,直到它们相遇为止,相遇的节点即为它们的最近公共祖先节点。 时间复杂度分析:计算深度的过程需要遍历二叉树,时间复杂度为O(n);向上移动节点的过程最多移动logn次,每次移动的时间复杂度为O(1),因此时间复杂度为O(logn)。总时间复杂度为O(n+logn),即O(n)。 ### 回答3: 算法思路: 根据二叉树按顺序存储的方式,我们可以通过节点的编号计算出节点在数组中的下标。假设节点 i 的下标为 x,节点 j 的下标为 y。 由于二叉树是任意的,因此我们不能通过顺序遍历的方式来寻找最近公共祖先节点。但是,我们可以通过寻找路径的方式来找到最近公共祖先。具体来说,我们可以先分别找到节点 i 和节点 j 的路径,然后寻找路径中最后一个相同的节点即为最近公共祖先。 我们可以通过递归的方式来寻找节点的路径。具体来说,如果当前节点为空,则路径不存在。如果当前节点已经是目标节点之一,则路径为当前节点。否则,我们递归地在左子和右子中查找目标节点,如果找到了目标节点,则将当前节点加入路径中。 算法实现: 1. 计算节点 i 和节点 j 在数组中的下标 x 和 y。 2. 分别寻找节点 i 和节点 j 的路径。为了方便计算,我们可以将节点路径用数组来表示。假设节点 i 的路径为 path_i,节点 j 的路径为 path_j,那么: ``` void findPath(int i, int x, int path[]) { if (x >= MAX_SIZE) { // 超出数组边界,节点不存在 path[0] = -1; return; } if (x == i) { // 找到了目标节点 path[0] = 1; path[1] = x; return; } findPath(i, x * 2, path); // 在左子中查找目标节点 if (path[0] == 1) { // 找到了目标节点 path[++path[0]] = x; return; } findPath(i, x * 2 + 1, path); // 在右子中查找目标节点 if (path[0] == 1) { // 找到了目标节点 path[++path[0]] = x; return; } path[0] = -1; // 没有找到目标节点 } int path_i[MAX_SIZE], path_j[MAX_SIZE]; findPath(i, 1, path_i); findPath(j, 1, path_j); ``` 3. 寻找路径中最后一个相同的节点。具体来说,我们从末尾开始遍历两个路径,直到遇到第一个不相同的节点为止。 ``` int k = 1; while (k <= path_i[0] && k <= path_j[0] && path_i[path_i[0] - k + 1] == path_j[path_j[0] - k + 1]) { k++; } k--; int lca = path_i[path_i[0] - k + 1]; ``` 完整代码如下:

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值