剑指offer学习笔记 树

树的宽度(广度)优先遍历:先访问树的第一层节点,再访问第二层节点,直到最后一层。同一层节点中,从左到右依次访问。

二叉搜索树中,左子节点总是小于等于根节点,右子节点总是大于等于根节点。我们可以平均在O(logn)的时间内根据数值在二叉搜索树中找到一个节点。

二叉树的特例有堆和红黑树。堆分最大堆和最小堆,最大(小)堆中根节点的值最大(小),很多需要快速找到最值的问题可以用堆来解决。红黑树是把树中的节点定义为红、黑两种颜色,并通过规则确保从根节点到叶节点的最长路径的长度不超过最短路径长度的两倍。STL中,set、multiset、map、multimap等数据结构都是基于红黑树实现的。

面试题7:重建二叉树。输入某二叉树的前序遍历和中序遍历的结果,请重建该二叉树,输出头结点。二叉树的节点定义如下:

struct BinaryTreeNode{
    int m_nValue;
    BinaryTreeNode *m_pLeft;
    BinaryTreeNode *m_pRight;
};

在二叉树的前序遍历中,第一个数字总是树的根节点的值。但在中序遍历序列中,根节点的值在序列中间,左子树的节点的值位于根节点的值的左边,而右子树的节点的值位于根节点的值的右边。这样我们就可以找到左右子树的前序遍历序列和中序遍历序列,我们可以用递归完成剩下的部分:

#include <iostream>
using namespace std;

struct BinaryTreeNode {
    int m_nValue;
    BinaryTreeNode* m_pLeft;
    BinaryTreeNode* m_pRight;
};


BinaryTreeNode* ConstructCore(int* startPreorder, int* endPreorder, int* startInorder, int* endInorder) {
    BinaryTreeNode* root = new BinaryTreeNode();
    root->m_nValue = *startPreorder;
    root->m_pLeft = root->m_pRight = nullptr;

    if (startPreorder == endPreorder) {    //当递归到前序遍历只含一个元素时
        if (startInorder == endInorder && *startPreorder == *startInorder) {    //此时若中序遍历也只含一个元素并且这个元素值与前序遍历的元素值相同时,递归到底成功返回
            return root;
        }
        else {    //否则当前序和中序遍历的个数不相等(前序遍历只含一个元素但中序遍历有多个元素)或值不相等(前序遍历和中序遍历元素数都为1但这两个值不等)时
            throw exception("Invalid input.");    //说明输入的前序和中序遍历不匹配
        }
    }

    int* rootInorder = startInorder;
    while (rootInorder < endInorder && *rootInorder != *startPreorder) {    //遍历中序序列找到根节点
        ++rootInorder;
    }

    if (rootInorder == endInorder && *rootInorder != *startPreorder) {    //若以上循环结束仍未找到根节点,说明输入有误
        throw exception("Invalid input");
    }

    int leftLength = rootInorder - startInorder;    //左子树长度
    int* leftPreorderEnd = startPreorder + leftLength;    //左子树先序遍历尾边界
    if (leftLength > 0) {    //当左子树仍存在时
        root->m_pLeft = ConstructCore(startPreorder + 1, leftPreorderEnd, startInorder, rootInorder - 1);    //继续递归左子树
    }
    if (leftLength < endPreorder - startPreorder) {    //左子树长度小于当前遍历的树节点数-1(去掉根节点)时,说明存在右子树
        root->m_pRight = ConstructCore(startPreorder + leftLength + 1, endPreorder, rootInorder + 1, endInorder);    //继续递归右子树
    }

    return root;
}

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);
}

void PreorderPrint(BinaryTreeNode* root) {    //先序遍历打印树验证结果
    if (root == nullptr) {
        return;
    }
    cout << root->m_nValue << endl;
    PreorderPrint(root->m_pLeft);
    PreorderPrint(root->m_pRight);
    return;
}

int main() {
    int preorder[] = {1,2,4,7,3,5,6,8};
    int inorder[] = { 4,7,2,1,5,3,8,6 };
    BinaryTreeNode* root = Construct(preorder, inorder, 8);
    PreorderPrint(root);
	return 0;
}

在剑指offer原书中,这句代码有误:

    while (rootInorder <= endInorder && rootInorder != startPreorder) {    //遍历中序序列找到根节点
        ++rootInorder;
    }
    if (rootInorder == endInorder && *rootInorder != *startPreorder) {    //若以上循环结束仍未找到根节点,说明输入有误
        throw exception("Invalid input");
    }

错误在于,当while循环条件部分符号为小于等于时,若中序遍历中未找到根节点,rootInorder会指向endInorder之后,造成下一句中的if条件部分永远为false。

面试题8:二叉树的下一个节点。给定一棵二叉树和其中一个节点,如何找出中序遍历序列的下一个节点?树的节点结构如下:

struct BinaryTreeNode{
    char m_nValue;
    BinaryTreeNode *m_pLeft;
    BinaryTreeNode *m_pRight;
    BinaryTreeNode *m_pParent;
};

如果一个节点有右子树,那么它中序遍历的下一个节点就是它右子树中的最左子节点,即,从右子树出发一直沿着指向左子节点的指针,就能找到它中序遍历的下一个节点。

如果一个节点没有右子树,并且这个节点是左节点,那么它的中序遍历的下一个节点是它的父节点。

如果一个节点没有右子树,并且这个节点是右节点,我们可以沿着指向父节点的指针向上遍历,直到找到一个左节点,这个左节点的父节点就是它中序遍历的下一个节点。

