tips:树是一种特殊的图 是无环连通图

有向图边有方向 如a->b
无向图边没有方向 a<->b
无向图是特殊的有向图 我们就先理解有向图的储存

1.邻接矩阵 其实就是一个二维数组 g[a][b]就存储a->b 有权重就是权重 没有就是bool值表示有无边 邻接矩阵不能有重边 只能保留一条 这个比较浪费空间

2.邻接表 就是n个节点 每个都开单链表 就跟哈希拉链法一样

每个h[i]链中表示的是连接i结点的所有结点 也就是为每一个点都开一个单链表 里面是无序的
所以h[i]存的是n个头结点

[存邻接表] [c++]
1
2
3
4
5
6
7
8
9
10
11
12
const int N=10010;
int h[N],ne[N],e[N],st[N],idx;

void add(int a,int b)
{
e[idx]=b; //存编号
ne[idx]=h[a]; //插入 让此结点的下一结点指向之前的头结点
h[a]=idx; //替换头结点
idx++;
}


[树的重心(dfs用法)] [c++]
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
#include<bits/stdc++.h>
using namespace std;

const int N=1001000;
bool st[N]; //存有没有遍历过

int h[N],e[N],ne[N];
int n;
int ans=N;
int idx=0;

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

int dfs(int u)
{
st[u]=true;
int sum=1; //当前子树大小为1 因为包含当前这个点
int res=0; //删掉点后每一个连通块的最大值
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(res,ans);

return sum;
}

int main()
{
cin>>n;
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;
}


[图的层次遍历] [c++]
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
#include <cstring>
#include <iostream>

using namespace std;

const int N=1e5+10;

int h[N], e[N], idx, ne[N];
int d[N]; //存储每个节点离起点的距离 d[1]=0
int n, m; //n个节点m条边
int q[N]; //存储层次遍历序列 0号节点是编号为1的节点

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; //0号节点是编号为1的节点

memset(d,-1,sizeof d);

d[1]=0; //存储每个节点离起点的距离

//当我们的队列不为空时
while(hh<=tt)
{
//取出队列头部节点
int t=q[hh++];

//遍历t节点的每一个邻边
for(int i=h[t];i!=-1;i=ne[i])
{
int j=e[i];
//如果j没有被扩展过
if(d[j]==-1)
{
d[j]=d[t]+1; //d[j]存储j节点离起点的距离,并标记为访问过
q[++tt] = j; //把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;
}

拓扑序列
对于一个图中所有点构成的序列A满足 对于图中每一条边(x,y) x在A中都出现在y之前
简而言之所有边都是从前指向后的 有向无环图(拓扑图)一定存在拓扑序列

入度:有多少条边指向自己
出度:有多少边指向别人

性质 一个有向无环图一定存在一个入度为0的点
所有入度为0的点(无任何点指向自己的点)可以作为起点
1.将所有入度为0的点入队
2.宽搜过程 枚举所有初边

[拓扑图] [c++]
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
#include<bits/stdc++.h>
using namespace std;
const int N=100100;

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

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

bool topp()
{
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]--; //删除边 也就是对应点入度-1
if(d[j]==0) q[++tt]=j;
}
}
return tt==n-1; //如果队尾下标已经n-1 说明插入了n个元素 排列完成
}

int main()
{
memset(h,-1,sizeof h);
cin>>n>>m;
for(int i=0;i<m;i++)
{
int x,y;
cin>>x>>y;
add(x,y);
d[y]++;
}
if(topp())
{
for(int i=0;i<n;i++) cout<<q[i]<<" ";
cout<<endl;

}
else cout<<-1<<endl;
}