一、有向图、无向图?
有向图: a --> b
无向图: a —> b, b —>a
可以把无向图看成特殊的有向图,建两条边就可以了。只需要考虑有向图怎么存储
二、有向图的存储
1.邻接矩阵
开一个二维数组 g[a][b]
表示a – > b,
(不能存储重边)
- 如果有大小,
g[a][b] = 权重
- 如果没有权重, 可以表示
bool
值,有无道路。 - 空间复杂度: n * n, 比较适合存储稠密图
2、邻接表
每个点都有一个单链表,表示该点可以走到的点
每个单链表中每个点的顺序无妨
//举例
我们有四个点1,2,3,4, 每个点的单链表存储该点可以通向的点
1---->3 ---> 4 ---->空
2 ----> 1 -----> 4 ---->空
3 ----> 4 ----> 空
4 ---> 空
这样即可存储一个唯一确定的有向图
//如果我们想把 2--->3插进去, 每个单链表的表头为 h[1], h[2]等
我们一般在表头插入
h[2] -- > 3 --> 1--->4--->空
先把 3 指向 1,再把 h[2] 指向 3
h[]存储N个链表的表头, e[]存储每个点的值是多少
ne[]
存储的每个节点的next值,即指向的下一个点是多少
两者用下标关联起来,空节点的下标用-1表示
1.邻接表的初始化
memset(h, -1, sizeof(h)); //初始化
2. 邻接表的插入
void add(int a, int b){ //插入一条边 a --> b
e[idx] = b; //先把节点b的值附上
ne[idx] = h[a];
h[a] = idx ++;
}
3.举例说明,更清晰的理解邻接表
idx = 1, 全局变量
h[]每个链表的头, e[]存储的每个点的值(非下标)是多少,
ne[]存储的每个结点的下一个值(下标)
存储
1--->3 ---> 4--->空
下标: 0-----1 ---- 2
2--->1 ----> 4 ---> 空
下标 3-----4 ------5
我们把这两条输出来,不是插入
h[1] = 1; //头指向的第一个是下标为1的
e[0] = 1, ne[0] = 1,
e[1] = 3, ne[1] = 2
e[2] = 4, ne[2] = -1, //第一条链表结束, idx = 3
h[2] = 3, e[3] = 2, ne[3] = 4,
e[4] = 1, ne[4] = 5
e[5] = 4, ne[5] = -1; 第二条结束: idx = 6
插入一条 1--->7
e[6] = 7, ne[6] = h[1] = 1, h[1] = 6, idx = 7
三、有向图的遍历
每个点只会遍历一次
深度优先遍历
(一条道走到黑)
bool st[N]; //记录该点是否遍历过了
void dfs(int u){ //u 表示遍历的点
st[u] = true; //标记该点已经被搜过了
for(int i = h[u]; i != -1; i = ne[i]){
int j = e[i];
if(!st[j]) dfs(j);
}
}
宽度优先遍历
(一层一层的)
四、树的重心
题目链接
题目描述:给定一棵树,树中包含n个结点(1-n),和n - 1条无向边。请找到树的重心,并输出将重心删除后,剩余各个连通块中点数的最大值。 重心定义:重心是指树中的一个结点,如果将这个点删除后,剩余各个连通块中点数的最大值最小,那么这个节点被称为树的重心。
输入格式
第一行包含整数n,表示树的结点数。
接下来n-1行,每行包含两个整数a和b,表示点a和点b之间存在一条边。
输出格式
输出一个整数m,表示将重心删除后,剩余各个连通块中点数的最大值。
数据范围 1≤n≤105
分析
- 题意理解:输出的是去掉重心(树的重心可能不唯一。但是树是唯一的),剩余连通块中 点数 最大值。
- 对每个点求出删除改点后剩余连通块中点数的最大值,从这些最大值中找出最小的那个 即是答案
- 无向图:建立边的时候要建立两条相反方向的边
- 树的dfs,每次更新一下ans,返回以u为根的子树中节点的个数,包括u节点 dfs可以算出子树的大小
#include <iostream>
#include <cstring>
using namespace std;
const int N = 100010, M = 2 * N;
int ans = N, n, m;
int h[N], e[M], ne[M], idx;
bool st[N];
void add(int a, int b){
e[idx] = b;
ne[idx] = h[a];
h[a] = idx++;
}
//返回以 u 为根的子树大小
int dfs(int u){
st[u] = true;
int sum = 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(s, res); //当前子树也算一个连通块,和res取一个max
sum += s;//该子树是以u为节点的子树的一部分,所以sum要加上s
}
}
res = max(res, n - sum); //去掉每个点后连通块中点数最大值
ans = min(ans, res);
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;
}