【数据结构与算法(五)】——树

时间:2021-03-12 12:59:12

第五天,突然又迷茫了!难受的一天

--->想到递归?

1、树的逻辑:除根节点之外的每个节点只有一个父节点,根节点没有父节点;除叶节点之外所有节点都有一个或多个子节点,叶节点没有子节点。树结构会涉及大量的指针。一种特殊的树:二叉树。在二叉树中每个节点最多只能有两个子节点。
2、在二叉树中最重要的是遍历
前序遍历:根节点–左子节点–右子节点
中序遍历:左子节点–根节点–右子节点
后序遍历:左子节点–右子节点–根节点 ==》每种遍历都有循环和递归两种实现方法
3、宽度优先遍历:先访问树的第一层节点,再访问树的第二层节点……一直到访问到最下面一层的节点。在同一层中,访问的顺序是从左到右一次访问。
4、二叉搜索树/二叉查找树:排好序的、有规律的树。在二叉搜索树中,左子节点总是小于或等于根节点,而右子节点总是大于或等于根节点。可以在O(log n)的时间内根据数值在二叉搜索树中找到一个节点【怎么做??】
5、堆:分为最大堆和最小堆。在最大堆中根节点的值最大,在最小堆中根节点的值最小。有很多需要快速找到最大值和最小值的问题都可以用堆来解决。
6、红黑树:(几乎所有的面经中都有提到红黑树)把树中的节点定义为红、黑两种颜色,并通过规则确保从根节点到叶节点的最长路径的长度不超过最短路径的两倍。在C++的STL中,set、multiset、map、multimap等数据结构都是基于红黑树实现的。【这个红黑树真的很重要】

题目

重建二叉树

输入某二叉树的前序遍历和中序遍历的结果,请重建该二叉树,假设输入的前序遍历和中序遍历的结果中都不含重复的数字。例如,输入前序遍历序列{1,2,4,7,3,5,6,8}和中序遍历序列{4,7,2,1,5,3,8,6},则重建二叉树并输出它的头节点。

思路:

1、在二叉树的前序遍历序列中,第一个数字总是树的根节点的值。但在中序遍历中,根节点的值在序列的中间,左子树的节点的值位于根节点的左边。所以这样一来,我们就要先确定根节点的值
2、前序遍历的第一个数字就是根节点,这样就知道根节点的值啦,之后就扫描中序遍历的序列,确定根节点在序列中的位置。根据中序遍历中根节点的位置,又可以知道左子树和右子树中分别有哪些数,记有n个数。然后又回到前序遍历序列了,根节点的后面n个数是左子树的数,之后又可以根据这n个数的排序确定左子树的根节点,同样用同样的方法可以找到右子树的根节点。这样一来就可以分别确定左右子树的前序遍历序列和中序遍历序列了。接下来就是递归的事了。

struct BinaryTreeNode
{
    int value;
    BinaryTreeNode* leftNode;
    BinaryTreeNode* rightNode;
};

//核心代码
BinaryTreeNode* ConstructCore(int* firstPreorder, int* lastPreorder,
    int* firstMidorder, int* lastMidorder)
{
    //在前序遍历序列中得到根节点
    int rootValue = firstPreorder[0];
    BinaryTreeNode* root = new BinaryTreeNode();
    root->value = rootValue;
    root->leftNode = root->rightNode = nullptr;
    //只剩一个节点,子树就返回一个节点就可以了,不需要进行后面的操作
    if (firstPreorder==lastPreorder)
    {   
        if (firstMidorder == lastMidorder && *firstPreorder == *firstMidorder)
            return root;
        else
            throw std::exception("Invalid order");
    }
    //在中序遍历中找到根节点位置,从而区分左子树和右子树
    int* rootMidorder = firstMidorder;
    //找到根节点的位置
    while (rootMidorder <= lastMidorder && *rootMidorder != rootValue)
        ++rootMidorder;
    if (rootMidorder == lastMidorder && *rootMidorder != rootValue)
        throw std::exception("Invalid input");
    //左子树的个数
    int leftLen = rootMidorder - firstMidorder;
    int *leftPreorderEnd = firstPreorder + leftLen;
    if (leftLen > 0)
        //构建左子树
        root->leftNode = ConstructCore(firstPreorder + 1, leftPreorderEnd, firstMidorder, rootMidorder - 1);
    if (leftLen < lastPreorder - firstPreorder)//说明有存在右子树
        //构建右子树
        root->rightNode = ConstructCore(leftPreorderEnd + 1, lastPreorder, rootMidorder + 1, lastMidorder);

    return root;
}

