树的遍历
树的遍历在笔试的选择中出现的概率还是比较大的,特别的由两个遍历序列得到一个唯一的树这种题,所以掌握好遍历这部分还是挺重要的。
一、树的节点数问题
数的节点数问题有时候也会问到,比较简单基础
- 1.非空二叉树的第N层最大有2^(N-1)个节点
- 2.高度为H的二叉树最多有2^(H)-1个节点
二、树的四种遍历方式
树有四种遍历方式:前序遍历、中序遍历、后序遍历、层次遍历
- 1.前序遍历:根节点–>左节点–>右节点
- 2.中序遍历:左节点–>根节点–>右节点
- 3.后序遍历:左节点–>右节点–>根节点
- 4.层次遍历:按照树的层输出
三、树的还原
树可以根据前序、中序和、后序的组合还原成唯一的树
- 1.前序+中序:根据前序得到根节点,然后根据中序得到根节点的左右节点
- 2.后序+中序:根据后序得到根节点,然后根据中序得到根节点的左右节点
- 3.前序+后序:能得到根节点,但无法确定左右节点,所以无法得到唯一的树
四、树的变量实现代码
下面是自己写的一个支持基本类型的的模板函数,可以进行树的前序、中序和后序遍历,到现在我们可以知道树的前序、中序和后序的前中后的含义其实是根节点的输出位置而言的,这样就能简单记住了
#pragma once
#ifndef __MYNODE_
#define __MYNODE_
#include <iostream>
#include <vector>
using namespace std;
template<class T>
struct TreeNode {
T val;
struct TreeNode *left;
struct TreeNode *right;
TreeNode(T x) :
val(x), left(NULL), right(NULL) {
}
//树的前序遍历
static void preorder(TreeNode<T>* root, vector<T> &v) {
v.push_back(root->val);
if (root->left != NULL) {
preorder(root->left, v);
}
if (root->right != NULL) {
preorder(root->right, v);
}
}
static void preorder(TreeNode<T>* root) {
std::cout << root->val << endl;
if (root->left != NULL) {
preorder(root->left);
}
if (root->right != NULL) {
preorder(root->right);
}
}
//树的中序遍历
static void inorder(TreeNode<T>* root, vector<T> &v) {
if (root->left != NULL) {
inorder(root->left, v);
}
v.push_back(root->val);
if (root->right != NULL) {
inorder(root->right, v);
}
}
static void inorder(TreeNode<T>* root) {
if (root->left != NULL) {
inorder(root->left);
}
std::cout << root->val << endl;
if (root->right != NULL) {
inorder(root->right);
}
}
//树的后序遍历
static void postorder(TreeNode<T>* root, vector<T> &v) {
if (root->left != NULL) {
postorder(root->left, v);
}
if (root->right != NULL) {
postorder(root->right, v);
}
v.push_back(root->val);
}
static void postorder(TreeNode<T>* root) {
if (root->left != NULL) {
postorder(root->left);
}
if (root->right != NULL) {
postorder(root->right);
}
std::cout << root->val << endl;
}
};
#endif // !__MYNODE_
五、Morris遍历方法
现在向大家介绍一个遍历二叉树的神级方法,这个方法速度比递归快,而且不需要其他的存储结构。遍历的总体只有两个:
- 1.开始时位于子树的头节点,使其左子树的最右节点的空节点指向它,然后向它的左子树移动。如果其左子树存在左儿子,重复步骤1;如果不存在,进行步骤2
- 2.向其右子树移动,判断移动到的节点的左子树的最右节点的空指针是否指向当前移动到的节点,如果是,则将左子树的最右节点指向空。接下来重复步骤2.如果当前节点的左子树的最右节点不是指向它,则进行步骤1。
- 3.如果当前移动的节点为空则结束
下面是各种实验Morris方法的前序、中序和后序代码,其中后序遍历比较麻烦,由于本文多是总结和展示,各位想深入了解可以看后面的参考链接和推荐书籍
//Morris前序遍历
static void OrderMorris(TreeNode<T>* root) {
if (root == NULL) return;
TreeNode* cur = root;
TreeNode* temp;
while (cur != NULL) {
temp = cur->left;
//如果其存在左子树,让其左子树的最右节点指向它或为空
if (temp != NULL) {
//找到其最右节点
while (temp->right != NULL && temp->right != cur) {
temp = temp->right;
}
//如果指向子树根节点则让其指向空,并且当前指针向左子树移动
if (temp->right == NULL) {
temp->right = cur;
cout << cur->val << ' ';
cur = cur->left;
}
//如果指向当前根节点则让其指向空
else {
temp->right = NULL;
cur = cur->right;
}
}
//当前节点不存在左子树则向右移动
else {
cout << cur->val << ' ';
cur = cur->right;
}
}
cout << endl;
}
//Morris中序遍历
static void InMorris(TreeNode<T>* root) {
if (root == NULL) return;
TreeNode* cur = root;
TreeNode* temp;
while (cur != NULL) {
temp = cur->left;
//如果其存在左子树,让其左子树的最右节点指向它或为空
if (temp != NULL) {
//找到其最右节点
while (temp->right != NULL && temp->right != cur) {
temp = temp->right;
}
//如果指向子树根节点则让其指向空,并且当前指针向左子树移动
if (temp->right == NULL) {
temp->right = cur;
cur = cur->left;
}
//如果指向当前根节点则让其指向空
else {
temp->right = NULL;
cout << cur->val << ' ';
cur = cur->right;
}
}
//当前节点不存在左子树则向右移动
else {
cout << cur->val << ' ';
cur = cur->right;
}
}
cout << endl;
}
//Morris后序遍历
static void PostMorris(TreeNode<T>* root) {
if (root == NULL) return;
TreeNode* cur = root;
TreeNode* temp;
while (cur != NULL) {
temp = cur->left;
//如果其存在左子树,让其左子树的最右节点指向它或为空
if (temp != NULL) {
//找到其最右节点
while (temp->right != NULL && temp->right != cur) {
temp = temp->right;
}
//如果指向子树根节点则让其指向空,并且当前指针向左子树移动
if (temp->right == NULL) {
temp->right = cur;
cur = cur->left;
}
//如果指向当前根节点则让其指向空
else {
temp->right = NULL;
//在第二次到此节点时逆序打印
InvertedPrint(cur->left);
cur = cur->right;
}
}
//当前节点不存在左子树则向右移动
else {
cur = cur->right;
}
}
//最后逆序打印根节点的右部分
InvertedPrint(root);
cout << endl;
}
//为了配合Morris后序遍历而生的链表逆序打印
static void InvertedPrint(TreeNode* head) {
if (head == NULL) return;
TreeNode *cur, *next, *nnp;
cur = head;
next = cur->right;
if (next == NULL) {
cout << cur->val << ' ';
return;
}
nnp = next->right;
if (nnp == NULL) {
cout << next->val << ' ' << cur->val << ' ';
return;
}
while (cur->right != NULL) {
next->right = cur;
cur = next;
next = nnp;
nnp = next->right;
if (nnp == NULL) {
next->right = cur;
cur = next;
break;
}
}
next = cur->right;
nnp = next->right;
cur->right = NULL;
while (true) {
cout << cur->val << ' ';
next->right = cur;
cur = next;
next = nnp;
if (nnp->right == cur) {
cout << cur->val << ' ' << next->val << ' ';
break;
}
nnp = next->right;
}
}
参考链接
数据结构(六)——二叉树 前序、中序、后序、层次遍历及非递归实现 查找、统计个数、比较、求深度的递归实现
Morris Traversal方法遍历二叉树(非递归,不用栈,O(1)空间
推荐书籍
《程序员代码面试指南》–左程云(这是一本神书,突击的时候还是挺有用,其中的解题思想和技巧给人很多启发,左神的解说视频也是很给人启发,特别推荐给大家,里面有这个题目的详细解法)