利用任意两种遍历方式重建二叉树

前言

学习了如何利用前序遍历和中序遍历来重建二叉树,我就在思考是否知道了任意两种遍历方式,都能将二叉树重构出来呢?仔细研究了一晚,果然如此。

找规律

先看三种遍历方式

struct Tree{
    int val;
    Tree *left;
    Tree *right;
};

void printTree(Tree *node,int mode){
    if(node!=nullptr){
        switch(mode){
            case 1:
                cout<<node->val<<",";
                printTree(node->left, mode);
                printTree(node->right, mode);
                break;
            case 2:
                printTree(node->left, mode);
                cout<<node->val<<",";
                printTree(node->right, mode);
                break;
            case 3:
                printTree(node->left, mode);
                printTree(node->right, mode);
                cout<<node->val<<",";
                break;
            default:
                break;
        }
    }
}

代码不多说,都明白,1、2、3分别对应前、中、后序遍历。

首先最重要的一个规律就是,除去根结点的数据,每次调用函数都是先输出左子树的所有数据,然后再输出右子树的数据。这个规律与递归无关,相信应该很容易就能发现。

//cout<<node->val<<",";
printTree(node->left, mode);
//cout<<node->val<<",";
printTree(node->right, mode);
//cout<<node->val<<",";

再说第二个规律,前序遍历每次输出的第一个必然是根结点的数据,后序遍历则相反,每次输出的最后一个才是根结点的数据。

前序遍历与中序遍历

前序遍历:3,0,1,2,6,5,4,8,7,9
中序遍历:0,1,2,3,4,5,6,7,8,9

根据前序遍历的规律,我们可以知道根结点为3。通过根结点我们将中序遍历划分为左子树与右子树。

中序遍历:[0,1,2] [3] [4,5,6,7,8,9]
根结点:3
左子树:0,1,2
右子树:4,5,6,7,8,9

此时由于知道了左右子树的个数,可将前序遍历也划分为左子树与右子树

前序遍历:[3] [0,1,2] [6,5,4,8,7,9]
根结点:3
左子树:0,1,2
右子树:6,5,4,8,7,9

对新生成的左子树与右子树重复进行上面的操作,直到每一个节点都当过根结点。

来看实现代码:

//pre:前序遍历
//mid:中序遍历
//length:数组长度
Tree* Fun1(const int *pre, const int *mid,const int length) {
	//当空指针或者数组长度为0时返回
	if (pre == NULL || mid == NULL || length <= 0) 
	    return NULL;

	//建立根节点
	Tree *head = new Tree();
	head->value=pre[0];
	head->left=head->right=NULL;

	//找到中序遍历中根节点所在位置
	int index = 0;
	while (index<length) {
		if (head->value == mid[index])
			break;
		++index;
	}

	//划分左子树
	head->left = Fun1(pre + 1, mid, index);
	//划分右子树
	head->right = Fun1(pre + index +1, mid + index + 1, length - index - 1);

	return head;
}

中序遍历与后序遍历

原理与上面基本相同,只不过根结点放到数组的最后,此处不给解释,希望各位能够自己琢磨。

//mid:中序遍历
//back:后序遍历
//length:数组长度
Tree* Fun2(const int *mid,const int *back,const int length){
    //当空指针或者数组长度为0时返回
    if(back==NULL||mid==NULL||length<=0)
        return NULL;
    
    //建立根节点
    Tree *head=new Tree();
    head->val=back[length-1];
    head->left=head->right=NULL;
    
    //找到中序遍历中根节点所在位置
    int index=0;
    while(index<length){
        if(head->val==mid[index]){
            break;
        }
        ++index;
    }
    
    //划分左子树
    head->left=Fun2(mid,back,index);
    //划分右子树
    head->right=Fun2(mid+index+1, back+index, length-index-1);
    
    return head;
}

前序遍历与后序遍历

前序遍历:3,0,1,2,6,5,4,8,7,9
后序遍历:2,1,0,4,5,7,9,8,6,3

看过前面的推导,相信找到根结点数据3对各位来说一点难度都没有。难点在于如何把左子树和右子树分开。
仔细观察,可以发现有一个非常有意思的规律,前序遍历的第一位总是与后序遍历的最后一位相同(毕竟都是根结点数据)。

前序遍历:[3],0,1,2,6,5,4,8,7,9
后序遍历:2,1,0,4,5,7,9,8,6,[3]

把根结点数据去掉:

前序遍历(左子树+右子树):0,1,2,6,5,4,8,7,9
后序遍历(左子树+右子树):2,1,0,4,5,7,9,8,6

因为左子树在前,所以此时前序遍历的第一位必然是左子树的根结点,同时我们还知道后序遍历左子树的根结点在左子树数据的最后一位,咱们标注一下:

前序遍历(左子树+右子树):[0],1,2 + 6,5,4,8,7,9
后序遍历(左子树+右子树):2,1,[0] + 4,5,7,9,8,6

这样一来就可以轻松的将左子树和右子树区别开来了。然后再进行进一步划分。
这样就完了吗?当然没有,其实这里还有一个小小的陷阱,以刚刚我们区分出来的左子树为例子:

前序遍历:[0],1,2
后序遍历:2,1,[0]

根据我们上面的方法,可以知道此时子树的区分:

前序遍历(左子树+右子树):[1],2 + NULL
后序遍历(左子树+右子树):2,[1] + NULL

然而此时[1,2]并非左子树(1、2均大于根节点0),而是右子树。
这种情况是由于左右子树其中一个为空,此时需要进行简单的判断(小左大右)。
OK,可以上代码了:

//pre:前序遍历
//back:后序遍历
//length:数组长度
Tree* Fun3(const int *pre,const int *back,const int length){
    //当空指针或者数组长度为0时返回
    if(pre==NULL||back==NULL||length<=0)
        return NULL;
    
    //建立根节点
	Tree *head = new Tree();
	head->value=pre[0];
	head->left=head->right=NULL;
    
    //数组长度为1时,直接返回,因为没有子节点了
    if(length>1){
        //找到左右子树的分界点
        int index=0;
        while(index<length){
            if(pre[1]==back[index]){
                break;
            }
            ++index;
        }
        
        //判断是否是唯一子树
        if(index==length-2){
            //唯一子树需要判断为左子树还是右子树
            if(back[0]<head->val){
                head->left=Fun3(pre+1,back,index+1);
            }else{
                head->right=Fun3(pre+1, back, index+1);
            }
        }else{
            //左右子树均存在时,左边的为左子树,右边的为右子树
            head->left=Fun3(pre+1,back,index+1);
            head->right=Fun3(pre+index+2, back+index+1, length-index-2);
        }
    }
    
    return head;
}
发布了63 篇原创文章 · 获赞 73 · 访问量 7万+

猜你喜欢

转载自blog.csdn.net/jjwwwww/article/details/85848891
今日推荐