2.5.1 图是什么
- n个顶点的无向图要连通至少需要(n-1)条边
E.g.2019山东省赛D - 一棵树的边数 = 定点数 - 1
边数 = 定点数 - 1 的连通图是树
无圈的有向图叫DAG(Directed Acyclic Graph),在DAG中对每个顶点给出一个编号,第i号顶点叫做vi。那么存在vi到vj的边时就有i<j成立。这样的编号方式叫做拓扑序。
有些DAP问题可以用dp解决。求解拓扑序的算法叫做拓扑排序。
2.5.2 图的表示
-
邻接矩阵
使用|V|x|V|的二维数组存储。无向图必有g[i][j] = g[j][i]。
在带权图中,令g[i][j] = INF,通常保存权值最小/最大的边即可。 -
邻接表
在边数稀少时相交邻接矩阵占用内存更少,但实现起来更复杂。
并且,在邻接表中查询两点之间是否有边需要遍历一遍链表才能知道。
#include <iostream>
#include <vector>
#include <cstdio>
using namespace std;
#define MAX_V 10000
vector<int> G[MAX_V];
/*
边上有属性的情况
struct edge
{
int to, cost;
};
vector<edge> G[MAX_V];
*/
int main()
{
int V, E;
scanf ("%d %d", &V, &E);
for(int i = 0;i < E;i++)
{
//s --> p
int s, t;
scanf("%d %d", &s, &t);
G[s].push_back(t);
//如果是无向图,则需要再从t向s连边
}
return 0;
}
#include <iostream>
#include <vector>
#include <cstdio>
using namespace std;
#define MAX_V 10000
struct vertex
{
vector<vertex*> edge;
/*
顶点的属性
*/
};
vertex G[MAX_V];
int main()
{
int V, E;
scanf("%d %d", &V, &E);
for(int i = 0;i < E;i++)
{
int s, t;
scanf("%d %d", &s, &t);
G[s].edge.push_back(&G[t]);
//G[t].edge.push_back(&G[s]);
}
/*
图的操作
*/
return 0;
}
2.5.3 图的搜索
/*二分图判定*/
#include <iostream>
#include <vector>
#include <cstdio>
using namespace std;
#define MAX_V 10000
vector<int> G[MAX_V]; //graph
int V; //vertex sum
int color[MAX_V]; //顶点i的颜色(1, -1)
bool dfs(int v, int c)
{
color[v] = c; //把顶点v染成颜色c
for(int i = 0;i < G[v].size();i++)
{
//相邻顶点同色
if(color[G[v][i]] == c)
return false;
//尚未染色,染色为-c
if(color[G[v][i]] == 0 && !dfs(G[v][i], -c))
return false;
}
//循环能完整结束说明所有顶点均被染色过
return true;
}
int main()
{
scanf("%d", &V);
for(int i = 0;i < V;i++)
{
if(color[i] == 0)
{
//如果顶点i尚未染色,则染色为1
if(!dfs(i, 1))
{
printf("No\n");
return;
}
}
}
printf("Yes\n");
return 0;
}
2.5.4 最短路
- 单源最短路问题
存在负边: Bellman-Ford, Floyd-Warshall(复杂度较低时)
不存在负边:Dijkstra
Bellman-Ford
求最短路,检查负图
#include <iostream>
using namespace std;
#define MAX_E 1000
struct edge
{
int from,
to,
cost;
//由from顶点指向to顶点 值为cost的边
};
edge es[MAX_E]; //边
int d[MAX_V]; //最短距离
int V, //顶点数
E; //边数
//求解从顶点s出发到所有点的最短距离
void shortest_path(int s)
{
for(int i = 0;i < V;i++)
d[i] = INF;
d[s] = 0;
while(true)
{
bool update = false;
for(int i = 0;i < E;i++)
{
edge e = es[i];
if(d[e.from] != INF && d[e.to] > d[e.from] + e.cost)
{
d[e.to] = d[e.from] + e.cost;
update = true;
}
}
if(!update)
break;
}
}
Dijkstra
使用邻接矩阵实现,复杂度为O(|V^2|)
#include <iostream>
using namespace std;
#define MAX_V 10000
int cost[MAX_V][MAX_V]; //cost[u][v]表示边e=(u,v)的权值(不存在这条边时设为INF)
int d[MAX_V]; //顶点s出发的最短距离
bool used[MAX_V]; //已经使用过的图
int V;
//求从起点出发到各个顶点的最短距离
void dijkstra(int a)
{
fill(d, d+V, INF);
fill(used, used+V, false);
d[s] = 0;
while(true)
{
int V = -1;
//从尚未使用过的顶点中选择一个距离最小的顶点
for(int u = 0;u < V;u++)
{
if(!used[u] && (V == -1 || d[u] < d[v]))
v = u;
}
if(v == -1)
break;
used[v] = true;
for(int u = 0;u < V;u++)
{
d[u] = min(d[u], d[v] + cost[v][u]);
}
}
}
用堆优化数值的插入(更新)和取出最小值两个操作
#include <iostream>
#include <vector>
#include <queue>
using namespace std;
#define MAX_V 10000
#define INF 99999999
struct edge
{
int to,
cost;
};
typedef pair<int, int> P; //first为最短距离,second为顶点编号
int V;
vector<edge> G[MAX_V];
int d[MAX_V];
void dijkstra(int s)
{
//通过指定greater<P>参数,堆按照从小到大顺序排序
priority_queue<P, vector<P>, greater<P> > que;
fill(d, d + V, INF);
d[s] = 0;
que.push(P(0, s));
while(!que.empty())
{
P p = que.top();
que.pop();
int v = p.second;
if(d[v] < p.first)
continue;
for(int i = 0;i < G[v].size();i++)
{
edge e = G[v][i];
if(d[e.to] > d[v] + e.cost)
{
d[e.to] = d[v] + e.cost;
que.push(P(d[e.to], e.to));
}
}
}
}
Floyd-Warshall
求解任意两点间的最短路
#include <iostream>
using namespace std;
#define MAX_V 1000
int d[MAX_V][MAX_V]; //d[u][v]表示e=(u,v)的权值(不存在时设置为INF,不过d[i][i] = 0)
int V; //顶点数
void warshall_floyd()
{
for(int k = 0;k < V;k++)
for(int i = 0;i < V;i++)
for(int j = 0;j < V;j++)
d[i][j] = min(d[i][j], d[i][k] + d[k][j]);
}