扩大
缩小

复习3图的全家桶

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

-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)\)

Floyd

Floyd就是一种基于松弛的dp啊,时间复杂度\(O(n^3)\)
这里我就不写代码了

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;
}

Dijkstra

这个算法我也不好说,直白点就是堆广搜
有时候SPFA会被卡,我们就要有Dijkstra,但是不能有负边

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

using namespace std;

int n,m,s,tot,nxt[200001],head[100001],to[200001],d[100001],cost[200001];

struct node{
    int x;int dis;
    inline bool operator < (const node &b) const {
        return dis>b.dis;
    }
};

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

void dijkstra(int xx){
    memset(d,0x7f,sizeof(d));
    d[xx]=0;
    priority_queue<node> q;
    q.push(node{xx,0});
    while(!q.empty()){
        int x=q.top().x;
        int dis=q.top().dis;
        q.pop();
        if(dis>d[x])continue;
        for(int i=head[x];i;i=nxt[i]){
            int v=to[i];
            if(d[v]>d[x]+cost[i]){
                d[v]=d[x]+cost[i];
                q.push(node{v,d[v]});
            }
        }
    }
}

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);
    }
    dijkstra(s);
    for(int i=1;i<=n;i++){
        printf("%d ",d[i]);
    }
    return 0;
}

3.Tarjan算法

Tarjan算法有很多种这里我就讲下最常见的

割点

了解定义,算法浅显

// luogu-judger-enable-o2
#include<iostream>
#include<cstdio>
#include<cstring>

using namespace std;

int n,m,ind,ans,tot,dfn[100001],low[100001],head[100001],nxt[200001],to[200001];
bool iscp[100001];

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

void find_cp(int u,int fa) {
    int child=0;
    dfn[u]=low[u]=++ind;
    for(int i=head[u];i;i=nxt[i]) {
        int v=to[i];
        if(!dfn[v]) {
            child++;
            find_cp(v,u);
            low[u]=min(low[u],low[v]);
            if(low[v]>=dfn[u])iscp[u]=1;
        } else {
            if(dfn[v]<dfn[u] and v!=fa) {
                low[u]=min(low[u],dfn[v]);
            }
        }
    }
    if(fa<0 and child==1){
        iscp[u]=0;
   }
}

int main() {
    scanf("%d%d",&n,&m);
    for(int i=1; i<=m; i++) {
    	int x,y;
        scanf("%d%d",&x,&y);
        addedge(x,y);
        addedge(y,x);
    }
    ind=0;
    for(int i=1; i<=n; i++) {
        if(!dfn[i]) {
            find_cp(i,-1);
        }
    }
    for(int i=1; i<=n; i++) {
        if(iscp[i]) {
            ans++;
        }
    }
    printf("%d\n",ans);
    for(int i=1; i<=n; i++) {
        if(iscp[i]) {
            printf("%d ",i);
        }
    }
    return 0;
}

强联通分量&缩点

posted @ 2018-08-06 16:37  ezoiHY  阅读(289)  评论(3编辑  收藏  举报