算法笔记-树遍历基本操作

本章内容讨论的是一般意义上的树,即子结点个数不限且子结点没有先后次序的树。
建议使用静态写法,即用数组下标来代替所谓的地址。这需要事先开一个大小不低于结点上限个数的结点数组。

struct node {
	typename data;	//数据域
	int child[maxn];	//指针域,存放所有子结点的下标 
}Node[maxn];	//结点数组,maxn为结点上限个数

而child数组的长度由于无法预知子结点个数只能开到最大,有些时候不可接受,故使用STL中的vector。

struct node {
	typename data;	//数据域
	vector child;	//指针域,存放所有子结点的下标 
}Node[maxn];	//结点数组,maxn为结点上限个数

新建结点

int index = 0;
int newNode(int v) {
	Node[index].data = v;	//数据域为v
	Node[index].child.clear();	//清空子结点
	return index++;		//返回结点下标,并令index自增 
}

但一般情况下会给出结点的编号,并且编号一定是0,1,…,N-1或1,2,…,N情况下就不需要newNode函数了

树的先根遍历
针对不同的题目,需要在此基础上增加相关的代码。

void PreOrder(int root) {
	printf("%d ", Node[root].data);	//访问当前结点
	for(int i = 0; i < Node[root].child.size(); i++) {
		PreOrder(Node[root].child[i]);	//递归访问结点root的所有结点 
	} 
}

树的层次遍历
实现方法类似二叉树的层次遍历

void LayerOrder(int root) {
	queue<int> Q;
	Q.push(root);	//将根结点入队
	while(!Q.empty()) {
		int front = Q.front();	//取出队首元素 
		printf("%d ", Node[front].data);	//访问当前结点的数据域
		Q.pop();	//队首元素出队
		for(int i = 0; i < Node[front].child.size(); i++) {
			Q.push(Node[front].child[i]);	//将当前结点的所有子结点入队 
		} 
	} 
}

如需要对结点的层号进行求解,只需要在结构体node的定义中增加变量来记录结点的层号:

struct node {
	int layer;		//记录层号 
	int data;
	vector<int> child;
}; 

树的层次遍历改写后:

void LayerOrder(int root) {
	queue<int> Q;
	Q.push(root);	//将根结点入队
	Node[root].layer = 0;	//记根结点的层号为0 
	while(!Q.empty()) {
		int front = Q.front();	//取出队首元素 
		printf("%d ", Node[front].data);	//访问当前结点的数据域
		Q.pop();	//队首元素出队
		for(int i = 0; i < Node[front].child.size(); i++) {
			int child = Node[front].child[i];	//当前结点的第i个子结点的编号
			//子结点层号为当前结点层号加1
			Node[child].layer = Node[front].layer + 1; 
			Q.push(child);	//将当前结点的所有子结点入队 
		} 
	} 
}

从树的遍历看DFS与BFS

  1. 深度优先搜索(DFS)与先根遍历
    如果采用树的先根遍历这棵树(即先访问根结点,再从左至右依次访问所有子树),将会得到和DFS同样的序列。事实上,对所有合法的DFS求解过程,都可以把它画成树的形式,此时死胡同等价于树中的叶子结点,而岔道口等价于树中的非叶子结点,并且对这棵树的DFS遍历过程就是树的先根遍历的过程。
    遇到DFS问题,可以把一些状态作为树的结点,然后就可以转换为更直观的对树的先根遍历问题。同理,也可借用DFS以深度作为第一关键词的思想来对结点进行遍历,以获得所需结果。
    在进行DFS过程中对某条可以确定不存在解的子树采取直接剪断的策略,但剪枝的前提是必须保证剪枝的正确性。
  2. 广度优先搜索(BFS)与层序遍历
    对所有合法的BFS求解过程,可以画出一棵树并且将广度优先搜索问题转换为树的层序遍历的问题。

