咕了两个月的我(如果不算Luogu的题解)终于回来写博了qwq,因为我的数据结构知识一直很薄弱,每次考试老是失分,所以我决定写一写关于图论的博客,最近一段时间也正好在复习这部分,这篇博客的内容会涉及到:树与图的遍历,树的深度,图的联通块,拓扑排序,树的重心,最短路,最小生成树,并查集,Tarjan与图的连通性,树的直径,LCA,树链剖分,负环。文章内容与lyd的《算法竞赛进阶指南》重合度比较高(因为我就是按照他的书来复习的),同时文章以讲解为辅,代码为主,适合各位同学复习而并非初学者接触。

  所有模板都是我手打的,也许有错误,欢迎批评指正弱弱的我qwq。

 声明

  博客中如果没有特殊说明,则默认是n个点和无向图,文中会使用vector,邻接表,邻接矩阵这三种方式来存储。

 树与图的遍历

  树与图的存储方式是相同的,遍历可以采用dfs和bfs这两种方式。

  深度优先遍历与深度

  众所周知写这个只是为了知识的完整性,大家随便看看就行。

  dep是深度数组。

  

void dfs(int u)
{
    vis[u]=true;
    for(int i=h[u];i!=-1;i=e[i].nxt)
    {
    int v=e[i].v;
    if(vis[v])    continue;
    dep[v]=dep[u]+1;
    dfs(v);
    }
} 

  广度优先遍历

  bfs并没有dfs常用,用队列q来存储,每次遇到一个节点u,就入队,然后依次入队它的子节点,如果子节点也全部入队,就把u出队,又入队它第一个子节点的所有子节点,以此类推...

  eg:

  

入队标蓝,出队标红,这张图的bfs模拟过程如下:

1 234

234 5

345 

45 6

56 7

67 8

78 9

89

代码的实现很简单。

void bfs()
{
    memset(d,0,sizeof(d));
    q.push(1);
    d[1]=1;
    while(!q.empty())
    {
    int u=q.front(),q.pop();
    for(int i=h[u];i!=-1;i=e[i].nxt)
    {
    int v=e[i].v;
    if(d[v]) continue;
    d[v]=d[u]+1;
    q.push(v);
    }
    }
} 

我们可以看到图中有一个d数组,d[u]的作用是存储从1遍历到节点u所需经过的最少点数,qwq但是请大家不要妄想能用这个来求最短路,stO青君大佬

bfs的遍历有两个性质:

1.在访问完节点i的所有节点后,才会开始访问节点i+1。

2.队列中的元素至多有两个层次的节点,第一部分属于i层,第二部分属于i+1层,所有i的节点排在i+1的节点之前,也就是说,bfs遍历满足两段性和单调性,这也是它的基本性质。

树的dfs序

一般地,我们在dfs时,在刚进入递归前和即将回溯之前各记录一次该点的编号,最后产生的2n长节点的序列就是dfs序,如果用a[N>>1]存储dfs序,l[ ]和r[ ]分别存递归前和回溯前的cnt,a[ l [ u ] ]和a[ r [ u ] ]这一段记录的就是u节点的子树,由此可以很好地把树上操作转化为区间问题~!

代码简短也好理解。

void dfs(int u)
{
    vis[u]=true;
    l[u]=++cnt;
//    a[cnt]=u;
    for(int h[u];i!=-1;i=e[i].nxt)
    {
        int v=e[i].v;
        if(vis[v]) continue;
        dfs(v);
    }
    r[u]=++cnt;
//    a[cnt]=u;
} 

树的重心和子树大小

如果一棵树有v1~vk个节点,并且以v1~vk为根的子树大小是siz[v1]~siz[vk],那么以u为根的子树大小是siz[u]=siz[v1]+...+siz[vk],也就是说siz数组用来存储节点的子树大小(包括节点本身)。

树的重心指的是,如果我们把一个节点u从树中删除,那么原来的一棵树可能会分成若干个不同的部分,如果在删除一个节点后,剩下的所有子树中最大的一棵是最小的,那么这个被删去的节点称为树的重心,对于无权图,大小一般指的是节点个数。

代码简短好理解,如下。

void dfs(int u)
{
    max_point=0;
    siz[u]=1;
    vis[u]=true;
    for(int i=h[u];i!=-1;i=e[i].nxt)
    {
        int v=e[i].v;
        if(vis[v]) continue;
        dfs(v);//要先遍历子节点才能找到子节点的siz值 qwq为什么总是忘记dfs 我有罪 
        siz[u]+=siz[v];
        max_point=max(max_point,siz[v]);
    }
    max_point=max(max_point,n-siz[u]);
    if(max_point<ans)
    {
        ans=max_point;
        pos=u;
    }
} 

图的连通块划分

经过多次dfs,可以找出一张图的每一个连通块,不会连通块的同学可以去看看知识点再做两道题,这个很简单,不赘述了。

void dfs(int u)
{
    scc[u]=cnt;
    for(int i=h[u];i!=-1;i=e[i].nxt)
    {
    int v=e[i].v;
    if(scc[v]) continue;
    scc[v]=cnt;
    dfs(v);
    }
}
int main()
{
    for(int i=1;i<=n;i++)
    if(!scc[i]) dfs(i);
}

 

  

02-13 22:29