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