测试用例
1、考虑普通二叉树(完全二叉树;不完全二叉树)
2、特殊二叉树(所有节点都没有右子节点的二叉树;所有节点都没有左子节点的二叉树;只有一个节点的二叉树)
3、特殊输入:二叉树的根节点的指针是nullptr;输入的前序遍历序列和中序遍历序列不匹配

二叉树的下一个节点

给定一棵二叉树和其中一个节点,如何找出中序遍历序列的下一个节点?树的节点除了有两个分别指向左、右子节点的指针,还有一个指向父节点的指针。

思路:

1、如果一个节点有右子树,那么它的下一个节点就是它的右子树最左的子节点。也就是说,从右子节点出发一直沿着左子节点的指针,就能找到它的下一个节点,注意!必须是右子树
2、如果一个节点没有右子树,并且该节点是其当前节点的左子节点,那么它的下一个节点就是它的父节点
3、如果一个节点没有右子树,并且其自己不是它的父节点的左子节点,那么它的下一个节点需要沿着它的父节点找到一个节点,这个节点必须是它的父节点的左子节点。
以上三种情况分别考虑并进行相应的操作。

//拥有父节点的树
struct BinaryTreeParentNode
{
    int value;
    BinaryTreeParentNode* leftNode;
    BinaryTreeParentNode* rightNode;
    BinaryTreeParentNode* parentNode;
};

//从二叉树中找出一个节点的下一个节点
BinaryTreeParentNode* findNext(BinaryTreeParentNode* pNode)
{
    if (pNode == nullptr)
        return;

    BinaryTreeParentNode* pNext;
    //存在右节点==>下一个节点是其右子树的最左边的一个节点,顺着左节点一直走
    if (pNode->rightNode != nullptr) {
        BinaryTreeParentNode* rightNode = pNode->rightNode;
        while (rightNode->leftNode != nullptr)
            rightNode = rightNode->leftNode;
        pNext = rightNode;
    }//不存在右节点,但是该节点是其父节点的左节点==>下一个节点就是它的父节点
    //或者该节点不是其父节点的左节点==>一直沿着父节点网上找,直到找到当前位置的节点使其父节点的左节点 ,否则下一个节点就是nullptr
    //其实这两种做法总结起来就是找到当前节点使其父节点的左节点就可以了,也不用分类,就是一步和多步的区别而已
    else if (pNode->parentNode != nullptr) {
        BinaryTreeParentNode* pParent = pNode->parentNode;
        BinaryTreeParentNode* pCurrent = pNode;
        while (pParent != nullptr&&pCurrent == pParent->rightNode) {
            pCurrent = pParent;
            pParent = pParent->parentNode;
        }
        pNext = pParent;
    }
    return pNext;
}

测试用例:
1、普通二叉树(完全二叉树;不完全二叉树)
2、特殊二叉树:
①所有节点都没有右子节点的二叉树(rightNode==nullptr)==>也就是说所有节点都是其父节点的左子节点,那么需要走到第三个if条件,之后第一个while循环就已经符合退出循环的条件了,所以直接就是它的父节点,函数结束
②只有一个节点的二叉树(rightNode==nullptr && pParent==nullptr)==>三个if条件都不能进入,直接返回pNext(=nullptr)
③所有节点都没有左子节点的二叉树==>也就是说所有节点都是存在右子节点,进入第二个if条件,之后到达while循环的时候进不去,直接就pNext=它的右子节点
④二叉树的根节点指针为nullptr,第一个if就符合了,进入之后就return了
3、不同位置的节点的下一个节点,下一个节点为:
①当前节点的右子节点
②右子树的最左子节点
③父节点
④跨层父节点
⑤没有下一个节点

对于程序,要多注释,之后看的时候才会清楚一点,不会跟第一次看一样,重新开始,那博客记录的意义就不大了