树的周边(二)树的基本操作:深度、宽度与遍历

本文深入探讨了树的存储方式、求树的深度、树的宽度、三种遍历方式及其实现,并介绍了如何根据前序/后序和中序遍历重构树或推知后序/前序遍历。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

1.树的存储

树的数据结构中除了要存储本节点内的值之外,还要存储子结点的指针。可以定义为:

struct TreeNode
{
	int data;
	TreeNode *left,*right;
};

根据具体的需求情况,也可以添加其他的成员,比如可以添加一个指针指向其父结点。因为树的存储特点,一般情况下,各种树的操作都可以有递归与非递归两种方式,非递归的方法一般需要用到栈进行各层结点的逐层访问。


2.求树的深度。

树的深度是指树的层数。其中根节点位于树的第1层,依次累加。

参考:

http://blog.youkuaiyun.com/linraise/article/details/9878859

http://blog.youkuaiyun.com/walkinginthewind/article/details/7518888

递归的方式:

int treeDepth(TreeNode *p)
{
		if (!p)
		{
			return 0;
		}
		int left = treeDepth(p->m_pLeft);
		int right = treeDepth(p->m_pRight);
		return left>right?left+1:right+1;
}

非递归的方式:

int Depth(TreeNode *T)
{
		TreeNode * store[50];
		memset(store,0,50);
		int length=0;
		int depth=0;
		stack<TreeNode *> st;
		if (T)
		{
			st.push(T);
			while (!st.empty())
			{
				depth++;
				length=0;
				while (!st.empty())
				{
					TreeNode * p=st.top();
					st.pop();
					if (p->m_pLeft)
						store[length++]=p->m_pLeft;
					if (p->m_pRight)
						store[length++]=p->m_pRight;
				}
				for (int i=0;i<length;i++)
					st.push(store[i]);
			}
			return depth;
		}
		else
			return 0;
}

3.树的宽度

根据上述非递归方式中获得最大的length即可得到树的‘宽度’,如此即可以获得树的面积=深度×宽度。


4.树的三种遍历

树有三种顺序遍历方式:中序遍历、前序遍历及后序遍历。中序遍历:左结点->当前结点->右结点 前序遍历:当前结点->左结点->右结点  后序遍历:左结点->右结点->当前结点。根据中序遍历和前序遍历或后序遍历可以唯一确定一棵树,但仅有一种遍历方式,或根据前后遍历不能唯一确定一棵树。

前序遍历

递归方式:

void preorder(TreeNode *root)
{
  if(root!=NULL)
{
    cout<<root->data<<endl;
    preorder(root->left);
    preorder(root->right);

}

}

非递归方式:

void preorder(TreeNode*root)
{
	treeNode *p=root;
	stack<TreeNode*>s;
	while(p!=NULL || !s.empty())
	{
		while (p!=NULL)
		{
			s.push(p);
			cout<<p->data<<endl;
			p=p->left;
		}
		if (!s.empty())
		{
			p=s.top();
			s.pop();
			p=p->right;
		}
	}
}

中序遍历

递归方式:

void inorder(TreeNode *root)      //递归中序遍历
{
	if(root!=NULL)
	{
		inorder(root->lchild);
		cout<<root->data<<" ";
		inorder(root->rchild);
	}
} 


非递归方式:

void inorder(TreeNode *root)      //非递归中序遍历
{
	stack<TreeNode*> s;
	TreeNode *p=root;
	while(p!=NULL||!s.empty())
	{
		while(p!=NULL)
		{
			s.push(p);
			p=p->lchild;
		}
		if(!s.empty())
		{
			p=s.top();
			cout<<p->data<<" ";
			s.pop();
			p=p->rchild;
		}
	}    
} 

后序遍历

递归方式:

void postorder(TreeNode *root)    //递归后序遍历
{
	if(root!=NULL)
	{
		postorder(root->lchild);
		postorder(root->rchild);
		cout<<root->data<<" ";
	}    
}

非递归方式:
    后序遍历的非递归实现是三种遍历方式中最难的一种。
    因为在后序遍历中,要保证左孩子和右孩子都已被访问并且左孩子在右孩子前访问才能访问根结点,这就为流程的控制带来了难题。要保证根结点在左孩子和右孩子访问之后才能访问,因此对于任一结点P,先将其入栈。如果P不存在左孩子和右孩子,则可以直接访问它;或者P存在左孩子或者右孩子,但是其左孩子和右孩子都已被访问过了,则同样可以直接访问该结点。若非上述两种情况,则将P的右孩子和左孩子依次入栈,这样就保证了每次取栈顶元素的时候,左孩子在右孩子前面被访问,左孩子和右孩子都在根结点前面被访问。

