首页 > 其他分享 >图论

图论

时间:2024-02-18 17:55:05浏览次数:31  
标签:图论 dist int Dijkstra 算法 Part dis

图论

Part 1 图

Part 1.1 简介

一张图 $G$ 由若干个点和连接这些点的边构成。称点的集合为 点集 $V$,边的集合为 边集 $E$,记 $G=(V,E)$。

Part 1.2 图的种类

图大体上分为两种:边没有指向性的图称为无向图,有指向性的图称为有向图。

我们可以给点和边赋予属性,例如权值(cost)。边上带有权值的图称为带权图。

途径:连接一串结点的序列称为 途径,用点序列 $v_{0..k}$​ 和边序列 $e_{1..k}$ 描述,其中 $e_i=(v_{i-1},v_i)$。通常写为 $0​→v_1​→⋯→v_k$​。

连通:对于无向图的两点 $u,v$,若存在途径使得 $v_0=u$ 且 $v_k=v$,则称 $u,v$ 连通

弱连通:对于有向图的两点 $u,v$,若将有向边改为无向边后 $u,v$ 连通,则称 $u,v$ 弱连通

连通图:任意两点连通的无向图称为 连通图

弱连通图:任意两点弱连通的有向图称为 弱连通图

简单图:不含重边和自环的图称为 简单图

基图:将有向图的所有有向边替换为无向边得到的图称为该有向图的 基图

有向无环图:不含环的有向图称为 有向无环图,简称 DAG(Directed Acyclic Graph)。

稀疏图 / 稠密图: $|E|$ 远小于 $|V|^2$ 的图称为 稀疏图,$|E|$ 接近 $|V|^2$ 的图称为 稠密图

约定

一般记 $n$ 表示点集大小 $|V|$,$m$ 表示边集大小 $|E|$。

Part 1.3 度

有向图中,顶点的度分为入度和出度。

  • 入度:以该顶点为头的弧的数目;
  • 出度:以该顶点为尾的弧的数目。

Part 2 图的存储

Part 2.1 邻接矩阵

邻接矩阵使用 $n^2$ 的二维数组来表示图。$g_{i,j}$ 表示点 $i$ 与点 $j$ 是否有边相连。

Part 2.2 邻接表

Part 2.2.1 vector存图

在邻接矩阵中,有很多的空间被浪费了。使用 vector 可以节省很多空间,使得空间复杂度达到 $\mathcal{O}(m)$。

vector<int> g[100010]; //定义

//设有一条边为 u -> v

g[u].push_back(v); //存边

//设从u点开始遍历

for(int i=0;i<g[u].size();i++)
{
    int v=g[u][i]; // u -> v
    // ...
}

Part 2.2.2 链式前向星

前置知识:链表

使用 vector 存图常数大,因此我们使用链式前向星来实现邻接表。

建立结构体:

struct edge
{
    int to,w,nxt;
};

其中 $to$ 为终点,$w$ 为权值,$nxt$ 为下一条边的存储位置。

另外一个数组 $head$,表示以 $i$ 为起点的第一条边存储的位置。由于在插入链表的时候使用 头插法,所以在链式前向星是以倒序存储的。

void add(int u,int v,int w)
{
    a[++cnt]=edge{v,w,head[u]};
    head[u]=cnt;
}

遍历:

for(int i=head[u];i;i=a[i].nxt)
	// ...

Part 3 最短路

Part 3.1 Dijkstra

Part 3.1.1 Dijkstra 朴素算法

洛谷 P3371 【模板】单源最短路径(弱化版)

Dijkstra 算法

Dijkstra 算法的流程如下:

  1. 初始化 $dist[1]=0$,其余节点的 $dist$ 值为正无穷大。
  2. 找出一个未被标记的、 $dist[x]$ 的最小节点 $x$,然后标记节点$x$。
  3. 扫描节点 $x$ 的所有出边 $(x,y,z)$,若 $dist[y]>dist[x]+z$,则使用 $dist[x]+z$ 更新 $dist[y]$。
  4. 重复上述 2~3 两个步骤,知道所有节点都被标记。

