树与图的遍历:拓扑排序(用C/C++实现)

一、树与图的存储

树是一种特殊的图——无环连通图,故只介绍图即可。

无向图:边无方向,每次要建立两条有向边。是一种特殊的有向图,故只介绍有向图即可。

有向图:

1. 邻接矩阵:一个二维数组g[a, b],表示a->b,边的权重即为数组的值,无权重即为bool值,0表示无边,1表示有边。

2. 邻接表:单链表,每个节点都有一个单链表,用来存储该点可以走到的点。


二、树与图的深度优先遍历


树的重心寻找方法的理解:

遍历每一个点将其删除,寻找最大的节点数。

使用深度优先遍历,返回要删除节点所连通的子树的size。

#include<iostream>
#include<cstring>
#include<algorithm> 
/*给定一棵树,树中包含n个节点和n-1条无向边
请你找到树的重心,并输出将重心删除后剩余各个连通块中点数的最大值
重心是指树中一个节点,将该点删除后剩余各个连通块中点数最大值最小 
*/
using namespace std;

int n; 
const int N = 100010, M = N * 2;
//h是链表头,e是节点的值,ne是下一个节点的位置,idx是当前的的节点 
int h[N], e[M], ne[M], idx; 
bool st[N];

int ans = N; 
//在第a个邻接表里面插入节点b 
void add(int a, int b)
{
	e[idx] = b, ne[idx] = h[a], h[a] = idx ++;
}

//返回以u为根的子树中的点的个数 
int dfs(int u)
{
	st[u] = true;//当前点已经被搜过了
	//sum当前大小,res子树节点最大值 
	int sum = 1, res = 0;
	//遍历u的所有边
	for(int i = h[u]; i != -1; i = ne[i])
	{
		int j = e[i];//找到图中的这个点 
		if(!st[j]) //如果这个点没有被搜过那就继续搜,往深处
		{
			//当前子树的大小 
			int s = dfs(j);
			res = max(res, s);
			sum += s;
		}
	}
	//剩下的 
	res = max(res, n - sum);
	
	ans = min(ans, res);
	return sum; 
}
int main()
{
	cin >> n;
	//让n个头节点全部指向-1
	memset(h, -1, sizeof h); 
	for(int i = 0; i < n - 1; i ++)
	{
		int a, b;
		cin >> a >> b;
		//无向图 
		add(a, b), add(b, a);
	}
	dfs(1);
	
	cout << ans << endl;
	return 0;
}


三、树与图的宽度优先遍历

#include<iostream>
#include<cstring>
#include<algorithm>
/*给定一个n个点m条边的有向图,图中可能存在重边和自环
所有边的长度都是1,点的编号为1-n
请你求出1号点到n号点的最短距离,如果走不通输出-1*/
using namespace std;

const int N = 100010;

int n, m;
int h[N], e[N], ne[N], idx;
int d[N], q[N];

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

int bfs()
{
	int hh = 0, tt = 0;
	q[0] = 1;//第一个元素是起点1
	
	memset(d, -1, sizeof d);
	d[1] = 0;
	
	while(hh <= tt)
	{
		int t =	q[hh ++];
		for(int i = h[t]; i != -1; i = ne[i])
		{
			int j = e[i];
			if(d[j] == -1) 
			{
				d[j] = d[t] + 1;
				q[tt ++] = j;
			}
		 } 
	}
	return d[n];
}

int main()
{
	cin >> n >> m;
	memset(h, -1, sizeof h);
	
	for(int i = 0; i < m; i ++)
	{
		int a, b;
		cin >> a >> b;
		add(a, b);
	}
	cout << bfs() <<endl;
	return 0;
}

四、拓扑排序(图的宽搜的应用)

1. 无向图没有拓扑序列。可以证明,一个有向无环图一定存在拓扑序列,故有向无环图也被称为拓扑图。

1 2 3

对于边(1, 3),1在3前面;对于边(1, 2),1在2前面;对于边(2, 3),2在3前面,故这是一个拓扑序列。

2. 入度与出度

每个点的入度是指指向该点的边数,每个点的出度是指从该点指出的边数。以上图为例,1的入度为0,出度为2;2的入度为1,出度为1;3的入度为2,出度为0.一个有向无环图,一定至少存在一个入度为0的点。

3. 解法

所有入度为0的点可以排在最前的位置,所以先将它入队。

while queue非空

{

    取出队头元素t

    枚举t的所有出边t -> j

    删除t -> j,因为剩下的所有结点一定在t之后,t是一个完全满足条件的点,故可以删除不再考虑,并且d[j] --,更新j的入度d。

    if d[j] == 0

           j就已经到达最前了,这时让j入队。

}

#include<iostream>
#include<algorithm>
#include<cstring>
/*给定一个n个点m条边的有向图,可能存在重边和自环
请输入任意一个该有向图的拓扑序列,如果不存在则输出-1
若一个由图中所有点构成的序列A满足:
对于图中的每条边(x, y),x在A中都出现在y之前,则称A是该图的一个拓扑序列*/
using namespace std;

const int N = 100010;

int n, m;
int h[N], e[N], ne[N], idx;
int q[N], d[N];

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

bool topsort()
{
	int hh = 0, tt = -1;
	
	for(int i = 1; i <= n; i ++)
	{
		if(!d[i])
			q[++ tt] = i;//所有入度为0的点都插入队列 
	}
	while(hh <= tt)
	{
		int t = q[hh ++];
			
		for(int i = h[t]; i != -1; i = ne[i])
		{
			int j = e[i];
			d[j] --;
			if(d[j] == 0) q[++ tt] = j;
		}
	} 
	return tt == n - 1;
}
int main()
{
	cin >> n >> m;
	
	memset(h, -1, sizeof h);
	
	for(int i = 0; i < m; i ++)
	{
		int a, b;
		cin >> a >> b;
		add(a, b);
		d[b] ++; //是a指向b的边,每插入一条边,b的入度都要加一 
	}
	
	if(topsort())
	{
		for(int i = 0; i < n; i ++) printf("%d ", q[i]);
		puts(""); 
	}
	else puts("-1");

	return 0;
}

  • 3
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值