现在的位置: 首页 > 综合 > 正文

根据二叉树的遍历结果重建二叉树

2019年09月30日 ⁄ 综合 ⁄ 共 2867字 ⁄ 字号 评论关闭

问题来源:《编程之美》3.9 重建二叉树

给定一棵二叉树,假设每个节点都用唯一的字符来表示,具体结构如下:

struct Node
{
    struct Node *pLeft;
    struct Node *pRight;
    char chValue;
};

假设已经有了前序遍历和中序遍历的结果,希望通过一个算法重建这棵树。

给定函数的定义如下:

void ReBuild(char *pPreOrder, char *pInOrder, int nTreeLen, Node **pRoot)


参数:

pPreOrder: 以NULL为结尾的前序遍历结果的字符串数组

pInOrder: 以NULL为结尾的中序遍历结果的字符串数组

nTreeLen: 遍历结果字符串数组的大小

pRoot: 返回Node **类型,根据前序和中序遍历结果重新构建树的根节点。


前序遍历的每一个节点,都是当前子树的根节点,同时,以对应的节点为边界,就会把中序遍历的结果分为左子树和右子树。

例如:

前序遍历:a b d c e f

中序遍历: d b a e c f

前序遍历第一个节点a, 在中序遍历的字符串中找到,然后将中序遍历的字符串分为两部分,前一部分 db 刚好是以a为根节点的左子树的中序遍历结果,后一部分ecf是以a为根节点的右子树的中序遍历结果,同时可以计算出左子树和右子树的长度,这样就可以递归重建出二叉树。

程序描述如下:

void ReBuild(char *pPreOrder, char *pInOrder, int nTreeLen, Node **pRoot)
{
    if(pPreOrder == NULL || pInOrder == NULL) {
        return;
    }

    Node *pTemp = new Node;
    pTemp->chValue = *pPreOrder;
    pTemp->pLeft = NULL;
    pTemp->pRight = NULL;

    //如果根节点为空,则把当前节点复制到根节点
    if(*pRoot == NULL)
        *pRoot = pTemp;

    //如果当前树长度为1,那么已经是最后一个节点了
    if(nTreeLen == 1)
        return;

    //寻找子树长度
    char *pOrgInOrder = pInOrder;
    char *pLeftEnd = pInOrder;
    int nTempLen = 0;

    //找到左子树的结尾
    while(*pPreOrder != *pLeftEnd) {
        if(pPreOrder == NULL || pLeftEnd == NULL) {
            return; 
        }

        nTempLen++;

        //记录临时长度,以免溢出
        if(nTempLen > nTreeLen)
            break;

        pLeftEnd++;
    }

    //寻找左子树长度
    int nLeftLen = 0;
    nLeftLen = (int)(pLeftEnd - pOrgInOrder);

    //寻找右子树长度
    int nRightLen = 0;
    nRightLen = nTreeLen - nLeftLen - 1;

    //重建左子树
    if(nLeftLen > 0)
        ReBuild(pPreOrder + 1, pInOrder, nLeftLen, &((*pRoot)->pLeft));
    
    //重建右子树
    if(nRightLen > 0)
        ReBuild(pPreOrder + nLeftLen + 1, pInOrder + nLeftLen + 1, nRightLen, &((*pRoot)->pRight));
}

示例调试代码如下:

int main()
{
    char szPreOrder[] = {'a', 'b', 'd', 'y', 'c', 'e', 'f'};
    char szInOrder[] = {'y', 'd', 'b', 'a', 'e', 'c', 'f'};

    Node *pRoot = NULL;
    ReBuild(szPreOrder, szInOrder, sizeof(szPreOrder)/sizeof(char), &pRoot);

    printf("\n");
}

从上面的测试代码来看,个人认为使用

if(pPreOrder == NULL || pInOrder == NULL) {
        return;
    }

来检查边界条件不太靠谱,因为最后我们执行pPreOrder + 1 时,指针后移之后所指向的位置有可能会是野指针,也就是不为NULL,下面是我个人所写代码,使用左右子树的长度才终止递归,经测试,在前序遍历和中序遍历都正确的情况下,重建的二叉树也是正确的,有不同意见欢迎指点。

#include <stdlib.h>
#include <stdio.h>

struct Node
{
    struct Node *pLeft;
    struct Node *pRight;
    char chValue;
};

//以前序遍历的方式生成二叉树
void rebuild(char *pPreOrder, char *pInOrder, int nTreeLen, struct Node **pRoot)
{
    if(nTreeLen == 0) {
        (*pRoot) = NULL;
        return;
    }
    else {
        *pRoot = (struct Node *)malloc(sizeof(struct Node)); //为新的节点分配内存

        int leftLen;
        for(leftLen = 0; leftLen < nTreeLen; ++leftLen) {      //在中序遍历结果中寻找根节点的位置,根节点将其左子树和右子树遍历结果分开
            if(pInOrder[leftLen] == pPreOrder[0])
                break;
        }

        if(*pRoot) {
            (*pRoot)->chValue = pPreOrder[0];
            rebuild(pPreOrder+1, pInOrder, leftLen, &(*pRoot)->pLeft);     //递归建立左子树,注意遍历结果指针的移动及长度的变化
            rebuild(pPreOrder+leftLen+1, pInOrder+leftLen+1, nTreeLen-leftLen-1, &(*pRoot)->pRight);  //递归建立右子树
        }
        
    }
}

//打印二叉树,测试程序的正确性
void printList(struct Node *pRoot)
{
    if(pRoot != NULL) {
        //printf("%c", (pRoot)->chValue);
        printList((pRoot)->pLeft);
        printf("%c", (pRoot)->chValue);
        printList((pRoot)->pRight);
        //printf("%c", (pRoot)->chValue);
    }
}

int main()
{
    char szPreOrder[] = {'a', 'b', 'd', 'y', 'c', 'e', 'f'};
    char szInOrder[] = {'y', 'd', 'b', 'a', 'e', 'c', 'f'};

    struct Node *pRoot = NULL;
    rebuild(szPreOrder, szInOrder, sizeof(szPreOrder)/sizeof(char), &pRoot);
 
    printList(pRoot);
    printf("\n");
}

我的微博:http://weibo.com/caryaliu

抱歉!评论已关闭.