数据结构与算法——普通二叉树与真二叉树的重构实现(C++)

1.二叉树的重构

二叉树的重构就是基于二叉树的遍历结果对二叉树进行复原的一个过程。常用的方法包括三种:对于普通二叉树的先序遍历+中序遍历方法,后序遍历+中序遍历方法与对于真二叉树的先序遍历+后序遍历方法。

2 普通二叉树的重构

普通二叉树的重构方法包括两个:(1)前序遍历+中序遍历;(2)后序遍历+中序遍历。下面,依次研究实现这两种重构方法。

2.1 前序遍历+中序遍历

2.1.1 案例树的结构

在这里插入图片描述

2.1.2 实现过程

(1) 观察

前序遍历序列:
int pre []= { 1, 2, 4, 5, 9, 3, 7, 10, 11};
中序遍历序列:
int in [] = { 4, 2, 5, 9, 1, 3, 10, 7, 11};

  • 前序遍历的第一个总为根节点
  • 根据根节点在中序遍历序列中的位置可以将问题分而治之
(2)实现思路

重构函数:node* rebuild_1( int pre_lo, int pre_hi, int in_lo, int in_hi)
递归的要点:

  • 递归基: in_hi - in_lo < 2,即只剩一个节点的时候,返回当前节点
  • 函数体: 找到当前序列中根节点在in中的位置pos_root,以此获得左子树的节点规模num_ltree。通过这两个数据两个将当前序列分而治之。分成node1->lc = rebuild_1( pre_lo + 1,pre_lo + num_ltree + 1, in_lo, in_lo + num_ltree);node1->rc = rebuild_1( pre_lo + num_ltree + 1, pre_hi, in_lo + num_ltree + 1, in_hi); 由于是普通二叉树,需要考虑左子树与右子树存在与否
  • 返回值: 返回根节点,通过不断递归使得根节点成为一个有规模树的根节点
(3)实现

重构树,获取并比对其后序遍历序列。后序遍历序列均为4 9 5 2 10 11 7 3 1,重构成功。

#include <iostream>
#include <stdlib.h>
#include <stdio.h>
struct node{
	int data;
	node *lc,*rc;	
}; 
using namespace std;
//先序遍历与中序遍历序列
int pre []= { 1, 2, 4, 5,  9, 3, 7, 10, 11};
int in [] =  { 4, 2, 5, 9, 1, 3, 10, 7, 11};
//函数声明
node* rebuild_1(int pre_lo, int pre_hi, int in_lo, int in_hi);
void InTraverse(node* root);

int main(){
    //cout << 1 <<endl;
    node* r ;
    r = rebuild_1(0, 9, 0, 9);
	InTraverse(r);//后序遍历结果 4 9 5 2 10 11 7 3 1
	return 0;
}

node* rebuild_1(int pre_lo, int pre_hi, int in_lo, int in_hi){
    node* node1 = new node;
    
    node1->data = pre[pre_lo];
    //cout << "node_1 data: " << node1->data << endl;//测试

    node1->lc = nullptr;
    node1->rc = nullptr;

    //递归基
    if( in_hi - in_lo < 2) return node1;
    
    //函数体
    int num_ltree = 0;//当前左子树节点个数
    int pos_root;

    for( auto i : in){
        if( in[i] == pre[pre_lo]){
            pos_root = i;
            break;
        }
    }
    //找出左子树的长度
    num_ltree = pos_root - in_lo;
    cout << "num_ltree:" << num_ltree << endl;
    if( num_ltree )
        node1->lc = rebuild_1( pre_lo + 1,pre_lo + num_ltree + 1, in_lo, in_lo + num_ltree);
    if( pre_hi - (pre_lo + num_ltree + 1))
        node1->rc = rebuild_1( pre_lo + num_ltree + 1, pre_hi, in_lo + num_ltree + 1, in_hi); 
    //返回值
    return node1;
}
//遍历
void InTraverse(node* root){
	if(!root)return;
	InTraverse(root->lc);
	InTraverse(root->rc);
    cout << root->data << " " ;
}