实例:
给定一棵树和每个结点的权值,求所有从根结点到叶子结点的路径,使得每条路径上的结点的权值之和等于给定的常数S。如果有多条这样的路径,则按路径非递增的顺序输出。其中路径的大小是指,如果两条路径分别为a1->a2->…->ai->an与b1->b2->…->bi->bm,且有a1 == b1、a2 == b2、… 、ai-1 == bi-1成立,但ai > bi,那么称第一条路径比第二条路径大。
输入样例

20 9 24
10 2 4 3 5 10 2 18 9 7 2 2 1 3 12 1 8 6 2 2
00 4 01 02 03 04
02 1 05
04 2 06 07
03 3 11 12 13
06 1 09
07 2 08 10
16 1 15
13 3 14 16 17
17 2 18 19

输出样例

10 5 2 7
10 4 10
10 3 3 6 2
10 3 3 6 2

样例解释
从根到叶子的带权路径和为24的路径有4条,经过的结点标号分别为(括号中为点权):

  1. 00(10)->04(5)->06(2)->09(7)
  2. 00(10)->02(4)->05(10)
  3. 00(10)->03(3)->13(3)->17(6)->19(2)
  4. 00(10)->03(3)->13(3)->17(6)->18(2)

思路:

  1. 指针域使用vector存放所有孩子结点的编号,考虑到最后的输出需要按权值从大到小排序,不妨在读入时事先对每个结点的子结点vector进行排序,这样在遍历时就会优先遍历到权值大的子结点
  2. 令int型数组path[MAXV]存放递归过程中产生的路径上的结点编号。接下来进行DFS,参数有三个:当前访问的结点标号index、当前路径path上的结点个数numNode(也是递归层数)以及当前路径上的权值和sum。递归过程的伪代码:
    2.1 若sum > S,直接return。
    2.2 若sum == S,说明当前访问结点index位置,输入中需要达到的S已经得到,这时如果结点index为叶子结点,则输出path数组中的所有数据;否则return。
    2.3 若sum < S,说明要求还未满足。此时枚举当前访问结点index的所有子结点,对每一个子结点child,先将其存入path[numNode],然后再此基础上往下一层递归,下一层的递归参数为child、numNode + 1、sum + node[child].weight。
#include<cstdio>
#include<vector>
#include<algorithm>
using namespace std;
const int MAXN = 110;
struct node {
	int weight;	//数据域
	vector<int> child;	//指针域 
} Node[MAXN];

bool cmp(int a, int b) {
	return Node[a].weight > Node[b].weight;	//按结点数据域从大到小排序 
}

int n, m, S;	//结点数、边数、给定的和
int path[MAXN];	//记录路径

//当前访问结点为index,numNode为当前路径path上的结点个数
//sum为当前的结点点权和
void DFS(int index, int numNode, int sum) {
	if(sum > S) return ;
	if(sum == S) {
		if(Node[index].child.size() != 0) return ;	//还没到叶子结点,直接返回
		//到达叶子结点,此时path[]中存放了一条完整的路径,输出它 
		for(int i = 0; i < numNode; i++) {
			printf("%d", Node[path[i]].weight);
			if(i < numNode - 1) printf(" ");
			else printf("\n");
		}
		return ;	//返回 
	}
	for(int i = 0; i < Node[index].child.size(); i++) {	//枚举所有子结点 
		int child = Node[index].child[i];		//结点index的第i的子结点编号 
		path[numNode] = child;		//将节点child加到路径path末尾 
		DFS(child, numNode + 1, sum + Node[child].weight);	//递归进入下一层 
	}
} 
 
int main() {
	scanf("%d%d%d", &n, &m, &S);
	for(int i = 0; i < n; i++) {
		scanf("%d", &Node[i].weight);
	}
	int id, k, child;
	for(int i = 0; i < m; i++) {
		scanf("%d%d", &id, &k);	//结点编号、孩子个数 
		for(int j = 0; j < k; j++){
			scanf("%d", &child);
			Node[id].child.push_back(child);		//child为结点id的孩子 
		}
		sort(Node[id].child.begin(), Node[id].child.end(), cmp);	//排序
	}
	path[0] = 0;	//路径的第一个结点设置为0号结点 
	DFS(0, 1, Node[0].weight);
	return 0;
} 
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值