两种存储方式,树始终特殊的图,树是无环连通图
图分为有向图和无向图,而无向图属于一种特殊的有向图——所以实际上就是研究有向图
有向图分为两类:邻接矩阵、邻接表
//树和图的存储主要就是邻接矩阵或者邻接表
//采用邻接表的更多,邻接表可以看作一个一位数据上每个点接着一条单链表,插入方式和单链表一致
#include
using namespace std;
const int N = 1000010, M = N * 2;
int h[N], e[M], ne[M], index;
//h[]数据上的每个点都是每条单链表的头节点
//e[],ne[],index 和单链表中定义的完全一致,e[]存放值,ne[]表示指针
int add(int a, int b) {//要求实现从 a -> b 的边添加
e[index] = b, ne[index] = h[a], h[a] = index++;
}
int main() {
}
前面提到树本质就是图,而无向图本质就是有向图——所以需要搞清楚有向图是怎么实现遍历的
//树和图的存储主要就是邻接矩阵或者邻接表
//采用邻接表的更多,邻接表可以看作一个一位数据上每个点接着一条单链表,插入方式和单链表一致
#include
using namespace std;
const int N = 1000010, M = N * 2;
int h[N], e[M], ne[M], index;
bool st[N];//布尔数组用于存放状态,确保每一个点只走过一次
//h[]数据上的每个点都是每条单链表的头节点
//e[],ne[],index 和单链表中定义的完全一致,e[]存放值,ne[]表示指针
int add(int a, int b) {//要求实现从 a -> b 的边添加
e[index] = b, ne[index] = h[a], h[a] = index++;
}
//数和图的深度优先搜索
void dfs(int u) {
st[u] = true;
for (int i = h[u]; i != -1; i = ne[i]) {
int j = e[i];
if (!st[j]) dfs(j);
}
}
int main() {
dfs(1);//从任意结点开始遍历
}
//树或图的遍历过程
#include
#include
/*
* 例题:846.树的重心
给定一颗树,树中包含n个结点(编号1~n)和n-1条无向边。
请你找到树的重心,并输出将重心删除后,剩余各个连通块中点数的最大值。
重心定义:重心是指树中的一个结点,如果将这个点删除后,剩余各个连通块中点数的最大值最小,那么这个节点被称为树的重心。
本题的本质是树的dfs, 每次dfs可以确定以u为重心的最大连通块的节点数,并且更新一下ans。
也就是说,dfs并不直接返回答案,而是在每次更新中迭代一次答案。
这样的套路会经常用到,在 树的dfs 题目中‘
*/
using namespace std;
const int N = 100010, M = N * 2;
int n;//结点数
int h[N], e[M], ne[M], index;
int ans = N;
bool st[N];//st[]用于记录遍历过的节点的状态,初始化为-1
void add(int a, int b) {
//就是通过单链表的形式在头节点位置插入
e[index] = b, ne[index] = h[a], h[a] = index++;
}
int dfs(int u) {
int res = 0;//res表示以 u 为根节点所有子树中结点数最大值
int sum = 1;//sum是以 u 为根节点包含的所有子树结点数,包括自身
st[u] = true;//遍历整个过程每个结点只经过一次所以没有回溯的操作
for (int i = h[u]; i != -1; i = ne[i]) {//以 u 为根节点将其所有子树深度搜索一边
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() {
memset(h, -1, sizeof h);
scanf("%d", &n);
for (int i = 0; i < n - 1; ++i) {
int a, b;
scanf("%d%d", &a, &b);//无向边的两个结点
add(a, b), add(b, a);//无向边两个方向都要满足
}
dfs(1);
cout << ans << endl;
return 0;
}
例题:847. 图中点的层次
给定一个n个点m条边的有向图,图中可能存在重边和自环。所有边的长度都是1,点的编号为1~n。
请你求出1号点到n号点的最短距离,如果从1号点无法走到n号点,输出-1。
//树或者图的宽度优先遍历
#include
#include
using namespace std;
const int N = 100010, M = 2 * N;
int n, m;
int h[N], e[M], ne[M], index;
int d[N], q[N];//q[]表示队列;d[]标记宽度搜索过程中是否已经搜过,没有搜过的标记为-1
int add(int a, int b) {//建立边的过程
e[index] = b, ne[index] = h[a], h[a] = index++;
}
int bfs() {
memset(d, -1, sizeof d);
int hh = 0, tt = 0;//分别表示头结点和尾结点
d[1] = 0;
q[hh] = 1;
while (hh <= tt) {//队列不为空
int u = q[hh++];//取出队头元素
for (int i = h[u]; i != -1; i = ne[i]) {
int k = e[i];//循环取出以 u 为头节点所有的子节点值
if (d[k] == -1) {//当d[k]等于-1的时候表示k没有被访问过
d[k] = d[u] + 1;//k是前一个结点u的距离为1的下一个结点
q[++tt] = k;
}
}
}
return d[n];
}
int main() {
memset(h, -1, sizeof h);
scanf("%d%d", &n, &m);
while (m--) {
int a, b;
scanf("%d%d", &a, &b);
add(a, b);
}
int res = bfs();
cout << res << endl;
return 0;
}
图的宽度优先遍历应用就是拓扑序列,拓扑序列一定是针对有向图而言。
有向无环图一定有拓扑序列,拓扑序列中所有的边都是从前指向后的
度的概念:每个点都有入度(有多少边指向自己)和出度(多少条边出去)的概念
例题:848.有向图的拓扑序列给定一个n个点m条边的有向图,点的编号是1到n,图中可能存在重边和自环。请输出任意一个该有向图的拓扑序列,如果拓扑序列不存在,则输出-1。若一个由图中所有点构成的序列A满足:对于图中的每条边(x, y),x在A中都出现在y之前,则称A是该图的一个拓扑序列。
//一个又向无环图一定存在至少一个入度为0的点
#include
#include
using namespace std;
const int N = 100010;
int n, m;
int h[N], e[N], ne[N], index;
int d[N];//d[]数组用来记录入度
int q[N];//q[]表示队列,如果一个结点满足入读为零就放入队列中
void add(int a, int b) {
e[index] = b, ne[index] = h[a], h[a] = index++;
}
bool topsort() {
int hh = 0, tt = -1;
//首先需要找出所有入度为0的结点
for (int i = 1; i <= n; ++i) {
if (!d[i]) {
q[++tt] = i;//加入队列
}
}
while (hh <= tt) {//队列不为空
int u = q[hh++];//取出队头结点
//通过不断删除入度为0的结点,逐一攻破,如果存在有环图,最后加入队列的数目一定小于n-1
for (int i = h[u]; i != -1; i = ne[i]) {//找出u结点的所有相连结点
int k = e[i];
d[k]--;//删除相连的边,即入度--
if (!d[k]) {//如果入度为0,表示可以加入队列
q[++tt] = k;
}
}
}
return tt == n - 1;
}
int main() {
memset(h, -1, sizeof h);
scanf("%d%d", &n, &m);
while (m--) {
int a, b;
scanf("%d%d", &a, &b);
add(a, b);
d[b]++;//每建立一条边,b的入度++
}
if (topsort()) {
//满足拓扑排序,则输出
for (int i = 0; i < n; ++i) {
printf("%d", q[i]);
}
} else {
printf("%d", -1);
}
return 0;
}
注意事项:拓扑序列最后的输出结果顺序不唯一