Dijkstra 算法基于贪心思想,它只适用于所有边的长度都是非负数的图。Dijkstra 算法的时间复杂度为 $\mathcal{O}(n^2)$。

struct edge{
	int z,w;
};
int n,m,s,u,v,w,dis[100010],vis[100010];
vector<edge> a[100010];
void dijkstra(int s)
{
	fill(dis+1,dis+1+n,INT_MAX);
	dis[s]=0;
	for(int i=1;i<n;i++)
	{
		int x=0;
		for(int j=1;j<=n;j++)
			if(!vis[j]&&(x==0||dis[j]<dis[x]))
				x=j;
		if(x==0) return ;
		vis[x]=1;
		for(int j=0;j<a[x].size();j++)
			if(!vis[a[x][j].z])
				dis[a[x][j].z]=min(dis[a[x][j].z],dis[x]+a[x][j].w);
	}
}

Part 3.1.2 Dijkstra 堆优化

观察 Dijkstra 的流程,我们发现步骤2可以优化。

我们可以用堆对 $dist$ 数组进行维护,用 $\mathcal{O}(\log n)$ 的时间取出堆顶元素并删除,用 $\mathcal{O}(\log n)$ 的时间遍历每条边,总时间复杂度为 $\mathcal{O}((n+m) \log n)$。

struct edge
{
	int z,w;
	bool operator <(const edge &x)const
	{
		return x.w<w;
	}
};
int n,m,s,u,v,w,dis[100010],vis[100010];
vector<edge> a[100010];
priority_queue<edge> q;
void dijkstra(int s)
{
	memset(dis,0x3f,sizeof dis);
	q.push(edge{s,0});
	dis[s]=0;
	while(!q.empty())
	{
		int x=q.top().z;
		q.pop();
		if(vis[x]) continue;
		vis[x]=1;
		for(int i=0;i<a[x].size();i++)
		{
			int y=a[x][i].z,l=a[x][i].w;
			if(dis[y]>dis[x]+l&&!vis[y])
			{
				dis[y]=dis[x]+l;
				q.push(edge{y,dis[y]});
			}
		}
	}
}

Part 3.2 Bellman-Ford & SPFA

Part 3.2.1 Bellman-Ford算法

将所有边进行 $n-1$ 次松弛操作,在求解它的过程中,每次循环都要修改所有顶点对应的 $dist$ 数组的值,最短路径长度直到算法结束才能确定。算法的时间复杂度为:$\mathcal{O}(nm)$。

void BellmanFord(int s)
{
	memset(dis,0x3f,sizeof dis);
	dis[s]=0;
	for(int i=1;i<n;i++)
	{
		for(int j=1;j<=n;j++)
		{
			if(dis[j]==0x3f3f3f3f) continue;
			for(int k=0;k<a[j].size();k++)
				if(dis[a[j][k].z]>dis[j]+a[j][k].w)
					dis[a[j][k].z]=dis[j]+a[j][k].w;
		}
	}
}

Part 3.2.2 SPFA算法

SPFA 算法,即 Shortest Path Faster Algorithm,最短路径快速算法。

SPFA 算法在 Bellman-Ford 算法上进行了优化,将待更新的 $dist$ 数组的对应顶点存进队列里,每次取出队首元素 $u$,将与 $u$ 相连的节点进行松弛,如果节点 $y$ 不在队列里,且 $dis[y]$ 可以更新,那么将节点 $y$ 入队。

与 BFS 不同的是,SPFA 算法可以将一个节点多次入队。

SPFA 算法的时间复杂度为 $\mathcal{O}(km)$ ,其中 $k$ 是一个较小的常数。但是,如果题目数据故意卡 SPFA ,那么 SPFA 的时间复杂度会退化为 $\mathcal{O}(km)$。相较于 SPFA,Dijkstra 算法更为稳定。

