专题整理:图论(1)

本博客包含:图存储(邻接矩阵、vector、前向星)、拓扑排序

一、引入概念:

什么是图?

简单地说,用一组点和他们之间相连的若干线组成的图形称为图。

同时可以简单分为有向图和无向图,有向图两类图。

有向图,指点和点之间的线有明确的方向,从某个节点指向另一个节点。

无向图,指点和点之间的线没有明确方向,也可以看做线是双向的。

同时,根据实际应用需要,有些图有一定的权值,有些图具有点值,有些图则什么都没有。

该图为无向图

二、图的存储

虽然图看上去神秘莫测变化多端,但是编程软件毕竟不是SAI,计算机不可能直接识别你画的图像,因此,如何将图存储给电脑,就有极大的学问。

所谓一张图,在我们不考虑整体的情况下,我们实际上就是保存若干的点,以及点和点之间的连接关系,许多的点和他们的链接关系一起,就形成了一张图。

以下所有的边给出的方式都是x,y,z,表示x和y之间存在权值为z的有向边(无向边)

2-1、邻接矩阵

利用两个数组对图的信息进行存放,一个一位数组用于存放点权,一个二维数组用于存放边和边权。

例如

输入 1,2,5,就让mp[1][2]=5,表示点1和点2之间有一条权值为5的边。

这样子的存放方式优点是能够比较直观地展现出点和边的关系,缺点也很明显,那就是浪费了大量空间,假设题目中提到有10个点,但是却只给出了一条有效的边的关系,那么开出的10*10的空间只用了一个单位,造成了大量空间上的浪费。

2-2、vector存图

之前在数据结构里介绍过强大的STL,在STL中有一个存在vector,我们可以将其简单理解为可改变大小动态数组。

我们用结构体来保存边的信息(终点,权值),然后将每个点发出的边压到相应的vector数组下,这样子就完成了一次存图。

2-3、前向星&&链式前向星

2-3-1、前向星

vector存图依然存在一定的空间浪费问题,当给定的点比较多而边的信息较少的时候,还是会有空间浪费。

有没有一种以存储边的信息为第一要义的存图方式呢?

前向星是一种特殊的边集数组,所谓边集数组,就是利用一维数组存储图中所有边的一种图的表示方法。

我们还是用一个结构体数组存储边的信息,然后将边进行排序,排序依据是先比较起点大小后比较终点大小。

然后我们用一个head数组将起点为i的第一个边的位置存入head[i]中,用另一个数组len,将起点为i的边的数量存入len[i]中,由于之前已经进行了排序操作,因此我们所存的起点相同边一定是连续的,由此完成的一个图的存储。

2-3-2、链式前向星

前向星确实很好地解决了空间浪费的问题,但是问题又来了,排序的操作怎么看怎么不顺眼,假如出题人心地善良帮忙把数据排序好那还好,假如出题人心狠手辣,那可浪费了不少时间,有没有什么办法让排序的操作消失呢?

还记得数据结构里有一种不用在乎空间是否连续的结构吗?没错,链表。

这就是链式前向星,我们在边值结构体增加一个next,ed[i].next指向上一个和边i相同起点的边的位置信息,然后用head[j]来保存最后一个以j为起点的边,这样ed[i].next和head[j]就有了传递性,当读取下一个以i为起点的边的时候,将当前边的next指向当前的head[起点]所指向的边,然后将当前的head指向当前边。

2-4、代码

#include<iostream> 
#include<cstdio>
#include<string>
#include<cmath>
#include<algorithm>
#include<cstring>
#include<stack>
#include<algorithm>
#include<queue>
#include<vector>
#define N 100001
#define ll long long
using namespace std;
int n,m;

//链式前向星 
struct node{
    int to,next,w;
}ed[N];

int heads[N*10];
int tot
inline void qxx(int a,int b,int v)    //一条从a连向b权值为v的边。 
{
    ed[++tot].to=b;
    ed[tot].v=v;
    ed[tot].next=heads[a];
    heads[a]=tot;
}

