版权声明:本文为博主原创学习笔记,如需转载请注明来源。 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;
}