树也是一种特殊的图,图上每一个点有出度和入度,对于有向图来说,出度表示顶点的出边,入度表示顶点的入边,图可根据是否有边权分为有权图和无权图,一般来说,对于无权图求最短路时,可以将其赋值为1。可以证明,有向无环图一定存在拓扑序列,因此有向无环图又被成为拓扑图。具体看如下。
建图
邻接矩阵:邻接矩阵通过一个二维数组a[i][j]来存,首先通过memset(a,0x3f,sizeof(a))来给其赋初值为无穷大,每读入一条边就给其赋值为边权值,表示一条边从i指向j,权值为a[i][j],若无向图存两遍即可,时间复杂度和空间复杂度都为O(n^2),不难发现,邻接矩阵存图对时间和空间的消耗都较大,适用于存稠密图,但若是存储稀疏图,会造成大量的空间浪费。故当输入边集较多时,一般不采用邻接矩阵
邻接表:
数组实现
(模板来自于y总,个人感觉这种写法更像链式前向星...?)
// 对于每个点k,开一个单链表,存储k所有可以走到的点。h[k]存储这个单链表的头结点
int h[N], e[N], ne[N], idx;
// 添加一条边a->b
void add(int a, int b)
{
e[idx] = b, ne[idx] = h[a], h[a] = idx ++ ;
}
// 初始化
idx = 0;
memset(h, -1, sizeof h);
vector实现
#include<iostream>
#include<queue>
#include<vector>
using namespace std;
const int N = 1e3;
int n, m;//n为点数,m为边数
vector<pair<int, int>>h[N]; //N为边的起始点,first为边的入点,second为边的权值
struct node {
int id; int d;//编号和距离
node(){}
node(int id,int d):id(id),d(d){}
//重新建立优先级,实现小根堆
bool operator<(const node& A) const {
return d > A.d;
}
};
int main()
{
cin >> n >> m;
while (m--)
{
int x, y, z;
cin >> x >> y >> z;
h[x].push_back(make_pair(y, z));//有向图的存法
}
}
需要注意的是,因为求最短路时(如dijkstra)需要用到优先队列,但是优先队列默认大根堆,因此需要在结构体中修改他的优先级,将大根堆改为小根堆,但这并不是邻接表存图的主要内容。
链式前向星:
int head[maxn];//表示以i为起点的最后一个点的编号,初始为-1
int cnt = 1;//cnt表示边的编号
struct node {
int v, w, next;//next表示与这条边起点相同的上一条边的编号
}edge[maxn];
void addedge(int u, int v, int w)
{
edge[cnt].v = v;
edge[cnt].w = w;
edge[cnt].next = head[u];
head[u] = cnt++;
}
//初始化
memset(head, -1, sizeof(head));
/*若要遍历图, i表示点,j首先为以i为起点的最后一个点的编号,j不为 - 1
则证明还有连通的边,j又为与这条边起点相同的上一条边,若遇见-1,则表示
已经没有以i为起点的边了,结束循环,遍历下一个点
*/
for(int i = 0;i < n;i++)
for(int j = head[i];j != -1;j = edge[j].next)
图的遍历
图的深度优先遍历(基于链式前向星建图):
int dfs(int u)
{
st[u] = true; // st[u] 表示点u已经被遍历过
for (int i = head[u]; i != -1; i = edge[i].next)
{
int j = e[i];
if (!st[j]) dfs(j);
}
}
图的宽度优先遍历:
queue<int> q;
st[1] = true; // 表示1号点已经被遍历过
q.push(1);
while (!q.empty())
{
int t = q.front();
q.pop();
for (int i = head[t]; i != -1; i = edge[i].next)
{
int j = edge[i].next;
if (!st[j])
{
st[j] = true; // 表示点j已经被遍历过
q.push(j);
}
}
}
最短路是图论里一个重要的问题,给定一个图,求一条从起点到终点的路径,使得边权和最小。
最短路分为单源最短路径(从某个源点到其他各个顶点的最短距离)和多源最短路径(任意两个顶点之间的最短距离),一般情况来说,对于只有正权边的图,一般采用dijkstra算法,朴素版的dijkstra算法时间复杂度为O(n^2),堆优化版的dijkstra时间复杂度为O(mlogn),若是存在负权边,则可以选择Bellman-Ford算法或者SPFA算法,前者时间复杂度为O(nm),而后者时间复杂度一般为O(m),最坏为O(nm),一般来说,SPFA算法各方面都要优于Bellman-Ford算法,但是Bellman-Ford算法可以处理一些指定情况,比如限制边数,这是SPFA算法不能做到的,需要注意的是,若是图中存在负环,则最短路又可能不存在,而Bellman-Ford算法和SPFA算法都可以判断负环(一般使用SPFA),并且大多数情况,SPFA算法也是可以替代dijkstra算法的(也可能会被卡),对于求多源汇最短路,我们一般采用基于动态规划思想的Floyd算法,时间复杂度为O(n^3)(可以发现与边的数量无关,只与点的数量有关),代码如下。
dijkstra(朴素版)
#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
const int maxn=510;
int g[maxn][maxn],dis[maxn];
bool vis[maxn];
int n,m;
int dijkstra()
{
memset(dis,0x3f,sizeof dis);
dis[1]=0;
for(int i=0;i<n;i++)
{
int t=-1;
for(int j=1;j<=n;j++)
{
if(!vis[j]&&(t==-1||dis[j]<dis[t])) t=j;
}
vis[t]=1;
for(int j=1;j<=n;j++)
{
dis[j]=min(dis[j],dis[t]+g[t][j]);
}
}
if(dis[n]==0x3f3f3f3f) return -1;
return dis[n];
}
int main()
{
memset(g,0x3f,sizeof g);
scanf("%d%d",&n,&m);
while(m--)
{
int a,b,c;
scanf("%d%d%d",&a,&b,&c);
g[a][b]=min(g[a][b],c);//有向图
}
int res=dijkstra();
printf("%d",res);
return 0;
}
dijkstra(堆优化版)
#include <iostream>
#include <cstring>
#include <algorithm>
#include <queue>
using namespace std;
int n,m;
const int maxn=500,maxm=1e5+5;
int dis[maxn];
bool vis[maxn];
int h[maxm],e[maxm],ne[mexm],w[mexm],idx;
void add(int a,int b,int c)
{
w[idx]=c,e[idx]=b,ne[idx]=h[a],h[a]=idx++;
}
int dijkstra()
{
memset(dis,0x3f,sizeof dis);
dis[1]=0;
priority_queue<pii,vector<pii>,greater<pii>>q;
q.push(mp(0,1));
while(!q.empty())
{
auto t=q.top();
q.pop();
int ver=t.second,dist=t.first;
if(vis[ver]) continue;
vis[ver]=1;
for(int i=h[ver];i!=-1;i=ne[i])
{
int j=e[i];
if(dist+w[i]<dis[j])
{
dis[j]=dist+w[i];
q.push(mp(dis[j],j));
}
}
}
if(dis[n]==0x3f3f3f3f) return -1;
return dis[n];
}
int main()
{
memset(h,-1,sizeof h);
scanf("%d%d",&n,&m);
while(m--)
{
int a,b,c;
scanf("%d%d%d",&a,&b,&c);
add(a,b,c);
}
int res=dijkstra();
printf("%d",res);
return 0;
}
Bellman-Ford
(最多只能经过k条边)
#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
const int maxn=510,maxm=1e4+5;
int dis[maxn],backup[maxn];
struct Edge{
int a,b,w;
}edges[maxm];
int m,n,k;
void Bellman_Ford()
{
memset(dis,0x3f,sizeof dis);
dis[1]=0;
for(int i=0;i<k;i++) //有边数限制,最多只能经过k条边的最短路
{
memcpy(backup,dis,sizeof dis); //备份一下dis数组,防止发生串联,若没有边数限制则不需要备份
for(int j=0;j<m;j++)
{
int a=edges[j].a,b=edges[j].b,w=edges[j].w;
dis[b]=min(dis[b],backup[a]+w);
}
}
}
int main()
{
scanf("%d%d%d",&n,&m,&k);
for(int i=0;i<m;i++)
{
int a,b,c;
scanf("%d%d%d",&a,&b,&c);
edges[i]={a,b,c};
}
Bellman_Ford();
if(dis[n]>0x3f3f3f3f/2) puts("impossible");
else printf("%d",dis[n]);
return 0;
}
SPFA
#include <iostream>
#include <cstring>
#include <algorithm>
#include <queue>
using namespace std;
const int maxn=1e5+5;
int h[maxn],ne[maxn],w[maxn],e[maxn],idx;
int dis[maxn];
bool vis[maxn];
int n,m;
void add(int a,int b,int c)
{
e[idx]=b,w[idx]=c,ne[idx]=h[a],h[a]=idx++;
}
void spfa()
{
memset(dis,0x3f,sizeof dis);
dis[1]=0;
queue<int>q;
q.push(1);
vis[1]=1;
while(q.size())
{
int t=q.front();
q.pop();
vis[t]=0;
for(int i=h[t];i!=-1;i=ne[i])
{
int j=e[i];
if(dis[j]>dis[t]+w[i])
{
dis[j]=dis[t]+w[i];
if(!vis[j])
{
q.push(j);//如果不在队列中就加入队列
vis[j]=1;
}
}
}
}
}
int main()
{
memset(h,-1,sizeof h);
scanf("%d%d",&n,&m);
while(m--)
{
int a,b,c;
scanf("%d%d%d",&a,&b,&c);
add(a,b,c);
}
spfa();
if(dis[n]==0x3f3f3f3f) puts("impossible");
else printf("%d\n",dis[n]);
return 0;
}
SPFA判断负环
bool spfa()
{
queue<int>q;
for(int i=1;i<=n;i++)
{
q.push(i);
vis[i]=1;
}
while(!q.empty())
{
auto t=q.front();
q.pop();
vis[t]=0;
for(int i=h[t];i!=-1;i=ne[i])
{
int j=e[i];
if(dis[j]>dis[t]+w[i])
{
dis[j]=dis[t]+w[i];
cnt[j]=cnt[t]+1;
if(cnt[j]>=n) return true;
if(!vis[j]){
q.push(j);
vis[j]=1;
}
}
}
}
return false;
}
Floyd
#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
const int maxn=210,inf=0x3f3f3f3f;
int d[maxn][maxn];
int n,m,q;
void init()
{
for(int i=1;i<=n;i++)
for(int j=1;j<=n;j++)
if(i==j) d[i][j]=0;
else d[i][j]=inf;
}
void floyd()
{
for(int k=1;k<=n;k++)
{
for(int i=1;i<=n;i++)
{
for(int j=1;j<=n;j++)
{
d[i][j]=min(d[i][j],d[i][k]+d[k][j]);
}
}
}
}
int main()
{
scanf("%d%d%d",&n,&m,&q);
init();
while(m--)
{
int a,b,c;
scanf("%d%d%d",&a,&b,&c);
d[a][b]=min(d[a][b],c);
}
floyd();
while(q--)//q次询问
{
int a,b;
scanf("%d%d",&a,&b);
if(d[a][b]>inf/2) puts("impossible");
else printf("%d\n",d[a][b]);
}
return 0;
}
具体详情请见http://acwing.com