2.2 后序遍历+中序遍历

2.2.1 案例树的结构

在这里插入图片描述

2.2.2 实现过程

(1)观察

后序遍历序列:
int pre []= { 4, 9, 5, 2, 10, 11, 7, 3, 1};
中序遍历序列:
int in [] = { 4, 2, 5, 9, 1, 3, 10, 7, 11};

  • 后序遍历的最后一位总为根节点
  • 根据根节点在中序遍历序列中的位置可以将问题分而治之
(2)实现思路

重构函数:node* rebuild_2( int post_lo, int post_hi, int in_lo, int in_hi)
递归的要点:

  • 递归基: in_hi - in_lo < 2,即只剩一个节点的时候,返回当前节点
  • 函数体: 找到当前序列中根节点在in中的位置pos_root,以此获得左子树的节点规模num_ltree。通过这两个数据两个将当前序列分而治之。分成node1->lc = rebuild_2( post_lo, post_lo + num_ltree, in_lo, in_lo + num_ltree); node1->rc = rebuild_2( post_lo + num_ltree , post_hi - 1, in_lo + num_ltree + 1, in_hi); 由于是普通二叉树,需要考虑左子树与右子树存在与否
  • 返回值: 返回根节点,通过不断递归使得根节点成为一个有规模树的根节点
(3)实现

重构树,获取并比对其前序遍历序列。前序遍历序列均为1 2 4 5 9 3 7 10 11,重构成功。

#include <iostream>
#include <stdlib.h>
#include <stdio.h>
struct node{
	int data;
	node *lc,*rc;	
}; 
using namespace std;
//后序遍历与中序遍历序列
int post []= { 4, 9, 5, 2, 10, 11, 7, 3, 1};
int in [] =  { 4, 2, 5, 9, 1, 3, 10, 7, 11};
//函数声明
node* rebuild_2(int post_lo, int post_hi, int in_lo, int in_hi);
void InTraverse(node* root);

int main(){
    //cout << 1 <<endl;
    node* r ;
    r = rebuild_2(0, 9, 0, 9);
	InTraverse(r);//前序遍历结果 1 2 4 5 9 3 7 10 11
	return 0;
}

node* rebuild_2(int post_lo, int post_hi, int in_lo, int in_hi){

    node* node1 = new node;
    node1->data = post[post_hi - 1] ;
    cout << "node_1 data: " << node1->data << endl;//测试

    node1->lc = nullptr;
    node1->rc = nullptr;

    //递归基
    if( in_hi - in_lo < 2) return node1;
    
    //函数体
    int num_ltree = 0;//当前左子树节点个数
    int pos_root;

    for( auto i : in){
        if( in[i] == post[post_hi - 1]){
            pos_root = i;
            break;
        }
    }
    //找出左子树的长度
    num_ltree = pos_root - in_lo;
    cout << "num_ltree:" << num_ltree << endl;
    if( num_ltree )
        node1->lc = rebuild_2( post_lo, post_lo + num_ltree, in_lo, in_lo + num_ltree);
    if( post_hi - 1 - (post_lo + num_ltree ))
        node1->rc = rebuild_2( post_lo + num_ltree , post_hi - 1, in_lo + num_ltree + 1, in_hi); 
    //返回值
    return node1;
}
//遍历
void InTraverse(node* root){
	if(!root)return;
    cout << root->data << " " ;
	InTraverse(root->lc);
	InTraverse(root->rc);

}

3 真二叉树的重构

3.1 真二叉树

真二叉树是各个节点的子树个数均为偶数的二叉树。从本节的测试用例众可以领悟到真二叉树的真谛。
在这里插入图片描述

3.2 先序遍历+后序遍历实现过程

前序遍历序列:
int pre []= { 1, 2, 4, 5, 8, 9, 3, 6, 7, 10, 11}
后序遍历序列:
int post[] = { 4, 8, 9, 5, 2, 6, 10, 11, 7, 3, 1}

