由层序遍历和中序遍历重建二叉树

问题背景

这是我在阅读胡凡的《算法笔记》一书中的一道思考题,此前,书中讲解了如何由前序遍历序列和中序遍历序列重建一棵完整的二叉树。那么现在假设对于一棵二叉树,有它的
1.层序遍历序列:

ABCDEFGHIJ

2.中序遍历序列:

DBGEHJACIF

请你重建出这棵二叉树,并给出它的前序遍历结果。

算法分析与设计

当解决之前的那个问题,即由前序遍历序列和中序遍历序列重建二叉树,我们通过前序遍历序列确定根,然后在中序遍历序列中寻找这个根的位置,由此分出它的左子树和右子树,即分别为其左边的子序列和右边的子序列,并对上述过程不断递归,直到分不出合法的左右子序列(序列的左端点须小于等于右端点)为递归的终点,具体我们可以写出下面的代码:

struct node{
	int data;
	node* lchild;
	node* rchild;
};
//当前先序序列为[preL,preR],中序序列区间为[inL,inR],返回根节点地址
node* create(int preL,int preR,int inL,int inR) {
	if(inL > inR) {
		return NULL;
	}
	node* root = new node;
	root->data = pre[preL];
	int k;
	for(k=inL;k<=inR;k++) {
		if(in[k]==root->data)
			break;
	} 
	int nLeft = k - inL;
	int nRight = inR - k;
	root->lchild = create(preL + 1,preL + nLeft,inL,k-1);
	root->rchild = create(preR - nRight + 1,preR,k+1,inR);
	return root;
}

这段代码有兴趣的读者可以仔细阅读理解并尝试自己写出,但我解释这个例子的重点在于指出这样一个事实:
我们之所以能够根据前序遍历和中序遍历序列确定二叉树,是因为前序遍历序列可以确定根,中序遍历序列可以依据根确定左右子树。
回到本文所探讨的由层序遍历序列和中序遍历序列确定二叉树的这个问题,之所以能够还原二叉树,也是因为可以根据层序遍历序列确定根,然后在中序遍历序列中分出根的左右子树,我们思考的关键在于,如何确定根,如何走出这步先手棋。
您可以看到在先序序列中,左端点即为根,但是层序序列是由BFS算法计算出来的,不同于前、中、后,这三者均是由DFS算法得到的遍历序列,我们甚至无法在层序遍历序列中找到一个连续区间,其为某一个完整的子树,它是相对混乱的。
这势必要求我们开辟出一个“寻根”的全新思路。
事实上,当我们在中序序列中划分出了一棵子树,要依据层序遍历序列找到它的根结点是容易的。因为根结点是这个子序列中,第一个在层序序列中出现的元素,也就是在层序序列中的位置最靠前的元素。明白了这样的事实,重建算法便呼之欲出:

函数传入中序遍历序列子序列的左端点inL和右端点inR,我们的子序列即是[inL,inR]这一区间。
遍历[inL,inR],找到其在层序遍历序列中编号最小的元素,即为根。
新建结点,其数据域赋值为刚刚找到的根元素,将该元素的左侧子序列作为新的递归参数,传入函数进行递归,返回值作为新结点(根结点)的左子树,对根元素右侧子序列同样递归,返回为新结点的右子树。
最后,返回该结点。

实现代码

