word 很大,你忍一下。可以先让洗衣机工作后再来阅读文章~
一,回忆二叉树的遍历方式
前序遍历 : [根, [左子树], [右子树]]
中序遍历 : [[左子树], 根, [右子树]]
后序遍历 : [[左子树], [右子树], 根]
层序遍历 : 按照从上到下,从左到右的顺序,逐层遍历各个节点。
二,N叉树的遍历
注意,N叉树的中序遍历没有标准定义(你想想,一个节点最多有 N 个孩子,每个节点孩子数量还不一定相同,到底哪个算中间呢)。中序遍历只有在二叉树中有明确的定义。
N叉树遍历的重点是 : 对于每个子节点,通过递归的调用遍历函数来遍历以该子节点为根的子树
文字很拗口,直接看图简便
1
/ | \
2 3 4
/ \ | \
5 6 7 8
前序 : [1, 2, 5, 6, 3, 7, 4, 8]
后序 : [5, 6, 2, 7, 3, 8, 4, 1]
层序 : [1, 2, 3, 4, 5, 6, 7, 8]
三,遍历方法
1, 递归法
2, 迭代法
四,头文件
#ifndef __TREE_NODE_H__
#define __TREE_NODE_H__
#include <iostream>
#include <vector>
#include <time.h>
#include <stdlib.h>
#define MIN_NUM 0
#define MAX_NUM 10
#define N_NUM 5
#define N_PRE_ORDER 0
#define N_POST_ORDER 0
#define N_LEVEL_ORDER 1
#define PRINT_VEC(vec) \
do { \
for (auto it = (vec).begin(); it != (vec).end(); it++) { \
std::cout << *it << " "; \
} \
std::cout << std::endl; \
}while(0)
struct TreeNode {
int val;
std::vector<TreeNode*> children;
TreeNode() {}
TreeNode(int v) : val(v) {}
TreeNode(int v, std::vector<TreeNode*> c) : val(v), children(c) {}
};
static TreeNode* getRandomTree();
TreeNode* getRandomTree() {
srand((unsigned)time(NULL));
TreeNode* root = new TreeNode(rand() % (MAX_NUM - MIN_NUM + 1) + MIN_NUM);
int size = rand() % (N_NUM + 1);
for (int i = 0; i < size; i++) {
root->children.emplace_back(new TreeNode(rand() % (MAX_NUM - MIN_NUM + 1) + MIN_NUM));
}
for (auto it = root->children.begin(); it != root->children.end(); it++) {
size = rand() % (N_NUM + 1);
for (int i = 0; i < size; i++) {
(*it)->children.emplace_back(new TreeNode(rand() % (MAX_NUM - MIN_NUM + 1) + MIN_NUM));
}
}
return root;
}
#endif // __TREE_NODE_H
五,前序遍历
#include "TreeNode.h"
#if N_PRE_ORDER
#include <stack>
using namespace std;
/*
递归遍历:
先访问当前节点,然后递归的访问所有子节点
*/
void preorder(TreeNode* root, vector<int>& vec) {
if (root) {
vec.emplace_back(root->val); // 访问当前节点
// 递归的访问所有子节点
for (auto it = root->children.begin(); it != root->children.end(); it++) {
preorder(*it, vec);
}
}
}
/*
迭代遍历:
与二叉树一样,迭代遍历就是使用栈模拟递归。
因为栈是后进先出的,所以在将子节点加入栈中时,应该逆序加入(这与二叉树一样,先加右节点,再加左节点)
时间复杂度:时间复杂度:O(M),其中 M 是 N 叉树中的节点个数。每个节点只会入栈和出栈各一次。
空间复杂度:O(M)。在最坏的情况下,这棵 N 叉树只有 2 层,所有第 2 层的节点都是根节点的孩子。将根节点推出栈后,需要将这些节点都放入栈,共有 M−1个节点,因此栈的大小为 O(M)。
*/
void preorderN(TreeNode* root, vector<int>& vec) {
if (root) {
stack<TreeNode*> stack;
stack.emplace(root);
while (!stack.empty()) {
root = (TreeNode*)stack.top(); // 注意,这里用的是 root,会导致遍历完后 root 不再指向树根,可以用 TreeNode* top 替代
stack.pop();
vec.emplace_back(root->val);
for (auto it = root->children.rbegin(); it != root->children.rend(); it++) { // 逆序入栈
stack.emplace(*it);
}
}
}
}
int main() {
TreeNode* root = getRandomTree();
vector<int> ans;
preorder(root, ans);
PRINT_VEC(ans);
ans.clear();
preorderN(root, ans);
PRINT_VEC(ans);
return 0;
}
#endif // N_PRE_ORDER
六,后序遍历
#include "TreeNode.h"
#if N_POST_ORDER
#include <stack>
using namespace std;
/*
递归遍历 :
先遍历的访问当前节点的所有子节点,然后再访问当前节点
*/
void postorder(TreeNode* root, vector<int>& vec) {
if (root) {
for (auto it = root->children.begin(); it != root->children.end(); it++) {
postorder(*it, vec);
}
vec.emplace_back(root->val);
}
}
/*
迭代遍历 :
后序遍历是先将当前节点的所有子节点访问完,再访问当前节点。所以有个难题是,如何判断 "所有的"子节点 已经访问完了呢 ?
我们可以将访问过得子节点从当前节点的孩子列表中移除,这样当前节点的孩子列表为空时,则可以得出:没有孩子 或 孩子都已经访问完了。
步骤如下:
a, 将根节点入栈
b, 获取栈顶元素
c, 判断栈顶元素的孩子列表是否为空
c-1, 为空则 "弹出并访问" 该节点
c-2, 不为空,则将第一个孩子入栈,并将第一个孩子从栈顶节点的孩子列表中移除
d, 重复 b-c 步骤
用下面这个多叉树演示 :
1
/ | \
3 2 4
/ \
5 6
1, 我们先将 根节点(1) 入栈 栈 [1] , 访问顺序 []
2, 获取栈顶元素 => 1
3, 判断栈顶元素孩子列表是否为空 => 不为空,则将 节点(1) 的第一个孩子(3) 入栈,并将 3 从 1 的孩子列表中移除。 栈 [3,1] , 访问顺序 []
4, 获取栈顶元素 => 3
5, 判断栈顶元素孩子列表是否为空 => 不为空,则将 节点(3) 的第一个孩子(5) 入栈,并将 5 从 1 的孩子列表中移除。 栈 [5,3,1] , 访问顺序 []
6, 获取栈顶元素 => 5
7, 判断栈顶元素孩子列表是否为空 => 为空, 弹出并访问它。 栈 [3, 1] , 访问顺序 [5]
8, 获取栈顶元素 => 3
9, 判断栈顶元素孩子列表是否为空 => 不为空,将节点(3) 的第一个孩子(6) 入栈 [注意,之前3的孩子5已经被移出了],并将 6 从 3 的孩子列表移出。 栈 [6,3,1] , 访问顺序 [5]
10,获取栈顶元素 => 6
11, 判断栈顶元素孩子列表是否为空 => 为空, 弹出并访问它。 栈 [3,1] , 访问顺序 [5, 6]。
12,获取栈顶元素 => 3
13,判断栈顶元素孩子列表是否为空 => 为空,弹出并访问它。 栈 [1] , 访问顺序 [5, 6, 3]。
.......
*/
void postorderN(TreeNode* root, vector<int>& vec) {
if (root) {
stack<TreeNode*> stack;
stack.emplace(root);
while (!stack.empty()) {
TreeNode* top = (TreeNode*)stack.top(); // 注意,是先获取栈顶元素,而不是弹出来,只有当栈顶元素没有孩子时,才可以访问它
if (top->children.size() == 0) { // 栈顶元素没有孩子 或者 它的所有孩子已经被访问完了(因为被访问的孩子会被移出孩子容器,注意,这会改变树的形态),弹出并访问它
stack.pop();
vec.emplace_back(top->val);
}
else { // 该栈顶还有孩子,先将其第一个孩子加入栈,并将该孩子从孩子列表中移除
stack.emplace(top->children[0]);
top->children.erase(top->children.begin()); // 特别注意,需要将该节点从当前节点的孩子列表中移除!!!(这会改变树的形态)
}
}
}
}
int main() {
TreeNode* root = getRandomTree();
vector<int> ans;
postorder(root, ans);
PRINT_VEC(ans);
ans.clear();
postorderN(root, ans);
PRINT_VEC(ans);
return 0;
}
#endif // N_POST_ORDER
七,层序遍历
#include "TreeNode.h"
#if N_LEVEL_ORDER
#include <queue>
using namespace std;
void levelorder(TreeNode* root, vector<vector<int>>& ret) {
if (root) {
queue<TreeNode*> queue;
queue.emplace(root);
while (!queue.empty()) {
vector<int> vec;
int size = queue.size(); // 获取当前这层所有节点的个数
for (int i = 0; i < size; i++) { // 逐个弹出该层节点
root = (TreeNode*)queue.front();
queue.pop();
vec.emplace_back(root->val);
for (auto it = root->children.begin(); it != root->children.end(); it++) { // 对于该层节点,加入下一层的节点
queue.emplace(*it);
}
}
if (vec.size() > 0) {
ret.emplace_back(vec);
}
}
}
}
int main() {
TreeNode* root = getRandomTree();
vector<vector<int>> vec;
levelorder(root, vec);
for (auto it = vec.begin(); it != vec.end(); it++) {
for (auto i = (*it).begin(); i != (*it).end(); i++) {
cout << *i << " ";
}
cout << endl;
}
cout << endl;
return 0;
}
#endif // N_LEVEL_ORDER
八,总结
回溯算法实际就是对决策树的遍历,即 N叉树 的前序+后序遍历。
这个后续再补充。。。。。。