假如二叉树如下图所示(将a换为1,b换为2,以此类推):
在这里插入图片描述
完整代码如下:

#include <iostream>
using namespace std;

struct BinaryTreeNode {
    int m_nValue;
    BinaryTreeNode* m_pLeft;
    BinaryTreeNode* m_pRight;
    BinaryTreeNode* m_pParent;
};

BinaryTreeNode* ConstructCore(int* startPreorder, int* endPreorder, int* startInorder, int* endInorder) {
    BinaryTreeNode* root = new BinaryTreeNode();
    root->m_nValue = *startPreorder;
    root->m_pLeft = root->m_pRight = nullptr;

    if (startPreorder == endPreorder) {    //当递归到前序遍历只含一个元素时
        if (startInorder == endInorder && *startPreorder == *startInorder) {    //此时若中序遍历也只含一个元素并且这个元素值与前序遍历的元素值相同时,递归到底成功返回
            return root;
        }
        else {    //否则当前序和中序遍历的个数不相等(前序遍历只含一个元素但中序遍历有多个元素)或值不相等(前序遍历和中序遍历元素数都为1但这两个值不等)时
            throw exception("Invalid input.");    //说明输入的前序和中序遍历不匹配
        }
    }

    int* rootInorder = startInorder;
    while (rootInorder < endInorder && *rootInorder != *startPreorder) {    //遍历中序序列找到根节点
        ++rootInorder;
    }

    if (rootInorder == endInorder && *rootInorder != *startPreorder) {    //若以上循环结束仍未找到根节点,说明输入有误
        throw exception("Invalid input");
    }

    int leftLength = rootInorder - startInorder;    //左子树长度
    int* leftPreorderEnd = startPreorder + leftLength;    //左子树尾边界
    if (leftLength > 0) {    //当左子树仍存在时
        root->m_pLeft = ConstructCore(startPreorder + 1, leftPreorderEnd, startInorder, rootInorder - 1);    //继续递归左子树
    }
    if (leftLength < endPreorder - startPreorder) {    //左子树长度小于当前遍历的树节点数-1(去掉根节点)时,说明存在右子树
        root->m_pRight = ConstructCore(startPreorder + leftLength + 1, endPreorder, rootInorder + 1, endInorder);    //继续递归右子树
    }

    return root;
}

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);
}


BinaryTreeNode* GetNextInorder(BinaryTreeNode* pNode) {    //找到中序遍历下一个节点,若输入的已经是中序遍历最后一个节点,返回空指针
    if (pNode == nullptr) {
        return nullptr;
    }
    BinaryTreeNode* pNext = nullptr;    //用来存放中序遍历下一个节点

    if (pNode->m_pRight != nullptr) {    //当输入节点有右子树,中序遍历下一节点即为输入节点右子树的最左子节点
        BinaryTreeNode* pRNode = pNode->m_pRight;
        while (pRNode->m_pLeft != nullptr) {    //遍历找到该节点的最左子节点
            pRNode = pRNode->m_pLeft;
        }
        pNext = pRNode;
    }
    else if (pNode->m_pParent != nullptr) {    //当输入节点没有右子树且有父节点时
        BinaryTreeNode* pCurrent = pNode, * pCurrentParent = pCurrent->m_pParent;
        while (pCurrentParent != nullptr && pCurrent != pCurrentParent->m_pLeft) {    //当pCurrent节点的父节点不为空且pCurrent不是一个左节点时
            pCurrent = pCurrentParent;
            pCurrentParent = pCurrentParent->m_pParent;    //向上迭代
        }
        pNext = pCurrentParent;    //当循环结束时,要么找到了一个是左节点的父节点(此处包含了当输入节点没有右
                                   //子树时,输入节点是左节点和输入节点是右节点两种情况),要么父节点为空,意味着输入节点沿着父节点向上的所有节点都是右节点,
                                   //而右节点是中序遍历的最后一个节点,即输入节点是整棵树中序遍历的最后一个节点
    }

    return pNext;    //输入节点既没有右节点,其父节点也不存在时,即输入节点是根节点,并且这棵树只含左子树。那么它就是中序遍历的最后一个节点
}

void ConnectParent(BinaryTreeNode* root) {    //添加二叉树的父指针

    if (root == nullptr) {
        return;
    }

    if (root->m_pLeft != nullptr) {
        root->m_pLeft->m_pParent = root;
        ConnectParent(root->m_pLeft);
    }
    if (root->m_pRight != nullptr) {
        root->m_pRight->m_pParent = root;
        ConnectParent(root->m_pRight);
    } 
}

int main() {
    int inorder[] = { 4,2,8,5,9,1,6,3,7 };    //上图二叉树的中序遍历
    int preorder[] = {1,2,4,5,8,9,3,6,7};    //上图二叉树的前序遍历
    BinaryTreeNode* root = Construct(preorder, inorder, 9);    //构建二叉树,但不带父指针
    root->m_pParent = nullptr;
    ConnectParent(root);    //添加二叉树的父指针
    if (BinaryTreeNode* pNode = GetNextInorder(root->m_pRight->m_pRight)) {    //输入任意节点,找到中序遍历顺序下的下一节点
        cout << pNode->m_nValue << endl;
    }
    else {
        cout << "已经是中序遍历的最后一个节点。" << endl;
    }
    return 0;
}
发布了193 篇原创文章 · 获赞 11 · 访问量 6万+

猜你喜欢

转载自blog.csdn.net/tus00000/article/details/104213204