(1)观察

根据已有的遍历序列,不难发现:

  • 前序遍历的第一个总为当前的根节点;根节点之后一个为根节点的左子树的根节点;左子树与右子树以右子树根节点为界分布
  • 后序遍历中的最后一个为当前的根节点;左子树与右子树以左子树根节点为界分布
  • 问题的关键在于找到分割点,分而治之

(2)实现思路

二叉树的问题使用递归的方法,代码更加简洁。构建pre_lo, pre_hi, post_lo, post_hi四个指针位置,通过调整这四个值来实现分而治之。
重构函数:BinNodePosi(int) rebuild( int pre_lo, int pre_hi, int post_lo, int post_hi)
递归的要点:

  • 递归基: post_hi - post_lo < 2,即只剩一个节点的时候,返回当前节点
  • 函数体: 找到当前序列中左子树在post中的位置pos_lroot,以及左子树的节点规模num_ltree,通过这两个将当前序列分而治之。分成node->lc = rebuild( pre_lo + 1,pre_lo + num_ltree + 1, post_lo, pos_lroot+1); node->rc = rebuild( pre_lo + num_ltree + 1, pre_hi, pos_lroot+1, post_hi - 1);
  • 返回值: 返回根节点,通过不断递归使得根节点成为一个有规模树的根节点

前序遍历序列:
int pre []= { 1, 2, 4, 5, 8, 9, 3, 6, 7, 10, 11}
后序遍历序列:
int post [] = { 4, 8, 9, 5, 2, 6, 10, 11, 7, 3, 1}

(3)代码实现

重构真二叉树,获取并比对其中序遍历序列。中序遍历序列均为4 2 8 5 9 1 6 3 10 7 11 ,重构成功。

#include <iostream>
#include <stdlib.h>
#include <stdio.h>
struct node{
	int data;
	node *lc,*rc;	
}; 
using namespace std;
//先序遍历与后序遍历序列
int pre []= { 1, 2, 4, 5, 8, 9, 3, 6, 7, 10, 11};
int post [] =  { 4, 8, 9, 5, 2, 6, 10, 11, 7, 3, 1};
//函数声明
node* rebuild(int pre_lo, int pre_hi, int post_lo, int post_hi);
void InTraverse(node* root);

int main(){
    node* r ;
    r = rebuild(0,11,0,11);
	InTraverse(r);//中序遍历的结果为 4 2 8 5 9 1 6 3 10 7 11 
	return 0;
}

node* rebuild(int pre_lo, int pre_hi, int post_lo, int post_hi){
    node* node1 = new node;
    node1->data = post[post_hi - 1];
    //cout << node1->data << endl;//测试

    node1->lc = nullptr;
    node1->rc = nullptr;

    //递归基
    if( post_hi - post_lo < 2) return node1;
    
    //函数体
    int num_ltree = 0;//当前左子树节点个数
    int pos_lroot = 0;//当前左子树根在post中的秩
    for( auto i : post){
        if ( post[i] == pre[pre_lo + 1]){
            pos_lroot = i;
            break;
        }
    }
    num_ltree=pos_lroot-post_lo+1;

    node1->lc = rebuild( pre_lo + 1,pre_lo + num_ltree + 1, post_lo, pos_lroot+1);
    node1->rc = rebuild( pre_lo + num_ltree + 1, pre_hi, pos_lroot+1, post_hi - 1); 
    //返回值
    return node1;
}
//遍历
void InTraverse(node* root){
	if(!root)return;
	InTraverse(root->lc);
	cout << root->data << " ";
	InTraverse(root->rc);
}

运行之后输出中序遍历序列:
4 2 8 5 9 1 6 3 10 7 11
通过验证,得知重构真二叉树成功。

(4)注意

使用BinNodePosi(T) 来初始化新节点,会导致数据覆盖,必须new一个新的出来,比如使用node* node1 = new node。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值