一、树与图的存储
树是一种特殊的图——无环连通图,故只介绍图即可。
无向图:边无方向,每次要建立两条有向边。是一种特殊的有向图,故只介绍有向图即可。
有向图:
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;
}