inline void dfs(int a)   //遍历所有与a相连的边的方法
{
    for(int i=heads[a];i!=0;i=ed[i].next)
    {
        node t=ed[i];
    }
}

//vector存图
vector<node>mp[N];
inline void vec(int a,int b,int v)
{
    ed[++tot].to=b;
    ed[tot].w=v;
    mp[a].push_back(tot);
}

//邻接矩阵
int m[N][N];
inline void jz(int a,int b,int v)
{
    m[a][b]=v;
}

注:对于无向图,实际上就是看作双向图建立一条起点终点互换的边。

三、拓扑排序

3-1、题目链接:https://www.luogu.com.cn/problem/P1983

3-2、题目简介:

3-3、思路

3-3-1、什么是拓扑排序?

拓扑排序的对象是有向无环图,它期望找到一个走完所有节点的相对顺序

举个生活中的例子,做完所有家务,打五把CSGO,然后上床睡觉。

我们将不同家务作为不同的节点,每把CSGO作为不同的节点,上床睡觉作为一个节点。

我们可以看出一个大概的顺序来,那就是只有将所有的家务完成(将家务节点走完),之后再打CSGO(将游戏节点走完),最后再上床睡觉。

这就是拓扑排序的用法,它经常被用于对事物相对顺序的考察以及分级类的问题上。

具体的做法就是从入度(指向该节点的边的数量)为0的点开始(显然这些点的等级为1),指向其他的点,然后将该点为起点的边删除,再找入度为0的点,这些点的等级就是2,然后重复这样的过程。

3-3-2、分级

我们来考察一下这道题。

简单理解一下题意,就是如果这趟车次停靠了火车站 x,则始发站、终点站之间所有级别大于等于火车站 x 的都必须停靠。

那么停靠的火车站一定要比未停靠的等级要高,于是我们就用箭头将未停靠站连向停靠站。

最后连接出来的是一个有向无环图,我们在图上进行拓扑排序,最少的等级就是拓扑排序的次数。

3-3-3、代码

#include <iostream>
#include<cstdio>
#include<algorithm>
#include <cstring>
#include<cmath> 
#include<string>
#include <stack>
#define N 1020
using namespace std;
int degree[2*N],dep[2*N];
int ans;
struct node{
    int next,to;
}edge[N*1000];
bool vis[1010][1010];
int heads[2*N],tot=0;
void addin(int x,int y)
{
    tot++;
    edge[tot].to=y;
    edge[tot].next=heads[x];
    heads[x]=tot;
}

int stop[N],disstop[N],hash[N];
inline void init()
{
    memset(hash,0,sizeof(hash));
    int n,m=0;
    scanf("%d",&n);
    for(int i=1;i<=n;i++)
    {
        scanf("%d",&stop[i]);
        hash[stop[i]]=1;
        }
        for(int i=stop[1];i<=stop[n];i++) if(0==hash[i]) disstop[++m]=i;
        for(int i=1;i<=n;i++)
        {
            for(int j=1;j<=m;j++)
            {
                if(!vis[stop[i]][disstop[j]])
                {
                    vis[stop[i]][disstop[j]]=1;
                    addin(stop[i],disstop[j]);
                    degree[disstop[j]]=1;
                    }
                }
            }
            return;
}

inline void dfs(int u,int num)
{
    
    if(num<=dep[u]) return ;
    dep[u]=num;
    for(int i=heads[u];i!=-1;i=edge[i].next) dfs(edge[i].to,num+1);
}

int main()
{
    int n,m;
    scanf("%d%d",&n,&m);
    memset(heads,-1,sizeof(heads));
    memset(vis,false,sizeof(vis));
    for(int i=1;i<=m;i++) init();
    for(int i=1;i<=n;i++) if(!degree[i]) dfs(i,1);    //这里的思路是找到所有入度为0的边,一直走到终点,不断更新每个点的等级最大值
    for(int i=1;i<=n;i++) if(dep[i]>ans) ans=dep[i];
    printf("%d",ans);
    return 0;
}

 

posted @ 2021-03-16 17:40  ztlsw  阅读(55)  评论(0编辑  收藏  举报