2.5 图论
DAG 没有圈的有向图
2.5.3 图的搜索
本节提到了“二分图判定”这个题目。首先,我们先了解一下什么是二分图呢?
给定一个图,给图中每一个结点涂上颜色,相邻结点的颜色不能相同,这就是图的着色问题。对图进行染色需要的最小颜色数就称为最小着色数。如果一个图仅仅需要两种颜色就可以满足相邻节点颜色不重复的情况,那么这个图就称作二分图,也就是最小着色数是2。
解决思路:
可以在纸上随机画一个简单的图,试着自己来涂色。先涂第一个点v1为红色,然后看和v1相邻的结点{v2,v3},都不能涂成红色了,我们给他们涂上绿色,依次再观察{v2,v3}的相邻结点是否可以涂成红色,依次往下判断下去。
代码实现:
dfs,由于每一个点和边都访问了一次,所以复杂度是O(V+E)。dfs还可以求图的拓扑序。
/*
二分图
*/
#include <iostream>
#include <cstdio>
#include <vector>
using namespace std;
const int MAX_V = 1000;
vector<int> G[MAX_V]; // 图 邻接表表示 G[s].push_back(t); s--->t
int V,E;
int color[MAX_V]; // 标记每一个结点的颜色
bool dfs(int v, int c) {
color[v] = c; // 先将起点 v 染色成 c
for (int i = 0; i < G[v].size(); i++) {
// 找 v 的邻接点
// 判断该点有没有颜色
if (color[G[v][i]] == c) return false; // 已经涂色成c了,就直接返回不是二分图
// 没有涂,就涂上-c ,进行dfs递归操作,如果递归返回上来的值是false,那也说明不是二分图
if (color[G[v][i]] == 0 && !dfs(G[v][i], -c)) return false;
}
// 都染过色了,而且没毛病,那么就说明是二分图咯
return true;
}
void solve() {
for (int i = 0; i < V; i++) {
if (color[i] == 0) {
if (!dfs(i, 1)){
printf("no\n");
}
}
}
printf("yes\n");
}
int main(){
cin>>V>>E;
for (int i = 0; i < E; i++){
int m,n;
cin>>m>>n;
G[m].push_back(n);
G[n].push_back(m);
}
solve();
return 0;
}
2.5.4 最短路问题
最短路是给定起点和终点,求出总长度最短的路径(长度就是途径的边的权值之和)
单源最短路径是固定一个点,如源点s,求该点到其余所有点的最短路径,其复杂度与求两点间最短路径相同。
<1>. 单源最短路径(Bellman-Ford算法)
前提条件:给定的图是DAG图, 使用拓扑序给顶点编号,通过递归关系求出d。
递推关系:记起点S到顶点i的最短距离为d[i]
,两点间距离用dist[i][j]
表示,那么d[i] = min{d[j] + dist[j][i]|e = (j,i)∈E}
该等式成立。(DP)
以下是我模拟的例子,每一条边在edge数组中的存放顺序是随机的,按照Bellman算法的思想,用dist[i]标记是否源点到顶点i的最短距离,初始化时,只有源点的dist[i]是0,其余的都是INF。所以,首先需要找到源点充当起点的边,就需要循环|E|次。然而一次循环并不一定可以得到所有点的最短距离,所以在之前的循环外层还需要套上一层循环。设置一个标记变量,待dist[i]不再需要修改的时候,就说明已经找到了最短路径。
仔细思考一下,外层循环需要几次呢?可以看出来,这个次数和在数组中变得存放顺序有关。假设初始点是V0,在数组中存放时,将V0为起点的边在数组的前面,也就是按照拓扑序,这样依次可以得到V0到各个点的距离,这是最好的情况,一次循环就可以搞定;如果存放顺序刚好和拓扑序是反的,比如每次内层for循环找到最后一条边才发现起点是V0的,这种情况是最复杂的,需要|V|-1次while(true)循环。该算法的时间复杂度为O(V*E)。
代码实现:
struct edge {
int from, to, cost; }; // 边的定义:起点、终点、权值
edge es[MAX_E]; // 存储图中众多边的数组
int dist[MAN_V]; // 源点到每一个点的最短路径长度
int V,E; // 顶点数,边数
// 源点S到其余点的最短距离
void shortest_path(