【二叉树】重建二叉树,中序遍历下一结点

版权声明:本文为博主原创学习笔记,如需转载请注明来源。 https://blog.csdn.net/SHU15121856/article/details/82352354

树问题

一般都是二叉树问题,遍历方式有前序遍历(中左右)、中序遍历(左中右)、后序遍历(左右中)、宽度优先遍历。二叉树还有很多变种,如二查搜索树、堆、红黑树等。

二叉树的实现

BinaryTree.h
//二叉树结点结构体 
struct BinaryTreeNode 
{
    int                    m_nValue;//结点的值 
    BinaryTreeNode*        m_pLeft;//左子树地址 
    BinaryTreeNode*        m_pRight;//右子树地址 
};

//传入结点的值,创建一个二叉树结点 
BinaryTreeNode* CreateBinaryTreeNode(int value);
//连接结点,给出父节点和左右子树结点 
void ConnectTreeNodes(BinaryTreeNode* pParent, BinaryTreeNode* pLeft, BinaryTreeNode* pRight);
//输出指定的二叉树结点 
void PrintTreeNode(const BinaryTreeNode* pNode);
//给出根节点,输出整棵二叉树 
void PrintTree(const BinaryTreeNode* pRoot);
//给出根节点,删除整棵二叉树 
void DestroyTree(BinaryTreeNode* pRoot);
BinaryTree.cpp
#include <cstdio>
#include "BinaryTree.h"

//传入结点的值,创建一个二叉树结点
BinaryTreeNode* CreateBinaryTreeNode(int value) {
    //创建二叉树结点
    BinaryTreeNode* pNode = new BinaryTreeNode();
    //设置值,左右子树默认为空
    pNode->m_nValue = value;
    pNode->m_pLeft = nullptr;
    pNode->m_pRight = nullptr;
    return pNode;
}

//连接结点,给出父节点和左右子树结点
void ConnectTreeNodes(BinaryTreeNode* pParent, BinaryTreeNode* pLeft, BinaryTreeNode* pRight) {
    if(pParent != nullptr) { //校验父节点必须存在
        //使父节点指向左右子树即可
        pParent->m_pLeft = pLeft;
        pParent->m_pRight = pRight;
    }
}

//输出指定的二叉树结点
void PrintTreeNode(const BinaryTreeNode* pNode) {
    if(pNode != nullptr) { //校验该结点非空
        printf("value of this node is: %d\n", pNode->m_nValue);

        if(pNode->m_pLeft != nullptr)//左子结点
            printf("value of its left child is: %d.\n", pNode->m_pLeft->m_nValue);
        else
            printf("left child is nullptr.\n");

        if(pNode->m_pRight != nullptr)//右子结点
            printf("value of its right child is: %d.\n", pNode->m_pRight->m_nValue);
        else
            printf("right child is nullptr.\n");
    } else {
        printf("this node is nullptr.\n");
    }
    printf("\n");
}

//给出根节点,输出整棵二叉树
void PrintTree(const BinaryTreeNode* pRoot) {
    PrintTreeNode(pRoot);//输出根结点信息

    if(pRoot != nullptr) {//校验这个结点非空
        //递归地调用其自身,输出左右子树的信息
        if(pRoot->m_pLeft != nullptr)
            PrintTree(pRoot->m_pLeft);

        if(pRoot->m_pRight != nullptr)
            PrintTree(pRoot->m_pRight);
    }
    //这种输出方式不太好看,以后有空再找更人性化的输出方式吧
}

//给出根节点,删除整棵二叉树
void DestroyTree(BinaryTreeNode* pRoot) {
    if(pRoot != nullptr) {//校验这个结点非空
        //将其左右子树的地址先分别记录下来 
        BinaryTreeNode* pLeft = pRoot->m_pLeft;
        BinaryTreeNode* pRight = pRoot->m_pRight;
        //然后删除这个根节点 
        delete pRoot;
        pRoot = nullptr;
        //然后调用这个函数本身,递归地删除左右子树 
        DestroyTree(pLeft);
        DestroyTree(pRight);
    }
}

面试题7:重建二叉树

输入某二叉树前序和中序遍历的结果,重建二叉树。

在前序遍历中第一个就是根结点,然后到中序遍历中找到这个根节点就能分出左右子树。

#include "../Utilities/BinaryTree.h"
#include<bits/stdc++.h>
#include<exception>
using namespace std;