#include <cstdio>
#include <cstring>
#include <cstdlib>
#include <map>
using namespace std;
struct node{
	char data;
	node* lchild;
	node* rchild;
};
char layer[20] = "ABCDEFGHIJ";//层序序列
char in[20]    = "DBGEHJACIF";//中序序列
map<char,int> layerPos; 
node* create(int inL,int inR){
	if(inL>inR)//递归终点
		return NULL;
	int rootPos = inL;
	for(int i=inL;i<=inR;i++){
	//寻找根结点
	//层序编号最小
		if(layerPos[in[i]]<layerPos[in[rootPos]])
			rootPos = i;
	}
	node* root = new node;
	root->data = in[rootPos];
	//递归生成左子树
	root->lchild = create(inL,rootPos - 1);
	//递归生成右子树
	root->rchild = create(rootPos + 1,inR);
	//返回树
	return root;
}
void preorder(node* a){
//先序遍历
	if(a==NULL)
		return;
	putchar(a->data);
	preorder(a->lchild);
	preorder(a->rchild);
} 
int main(){
	for(int i=0;i<10;i++){
		//建立字符与其在中序遍历序列中的位置的映射关系 
		layerPos[layer[i]] = i;
	}
	node* binTree = create(0,9);
	preorder(binTree);
}

运行结果:
在这里插入图片描述

一些碎碎念

这是今天下午的重头戏,没错,这是我解决的关于树的第一个问题,我就是这样的菜鸡,明明离考试没几天了。
但是因为我在网上,以及图书馆的参考书籍里没有发现令我满意的解答,当我灵光一现想到解题关键的时候,我就决定要把这个被我遗忘许久的方法贡献出来。之所以说遗忘许久,是因为我可能以前在某个课程上听过相关算法,只是我忘记了,今天万幸又回忆起来了。
我认为这是一个比网上很多解法都要简洁,且容易理解,且非常巧妙地算法,我绝不是自夸,这并不是我创造出来的,我已经说过,我以前一定听某位老师讲过同样的算法。
所以我写这篇博客,希望与之前的我一样对这个问题不清楚的朋友们少走一些弯路。
我始终认为,简洁是程序之美,算法之好,好在它的巧妙,这是笔者本人在编程中不断追求的东西,笔者更希望以后能够阅读到各路大神怀着相同目的分享出的好算法,当我想到我在阅读那些文章时心中涌动的温暖,此刻因为自己的这篇文章也可能涌动在别人心间的时候,我便认为自己终究做了一件蛮有意义的事情,总之,感谢您的阅读。

  • 26
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
好的,给定二叉树的层序遍历中序遍历,我们可以通过重建二叉树的方式来得到先序遍历和后序遍历序列。 先用中序遍历的结果确定根节点,然后在层序遍历中找到根节点,将层序遍历结果分为左子树和右子树。重复以上步骤,直到所有节点都添加到树中。 最后,我们可以通过深度优先搜索的方式得到先序遍历和后序遍历序列。 以下是代码实现: ```python from collections import deque class TreeNode: def __init__(self, val=0, left=None, right=None): self.val = val self.left = left self.right = right def buildTree(inorder, levelorder): if not inorder: return None # 根节点 root = TreeNode(levelorder[0]) index = inorder.index(root.val) # 左子树的中序遍历结果 left_inorder = inorder[:index] # 右子树的中序遍历结果 right_inorder = inorder[index+1:] # 左子树的层序遍历结果 left_levelorder = [x for x in levelorder if x in left_inorder] # 右子树的层序遍历结果 right_levelorder = [x for x in levelorder if x in right_inorder] # 递归构建左子树和右子树 root.left = buildTree(left_inorder, left_levelorder) root.right = buildTree(right_inorder, right_levelorder) return root def preorderTraversal(root): if not root: return [] return [root.val] + preorderTraversal(root.left) + preorderTraversal(root.right) def postorderTraversal(root): if not root: return [] return postorderTraversal(root.left) + postorderTraversal(root.right) + [root.val] inorder = [9, 3, 15, 20, 7] levelorder = [3, 9, 20, 15, 7] root = buildTree(inorder, levelorder) preorder = preorderTraversal(root) postorder = postorderTraversal(root) print("先序遍历序列:", preorder) print("后序遍历序列:", postorder) ``` 输出结果为: ``` 先序遍历序列: [3, 9, 20, 15, 7] 后序遍历序列: [9, 15, 7, 20, 3] ```
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值