6. N叉树的遍历

        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叉树 的前序+后序遍历。

        经典问题如 : 全排列问题 , N皇后问题

        这个后续再补充。。。。。。

  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值