数据结构九、树

树的概念

        树形结构是一类重要的非线性结构。

从上往下看,每个节点可能有0个或者多个后继;从下往上看,除了根节点,每个节点都有一个前驱。因此我们可以得到一条性质:节点个数 = 边数 + 1。 

树的存储

        树结构相对于线性结构来说就比较复杂,存储时既要保存值域,也要保存节点与节点之间的关系。树有很多种存储方式,现阶段我们只需掌握孩子表示法即可。 所谓孩子表示法,就是对于每一个节点,只存储所有孩子的信息。如下图所示:

        但是,如果这棵树是无根树 (树的根节点未知,父子关系不明确)该如何解决?我们只需要将与该节点相连的所有节点都存储下来。如下图所示:

实现方式一、用vector数组实现        

        1、创建一个大小足够的vector数组:vector<int> edges[10];注意这里是'[ ]',不是'( )'。

其中edges[i]里面就存着i号节点所有的孩子。

        2、 对于i的孩子,直接edges[i].push_back进去即可。

 

#include <iostream>
#include <vector>
using namespace std;

const int N = 1e5 + 10;

int n;
vector<int> edges[N];

int main()
{
	cin >> n;
	for(int i = 1; i < n; i++)
	{
		int a, b;
		cin >> a >> b;
		egdes[a].push_back(b);
		edges[b].push_back(a);
	}
	
	return 0;
}

实现方式二、链式前向星

         链式前向星的本质就是用链表存储所有的孩子,其中链表使用数组模拟实现的。

1、创建一个足够大的数组h,作为所有节点的哨兵位。

2、创建两个足够大的数组e和ne,一个作为数据域,一个作为指针域。

3、创建一个变量id,标记新节点的存储位置。

4、当x有一个节点y的时候,就把y头插到x的链表中。

#include <iostream>
using namespace std;

const int N = 1e5 + 10;

int n;
int id;
int h[N];
int e[N * 2], ne[N * 2]; //在存储的时候,每一条边存储了两遍

void add(int a, int b)
{
	id++;
	e[id] = b;
	ne[id] = h[a];
	h[a] = id;
}

int main()
{
	cin >> n;
	for(int i = 1; i < n; i++)
	{
		int a, b;
		cin >> a >> b;
		//a和b之间有一条边
		add(a, b);
		add(b, a);
	}
	
	return 0;
}

树的遍历 

        树的遍历就是不重不漏地将树中所有的点都扫描一遍。在树形结构中,如果不按一定的规则遍历,就会遗漏或者重复遍历一些节点。因此,在树形结构中,我们常用以下两种规则遍历树,一种是深度优先遍历,一种是宽度优先遍历。 树的遍历是许多算法的基石,一定要重点掌握。

深度优先遍历(DFS) 

         深度优先遍历,又称深度优先搜索(Depth First Search),是一种用于遍历树或者图的算法。所谓深度优先,就是每次都尝试向更深的节点走,也就是一条路走到黑~

从根节点出发,依次遍历每一棵子树,遍历子树的时候,还是从子树的根节点出发去遍历。因此,深度优先遍历可以用递归来实现。这里要注意一个细节,存储树结构时,我们把相邻的所有节点都存储了下来,这样在扫描子树时会扫描到上一层导致死递归,因此我们需要一个st数组来标记哪些节点已经访问过,接下来进行DFS的时候就不会再去遍历那些节点了。

用vector数组存储树的DFS

#include <iostream>
#include <vector>
using namespace std;

const int N = 1e5 + 10;

int n;
vector<int> edges[N];

bool st[N];

void dfs(int u)
{
	cout << u << " ";
	st[u] = true;
	for(auto v : edges[u])
	{
		if(!st[v])
		{
			dfs(v);
		}
	}
}

int main()
{
	cin >> n;
	for(int i = 1; i < n; i++)
	{
		int a, b;
		cin >> a >> b;
		edges[a].push_back(b);
		edges[b].push_back(a);
	}
	
	dfs(1);
	
	return 0;
}

用链式前向星存储树的DFS

#include <iostream>
using namespace std;

const int N = 1e5 + 10;

int n;
int id;
int h[N];
int e[N * 2], ne[N * 2];

bool st[N];

void add(int a, int b)
{
	id++;
	e[id] = b;
	ne[id] = h[a];
	h[a] = id;
}

void dfs(int u)
{
	cout << u << " ";
	st[u] = true;
	
	for(int i = h[u]; i; i = ne[i])
	{
		int v = e[i];
		if(!st[v])
		{
			dfs(v);
		}
	}
}

int main()
{
	cin >> n;
	for(int i = 1; i < n; i++)
	{
		int a, b;
		cin >> a >> b;
		add(a, b);
		add(b, a);
	}
	
	dfs(1);
	
	return 0;
}

 宽度优先遍历(BFS)

        宽度优先遍历,又称广度优先遍历(Breadth First Search),也是一种用于遍历树或图的算法。所谓宽度优先,就是每次访问同一层的节点,如果这一层都访问完了,就再访问下一层。 这里我们借助队列来实现这一过程。

1、创建一个队列

2、根节点入队,同时标记该节点已经入队

3、若队列不为空,队头节点出队并访问该节点,然后将该点的孩子一次入队

4、重复过程3,直到队列为空

用vector数组存储树的BFS 

#include <iostream>
#include <vector>
#include <queue>
using namespace std;

const int N = 1e5 + 10;

int n;
vector<int> edges[N];

bool st[N];

void bfs()
{
	queue<int> q;
	q.push(1);
	st[1] = true;
	
	while(q.size())
	{
		auto u = q.front();
		q.pop();
		cout << u << " ";
		
		for(auto v : edges[u])
		{
			q.push(v);
			st[v] = true; 
		}
	}
}

int main()
{
	cin >> n;
	for(int i = 1; i < n; i++)
	{
		int a, b;
		cin >> a >> b;
		edges[a].push_back(b);
		edges[b].push_back(a);
	}
	
	bfs(1);
	
	return 0;
}

 用链式前向星存储树的BFS

#include <iostream>
#include <queue>
using namespace std;

const int N = 1e5 + 10;

int n;
int id;
int h[N];
int e[N * 2], ne[N * 2];

bool st[N];

void add(int a, int b)
{
	id++;
	e[id] = b;
	ne[id] = h[a];
	h[a] = id;
}

void bfs()
{
	queue<int> q;
	q.push(1);
	st[1] = true;
	
	while(q.size())
	{
		auto u = q.front();
		q.pop();
		cout << u << " ";
		
		for(int i = h[u]; i; i = ne[i])
		{
			int v = e[i];
			if(!st[v])
			{
				q.push(v);
				st[v] = true;
			}
		}
	}
}

int main()
{
	cin >> n;
	for(int i = 1; i < n; i++)
	{
		int a, b;
		cin >> a >> b;
		add(a, b);
		add(b, a);
	}
	
	bfs();
	
	return 0;
}

 小结

        关于树的内容到这里已经全部讲解完毕了 ,重难点还是在树的存储与遍历上,需要反复体会和理解。创作不易,还请大家多多点赞支持,下一章二叉树不见不散。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值