// 参数:
//        startPreorder:     当前前序遍历数组开头地址
//        endPreorder:       当前前序遍历数组结尾地址
//        startInorder:      当前中序遍历数组开头地址
//        endInorder:        当前中序遍历数组结尾地址
// 返回值:
//        以输入的前序和中序遍历序列所构造的二叉(子)树的根节点地址
BinaryTreeNode* ConstructCore
(
    int* startPreorder, int* endPreorder,
    int* startInorder, int* endInorder
) {
    //根据前序遍历序列的第一个数字创建根结点
    int rootValue = startPreorder[0];
    BinaryTreeNode* root = new BinaryTreeNode();
    root->m_nValue = rootValue;
    root->m_pLeft = root->m_pRight = nullptr;//左右子树初始化为空

    //[递归出口]如果前序遍历序列开头和结尾地址一样,即只有这一个值
    if(startPreorder == endPreorder) {
        //这时去看中序遍历序列,也必须是开头和结尾地址一样,即只有一个值
        //且这个值和前序遍历序列的值相等,都是根节点的值
        if(startInorder == endInorder && *startPreorder == *startInorder)
            return root;//这时构建的二叉树就只有这一个根节点,返回
        else//否则,说明这两个序列是不对应的,无法正常构造二叉树
            throw exception();
    }

    //要寻找中序遍历序列中根结点的指针位置,从中序遍历序列开头处开始
    int* rootInorder = startInorder;
    //当没有超过中序遍历序列末尾结点,且不等于前序遍历序列第一个数字时
    while(rootInorder <= endInorder && *rootInorder != rootValue)
        ++ rootInorder;//一直往下走

    //走完以后,判断一下,如果当前走到了末尾,并且还是没有这个数字
    if(rootInorder == endInorder && *rootInorder != rootValue)
        throw exception();//说明这两个序列是不匹配的,出错

    //至此,已经找到了中序遍历序列中根结点的指针位置

    //这时左子树的长度就看中序遍历序列根节点左边有几个数(左子树结点数)
    int leftLength = rootInorder - startInorder;
    //用这个左子树结点数,在前序遍历序列找到左子树序列的末尾点
    int* leftPreorderEnd = startPreorder + leftLength;
    //那么它的下一个点也就是右子树序列的起始点!

    //如果左子树结点不为0,即存在左子树
    if(leftLength > 0) {
        //递归调用这个函数来构建左子树
        //前序子序列:起始点只要去掉根结点,终止点刚刚计算出来的
        //中序子序列:起始点还是大中序序列的起始点,终止点要从根结点往前一步
        root->m_pLeft = ConstructCore(startPreorder + 1, leftPreorderEnd,
                                      startInorder, rootInorder - 1);
        //构造之后挂成本结点左子树
    }
    //如果左子树结点数没有占完除了开头根节点后剩下的所有结点,即存在右子树
    if(leftLength < endPreorder - startPreorder) {
        //递归调用这个函数来构建右子树
        //前序子序列:起始点刚刚计算出来的左子树结束点+1,终止点使用大的终止点
        //中序子序列:起始点要从根结点往后一步,终止点使用大的终止点
        root->m_pRight = ConstructCore(leftPreorderEnd + 1, endPreorder,
                                       rootInorder + 1, endInorder);
        //构造之后挂成本结点右子树
    }

    return root;//返回构造好的结点给上层调用者
}


// 参数:
//        preorder:     前序遍历序列数组
//        inorder:      中序遍历序列数组
//        length:       数组的长度,两个序列一样
// 返回值:
//        以输入的前序和中序遍历序列所构造的二叉树的根节点地址
BinaryTreeNode* Construct(int* preorder, int* inorder, int length) {
    //输入合法性校验
    if(preorder == nullptr || inorder == nullptr || length <= 0)
        return nullptr;

    //调用递归的构造结点的函数,传入最大的序列前后值,构造成整棵二叉树
    return ConstructCore(preorder, preorder + length - 1,
                         inorder, inorder + length - 1);
}

//              1
//           /     \
//          2       3  
//         /       / \
//        4       5   6
//         \         /
//          7       8
int main() {
    const int length = 8;//序列长度 
    int preorder[length] = {1, 2, 4, 7, 3, 5, 6, 8};//前序 
    int inorder[length] = {4, 7, 2, 1, 5, 3, 8, 6};//中序

    try {
        //使用前序和中序构造整棵二叉树,获得根节点 
        BinaryTreeNode* root = Construct(preorder, inorder, length); 
        PrintTree(root);//输出 
        DestroyTree(root);//销毁 
    } catch(exception& exception) {
        printf("Invalid Input.\n");
    }
}

面试题8:二叉树的下一个结点

给定一棵二叉树和其中的一个结点,找出中序遍历顺序的下一个结点。树中的结点除了有两个分别指向左右子结点的指针以外,还有一个指向父结点的指针。

这题的树需要有个指向父节点的指针,前面的那个略做修改就可以用了:

#include <stdio.h>
//带父节点指针的二叉树 
struct BinaryTreeNode {
    int                    m_nValue;//结点值 
    BinaryTreeNode*        m_pLeft;//左子树 
    BinaryTreeNode*        m_pRight;//右子树 
    BinaryTreeNode*        m_pParent;//父结点 
};

