图的邻接表的存储与初始化
#include <iostream>
using namespace std;
const int N = 100010;
int h[N],e[N],en[N],idx;
int main()
{
memset(h,-1,sizeof h);
return 0;
}
宽度优先遍历
#include<iostream>
#include <cstring>
using namespace std;
const int N = 100010,M = N*2;
int h[N],e[M],en[M],idx;
bool st[N];
void add(int a,int b)
{
e[idx] = b,en[idx] = h[a],h[a] = idx,idx++;
}
//为节点a加一条有向边,或者说加一个可达节点
//当前节点idx的值先赋上,其指向头节点(因为是头插法)。
void dfs(int u)
{
st[u] = true;
for(int i = h[u];i!=-1;i = en[i])
{
int j = e[i];
if(!st[j]) dfs(j);
}
}
//存储按照邻接表的形式,每个节点u连了一大串,都是其可到达的节点
/*
st[u] 为true 代表u点已经用过了,遍历过了。接着看u节点的可达节点,看其是否用过
没用过就往下找,用过回溯,看看u节点的其他可达节点。
*/
int main()
{
memset(h,-1,sizeof h);
dfs(1);
return 0;
}
深度优先遍历
树的重心
样例模拟:
删除每个节点 统计其他连通块大小
删除每个节点 其左右儿子为连通块 剩余节点是另一个连通块
/*重心就是删除其之后 整个树的剩余连通块的点数最大值最小
找到重心 然后输出其剩余连通块点数最大的。 不理解就自己手动模拟一遍*/
#include<iostream>
#include <cstring>
using namespace std;
const int N = 100010,M = N*2;
int n;
int h[N],e[M],en[M],idx;
bool st[N];
void add(int a,int b)
{
e[idx] = b,en[idx] = h[a],h[a] = idx,idx++;
}
int ans = N; //全局答案,就是遍历所有删除节点的情况中 一个树中的剩余连通块里最大值的里最小的那一个 就是重心了。最大值也不会超过n个节点.
//dfs返回以u为根的子树中点的数量大小
int dfs(int u)
{
//1 当前节点已被使用
st[u] = true;
//2
int sum = 0;//当前子树大小(<=>当前节点下一共有多少节点)
int size = 0;//记录删除当前节点后 每一个连通块点的最大值(<=>当前节点下子树最小的)
//3
for(int i = h[u];i!=-1;i = en[i]) //遍历以u为根节点的所有的节点
{
int j = e[i];
if (st[j]) continue;
int s = dfs(j);//当前节点子树大小
size = max(size,s);
// 首先是0与当前节点左子树比较,接着是当前节点左子树与当前节点右子树(比较),接着等等 总之是所有的子树比较一遍。当前节点子树s也是一个连通块
sum += s;
//当前节点子树s也是以u为根节点的子树的一部分,因为sum一直在递归中 所以越上层值越大,因为一直在加,
//记录当前节点下一共有多少节点,(便于后续求u节点之上的连通块点数)
}
//4 此时的max中的size是左右子树中的最大值,接着再和 u节点上面的一堆n-sum-1比较
size = max(size,n-sum-1);
ans = min(ans,size);
//5
return sum+1;//每次返回一次就是其下加了一个节点
}
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);//dfs(n)//从树中任意一个节点开始搜都可以,因为是树->是无向边
printf("%d\n", ans);
return 0;
}