标签:图论,dist,int,Dijkstra,算法,Part,dis
From: https://www.cnblogs.com/xiaoyukai/p/18019722

相关文章

  • 找负环(图论基础)
    目录负环spfa找负环方法一方法二实际效果负环环内路径上的权值和为负。spfa找负环两种基本的方法统计每一个点的入队次数,如果一个点入队了n次,则说明存在负环统计当前每个点中的最短路中所包含的边数,如果当前某个点的最短路所包含的边数大于等于n,也说明存在负环实际上两......
  • 基础图论笔记
    二分图  染色法  例题ACwing860-染色法判断二分图(染色法)-CSDN博客给定一个n个点m条边的无向图,图中可能存在重边和自环。请你判断这个图是否是二分图。输入格式第一行包含两个整数n和m。接下来m行,每行包含两个整数u和v,表示点u和点v之间存在一条边。输出格式如......
  • 图论题目合集
    题面:要求把\(1\simn\)排序,满足给定的所有条件,满足条件之后,编号越小要越靠前。(满足条件情况下,先让1最靠左,然后让2……)每个条件会给出两个数\(a,b\),表示\(a\)必须在\(b\)之前。解答:看起来很像拓扑排序。于是我们对于每个条件\(a,b\),从\(a\)向\(b\)连一条边。每......
  • 【学习笔记】关于图论的一切
    存图邻接矩阵边集邻接表最小生成树primkruskal最短路dij堆优化spfafloyd欧拉路欧拉回路scc缩点2-sAT二分图基础概念匈牙利DAG最小链覆盖网络流Dinic最小割最大权闭合子图最小割集费用流Zkw双连通问题割边割点双连通分量圆方树生成树计数......
  • 图论笔记
    最短路相关最短路基础\(\mathbf{Floyed}\)求最短路本质上是dp。设\(f(w,i,j)\)表示当前松弛到第\(w\)轮,\(i\rightarrowj\)的最短路是\(f(w,i,j)\)。转移显然是:\[f(w,i,j)=f(w-1,i,k)+f(w-1,k,j)\]\(w\)显然可以滚掉。时间复杂度\(O(n^3)\)......
  • 图论——连通性
    图论——连通性基本知识路径相关途径:连接一串结点的序列称为途径,用点序列\(v_{0..k}\)和边序列\(e_{1..k}\)描述,其中\(e_i=(v_{i-1},v_i)\),通常写为\(v_0\tov_1\to\cdots\tov_k\)。迹:不经过重复边的途径称为迹。回路:\(v_0=v_k\)的迹称为回路。......
  • NOIP 图论[ZHX]
    基础定义图图\(G\)是一个有序二元组\((V,E)\),其中\(V\)成为点集(\(Vertices\)\(Set\)),\(E\)称为边集(\(Edges\)\(set\))。有向边、无向边如果边有方向,那么得到的图称为有向图。在有向图中,与一个节点相关联的有出边和入边之分。相反,边没有方向的图称为无向图,即所有边都......
  • 冯梓轩图论总结2
    图论学习总结2拓补排序当给定的一张图是一张DAG(有向无环图)时,可以对该图进行拓补排序,在\(O(n+m)\)的时间内转移一些信息。通常用队列进行实现。拓补排序经常与其他算法进行结合,如DP。例题[POI2015]PUS(Pustynia)当一些数之间只给定了大小关系,要求一组可行解时,可以考虑......
  • 图论算法学习笔记
    ybt1376floyd#include<iostream>#include<climits>#include<cstring>#include<queue>#include<vector>#defineinfinity0x3f3f3f3f#defineN105intn,m,G[N][N],dist[N][N];intmain(){ memset(dist,infinity,sizeof(dist)); st......
  • 20240203-图论随记
    最短路负环判断#include<bits/stdc++.h>usingnamespacestd;structnode{intfrom,to,v;}edge[100005];#defineoo2000000000intdis[100005];intmain(){intn,m,s,t;cin>>n>>m>>s>>t;for(inti=1;i<=m;i++){......