//传入结点的值,创建一个二叉树结点
BinaryTreeNode* CreateBinaryTreeNode(int value) {
    //创建二叉树结点
    BinaryTreeNode* pNode = new BinaryTreeNode();
    //设置值,左右子树,父结点皆默认为空
    pNode->m_nValue = value;
    pNode->m_pLeft = nullptr;
    pNode->m_pRight = nullptr;
    pNode->m_pParent = nullptr;
    return pNode;
}

//连接结点,给出父节点和左右子树结点
void ConnectTreeNodes(BinaryTreeNode* pParent, BinaryTreeNode* pLeft, BinaryTreeNode* pRight) {
    if(pParent != nullptr) {//校验父节点必须存在
        //使父节点指向左右子树
        pParent->m_pLeft = pLeft;
        pParent->m_pRight = pRight;

        //如果左右子树结点非空,那么它们也要指向父结点 
        if(pLeft != nullptr)
            pLeft->m_pParent = pParent;
        if(pRight != nullptr)
            pRight->m_pParent = pParent;
    }
}

//输出指定的二叉树结点 
void PrintTreeNode(BinaryTreeNode* pNode) {
    //略...这部分和前面的一样,没区别
}

//给出根节点,输出整棵二叉树
void PrintTree(BinaryTreeNode* pRoot) {
    //略...这部分和前面的一样,没区别
}

//给出根节点,删除整棵二叉树
void DestroyTree(BinaryTreeNode* pRoot) {
    //略...这部分和前面的一样,没区别
}

因为中序遍历是左中右。如果当前结点有右子树,那么当前结点相对而言就是”中”,下一结点就是右子树的最左叶子。

如果当前结点没有右子树,那么往上看父节点。如果当前结点是”左”,那么下一结点就是父节点;如果当前结点是”右”,那么可以想象包括根节点在内的这棵二叉子树已经遍历完了,要向上找”中”,即一直向上找祖先结点,如果某个祖先结点是其父节点的左结点,说明那个父结点所在的左子树遍历完了:
这里写图片描述
所以那个祖先节点(图上绿色),就是输入结点(图上蓝色)在中序遍历时的下一结点。

#include<bits/stdc++.h>
using namespace std;

//输入二叉树上的一个结点,返回其中序遍历的下一结点
BinaryTreeNode* GetNext(BinaryTreeNode* pNode) {
    if(pNode == nullptr)//非空校验
        return nullptr;

    BinaryTreeNode* pNext = nullptr;//要返回的下一结点
    //[1]如果存在右子树
    if(pNode->m_pRight != nullptr) {
        BinaryTreeNode* pRight = pNode->m_pRight;//从右子树根开始
        //不停向左子树走,直到走到叶子
        while(pRight->m_pLeft != nullptr)
            pRight = pRight->m_pLeft;
        pNext = pRight;//这个右子树的最左椰子就是所求
    }
    //[2]如果不存在右子树,但当前结点不是根节点
    else if(pNode->m_pParent != nullptr) {
        //设置两个移动指针
        BinaryTreeNode* pCurrent = pNode;//一个从当前结点开始
        BinaryTreeNode* pParent = pNode->m_pParent;//一个从其父结点开始
        //上面的结点没超出总根,且下面的结点始终是上面结点的右子树时
        while(pParent != nullptr && pCurrent == pParent->m_pRight) {
            //两个结点需要一直向上走
            pCurrent = pParent;
            pParent = pParent->m_pParent;
        }
        //至此,上面的结点超出总根(表示已经没有中序遍历的下一结点了)
        //或者是下面的结点成为了上面结点的左子树
        pNext = pParent;//不论哪种情形,都是上面的结点为中序遍历的下一结点(或空)
    }

    return pNext;
}

//            8
//        6      10
//       5 7    9  11
int main() {
    //建立结点 
    BinaryTreeNode* pNode8 = CreateBinaryTreeNode(8);
    BinaryTreeNode* pNode6 = CreateBinaryTreeNode(6);
    BinaryTreeNode* pNode10 = CreateBinaryTreeNode(10);
    BinaryTreeNode* pNode5 = CreateBinaryTreeNode(5);
    BinaryTreeNode* pNode7 = CreateBinaryTreeNode(7);
    BinaryTreeNode* pNode9 = CreateBinaryTreeNode(9);
    BinaryTreeNode* pNode11 = CreateBinaryTreeNode(11);

    //连接成树 
    ConnectTreeNodes(pNode8, pNode6, pNode10);
    ConnectTreeNodes(pNode6, pNode5, pNode7);
    ConnectTreeNodes(pNode10, pNode9, pNode11);

    //中序遍历中7的后面应是8 
    cout<<GetNext(pNode7)->m_nValue<<endl;

    //销毁二叉树 
    DestroyTree(pNode8);
    return 0;
}

猜你喜欢

转载自blog.csdn.net/SHU15121856/article/details/82352354