图的全家桶

图论还是来个全家桶吧,其实图论这种东西还是蛮好理解的

-1.什么是图

图(Graph)是表示物件与物件之间的关系的数学对象,是图论的基本研究对象。一个不带权图中若两点不相邻,邻接矩阵相应位置为0,对带权图(网),相应位置为∞。

有向图与无向图

如果给图的每条边规定一个方向,那么得到的图称为有向图。在有向图中,与一个节点相关联的边有出边和入边之分。相反,边没有方向的图称为无向图。

图的术语

阶(Order):

图G中顶集V的大小称作图G的阶。

子图(Sub-Graph):

当图G'=(V',E')其中V‘包含于V,E’包含于E,则G'称作图G=(V,E)的子图。每个图都是本身的子图。

生成子图(Spanning Sub-Graph):

指满足条件V(G') = V(G)的G的子图G'。

导出子图(Induced Subgraph):

以图G的顶点集V的非空子集V1为顶点集,以两端点均在V1中的全体边为边集的G的子图,称为V1导出的导出子图;以图G的边集E的非空子集E1为边集,以E1中边关联的顶点的全体为顶点集的G的子图,称为E1导出的导出子图。

度(Degree):

一个顶点的度是指与该顶点相关联的边的条数,顶点v的度记作d(v)。

入度(In-degree)和出度(Out-degree):

对于有向图来说,一个顶点的度可细分为入度和出度。一个顶点的入度是指与其关联的各边之中,以其为终点的边数;出度则是相对的概念,指以该顶点为起点的边数。

自环(Loop):

若一条边的两个顶点为同一顶点,则此边称作自环。

路径(Path):

从u到v的一条路径是指一个序列v0,e1,v1,e2,v2,...ek,vk,其中ei的顶点为vi及vi - 1,k称作路径的长度。如果它的起止顶点相同,该路径是“闭”的,反之,则称为“开”的。一条路径称为一简单路径(simple path),如果路径中除起始与终止顶点可以重合外,所有顶点两两不等。

行迹(Trace):

如果路径P(u,v)中的边各不相同,则该路径称为u到v的一条行迹。

轨道(Track):

如果路径P(u,v)中的顶点各不相同,则该路径称为u到v的一条轨道。

0. 图的存储

邻接矩阵

实际上就是一个二维数组,我们假设它是G,那么\(G[i][j]=k\)就表示\((i,j)\)之间有一条长为k的边,这里我们插入和查询的都是\(O(1)\)的,但是我们的空间复杂度达到\(O(n^2)\)的,所以实际上并不是特别实用

领接表

这里我们就上到我们比较高大上的领接表了,我们用它遍历就比较简单了
邻接表,存储方法跟树的孩子链表示法相类似,是一种顺序分配和链式分配相结合的存储结构。如这个表头结点所对应的顶点存在相邻顶点,则把相邻顶点依次存放于表头结点所指向的单向链表中。
对于下面这幅图
这里写图片描述
我们所建出来的邻接表就是这样的
这里写图片描述
刚刚开始这个我们还是不能理解这个神奇的东西,那么我们就可以先看下代码再来理解。对于建图中最重要的就是\(addedge\)函数了
我们就来看下\(addedge\)函数的具体的代码

void addedge(int x,int y){
    nxt[++tot]=head[x];
    head[x]=tot;
    to[tot]=y;
}

代码很短这里我建了一条有向但是没有边权的边
ps:其实无向图就正着建一次,反着在建一次

head[x]:从点x出发的最后一条边的标号
nxt[i]:与标号i从同一点出发的下一条边
to[i]:标号i的边到的点

我们遍历的代码就很简单了

for(int i=haed[x];i;i=nxt[i]){
    int v=to[i];
    //do something
}

当然我们这里可能会遇到一些边是还有一个属性的,那就是边权,那么我们代码就是长这样的

void addedge(int x,int y,int z){
    nxt[++tot]=head[x];
    head[x]=tot;
    to[tot]=y;
    cost[tot]=z;
}

和上面同一理论cost[i]:标号i的边的权值

1.搜索

显然图论中许多算法都是基于搜索的啊,但是搜索的顺序可能有一点不同

这是所有算法中最最基础的吧,这里也就不多说了,也就是按深度来搜索,代码大多数时候就是递归实现的啊

void dfs(int x){
    if(some resons)return;
    //do someting
    for(int i=head[x];i;i=nxt[i]){
        int v=to[i];
        if(!vis[v]){
            vis[v]=1;
            dfs(v);
        }
        //do someting
    }
    //do someting
}

这种算法其实就是一种以广度或者说宽度来决定优先顺序的算法,它往往是以队列为基础的一种算法
代码也就很简单,主要就是以队列为核心

void bfs(int s){
    queue<int> q;
    q.push(s);
    vis[s]=1;
    //do something
    while(!q.empty()){
        int x=q.front();
        q.pop();
        for(int i=head[x];i;i=nxt[i]){
            int v=to[i];
            //do something
            if(!vis[v]){
                //do something
                vis[v]=1;
                q.push(v);
            }
        }
    }
}

2.最短路算法

松弛

这里我们将松弛操作单独来讲,它算是所有最短路算法的核心
我们可以看个很有趣的东西
令我们找到当前\(i,j\)之间的最短路为\(dis(i,j)\)
现在我们找到一个点\(k\)
如果满足
\[dis(i,k)+dis(k,j)<dis(i,j)\]
我们就更新\(dis(i,j)=dis(i,k)+dis(k,j)\)

Bellman-Ford (SPFA)

我是真的不会Bellman-Ford啊只能讲讲它的队列优化SPFA,其实我是在\(NOIP2017\)场上突然明白了SPFA的,一定是我太弱了。直白的说,它就是BFS+松弛,代码也十分的好写
这是luogu3371 的代码,这就是一道模板题

#include<iostream>
#include<cstdio>
#include<cstring>
#include<queue>

using namespace std;

int n,m,s,tot,nxt[500001],cost[500001],to[500001],head[100001],d[100001];
bool inque[100001];

void addedge(int x,int y,int z){
    nxt[++tot]=head[x];
    head[x]=tot;
    to[tot]=y;
    cost[tot]=z;
}

void spfa(int x){
    memset(d,0x7f,sizeof(d));
    queue<int> q;
    q.push(x);
    inque[x]=1;
    d[x]=0;
    while(!q.empty()){
        int now=q.front();q.pop();
        inque[now]=0;
        for(int i=head[now];i;i=nxt[i]){
            int u=to[i];
            if(d[u]>d[now]+cost[i]){
                d[u]=d[now]+cost[i];
                if(!inque[u]){
                    q.push(u);
                    inque[u]=1;
                }
            }
        }
    }
}

int main(){
    scanf("%d%d%d",&n,&m,&s);
    for(int i=1;i<=m;i++){
        int x,y,z;scanf("%d%d%d",&x,&y,&z);
        addedge(x,y,z);
    }
    spfa(s);
    for(int i=1;i<=n;i++){
        printf("%d ",d[i]==0x7f7f7f7f?0x7fffffff:d[i]);
    }
    return 0;
}

猜你喜欢

转载自www.cnblogs.com/ezoihy/p/9431047.html