void postorder(TreeNode *root)     //非递归后序遍历
{
	stack<TreeNode*> s;
	TreeNode *cur;                      //当前结点 
	TreeNode *pre=NULL;                 //前一次访问的结点 
	s.push(root);
	while(!s.empty())
	{
		cur=s.top();
		if((cur->lchild==NULL&&cur->rchild==NULL)||
			(pre!=NULL&&(pre==cur->lchild||pre==cur->rchild)))
		{
			cout<<cur->data<<" "; //如果当前结点无孩子或者孩子都已访问过 
			s.pop();
			pre=cur; 
		}
		else
		{
			if(cur->rchild!=NULL)
				s.push(cur->rchild);
			if(cur->lchild!=NULL)    
				s.push(cur->lchild);
		}
	}    
}

5.遍历重构与互换

根据前序/后序和中序遍历重构树或推知后序/前序遍历

void buildTree(char* pre,char* mid,TreeNode* &root){
	int len=strlen(pre);        //实际下先序序列和后序序列的长度是一样的
	//序列长度为0,说明上一级已经是叶子节点,返回
	if(len==0)
	{
		root=NULL;
		return;
	}
	//返回先序的第一个元素在中序中出现的位置
	char* p=strchr(mid,pre[0]);
	int pos=(int)(p-mid);

	//建设子树的根节点
	//先序中第一个元素作为本子树的根节点
	root->data=pre[0];

	if(pos!=0){         //当前节点的左子树是存在的
		TreeNode* left=(TreeNode*)malloc(sizeof(TreeNode));
		root->left=left;
		//左子树根节点上的元素就是先序中的第2个元素
		left->data=pre[1];   
		char* left_pre=(char*)malloc((pos+1)*sizeof(char));
		char* left_mid=(char*)malloc((pos+1)*sizeof(char));
		//找到左子树的先序和中序
		strncpy(left_pre,pre+1,pos);
		left_pre[pos]='\0';
		strncpy(left_mid,mid,pos);
		left_mid[pos]='\0';
		//递归建立左子树
		buildTree(left_pre,left_mid,left);
	}
	else
	{
		root->left=NULL;
	}

	if(pos!=len-1){     //当前节点的右子树是存在的
		TreeNode* right=(TreeNode*)malloc(sizeof(TreeNode));
		root->right=right;
		//右子树根节点上的元素就是先序中的第pos+2个元素
		right->data=pre[pos+1];
		char* right_pre=(char*)malloc((len-pos)*sizeof(char));
		char* right_mid=(char*)malloc((len-pos)*sizeof(char));
		//找到右子树的先序和中序
		strncpy(right_pre,pre+pos+1,len-1-pos);
		right_pre[len-pos-1]='\0';
		strncpy(right_mid,mid+pos+1,len-1-pos);
		right_mid[len-pos-1]='\0';
		//递归建立右子树
		buildTree(right_pre,right_mid,right);
	}
	else
	{
		root->right=NULL;
	}
}

void postfrompremid(char *pre,char *mid,char*post)
{
	int length=strlen(pre);
	if (length==0)
	{
		return;
	}
	post[length-1]=pre[0];
	char *midnode=strchr(mid,pre[0]);
	if (!midnode)
	{
		cout<<"wrong input";
		return;
	}
	int midpos=(int)(midnode-mid);
	if (midpos!=0)//有左子树
	{
		char *leftpre=new char[midpos+1];
		char *leftmid=new char[midpos+1];
		strncpy(leftpre,pre+1,midpos);
		leftpre[midpos]='\0';
		strncpy(leftmid,mid,midpos);
		leftmid[midpos]='\0';
		postfrompremid(leftpre,leftmid,post);
		delete [] leftpre;
		delete [] leftmid;
	}

	if (midpos!=length-1)//有右子树
	{
		char *rightpre = new char[length-midpos];
		char *rightmid = new char[length-midpos];
		strncpy(rightpre,pre+midpos+1,length-midpos-1);
		strncpy(rightmid,mid+midpos+1,length-midpos-1);
		rightpre[length-midpos-1]='\0';
		rightmid[length-midpos-1]='\0';
		postfrompremid(rightpre,rightmid,post+midpos);
		delete [] rightpre;
		delete [] rightmid;
	}
}









评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值