标签:fat 时间 turn 申请 头结点 双向 -o 联通 spfa
顶点 (Vertex), 边 (Edge)
有向图 , 无向图 , 无向图是一种特殊的有向图
度,有向图分为出度 和 入度,无向图的度,代表 连出去的边
顶点和边都可以具有属性,称为权重,顶点称为 点权,边 称为 边权
稠密图 边很多,大约是 顶点的平方
稀疏图 边很少 ,
重边(平行边),自环,
路径:从一给顶点到达另一个顶点称为一条路径
路径中边的数量称为路径长度,如果路径中的顶点均不重复,称为简单路径
如果路径中的第一个顶点 \(v_i\) 和最后一个顶点 \(v_j\) 是同一个顶点,则称这条路径为回路
连通 : 两个点连通,指 u 和 v 相互可达
图连通 :无向图 任意两个节点 相互可达 ,
强连通 : 有向图 任意 两个节点 相互可达
邻接矩阵 (二维数组) 对于 n 个顶点,需要 \(O(n^2)\) 空间
严重浪费空间,适用于 稠密图 , 或者是 ,题目的输入 已矩阵的方式给出
G[N] [N]
#include <iostream>
#include <cstring>
using namespace std;
const int N = 1e3 + 233,INF = 0x3f3f3f3f; //INF 最大值
int g[N][N];
int main(){
int n,m,x,y,w;
memset(g,0x3f,sizeof g); // 初始化图,全都不联通
cin >> n >> m;
while(m--){
cin >> x >> y >> w;
g[x][y] = w; // 单向边,x to y ,权重 为 w
g[y][x] = w; // 两条都写,等于双向边, 图中都为双向边,就成无向图了。
}
for(int i = 1;i <= n; ++i){
for(int j = 1;j <= n; ++j){
cout << g[i][j] <<" ";
}
cout << endl;
}
return 0;
}
邻接表
对图中的每个顶点都建立一个单链表,存储这个顶点的连出的点
vector
vector 自身底层函数的缺陷, 每次扩大空间,都会copy一边,然后扩大原来空间的两倍
vector
优点:
1.写起来比链式前向星快(大概
2.每个顶点的出边都是用vector存储的,方便执行一些STL中的函数(比如排序)
缺点
1.STL会略慢一些
2.浪费空间,由于vector申请空间的方式是两倍扩容,遇到卡空间的题目的时候会跪
#include <iostream>
#include <cstring>
#include <vector>
using namespace std;
const int N = 1e3 + 233,INF = 0x3f3f3f3f;
struct edge
{
int node ,weight;
edge(int node_,int weight_)://构造函数,让我们可以直接给结构体赋值
node(node_),weight(weight_){}
};
vector<edge> v[N];
int n,m;
int main()
{
cin >> n >> m;
while(m--)
{
int x,y,w;
cin >> x >> y >> w;
v[x].push_back(edge(y,w));//模拟链表,存边
//v[y].push_back(edge(x,w));//双向
}
for(int i = 1;i <= n; ++i){
for(int j = 0;j < v[i].size(); ++j){
cout << i << " " << v[i][j].node <<" " << v[i][j].weight <<" ";
}
cout << endl;
}
return 0;
}
链式前向星 (数组模拟邻接表)
前置知识,数组模拟链表(头插法)
#include <iostream>
#include <cstring>
#include <vector>
using namespace std;
const int N = 1e3 + 233,INF = 0x3f3f3f3f;
int e[N],ne[N],h,idx;
// head 表示头结点的下标
// e[i] 表示节点i的值
// ne[i] 表示节点i的next指针是多少
// idx 存储当前已经用到了哪个点
void init(){ // 初始化为头节点 为 -1
h = -1,idx = 0;
}
void add(int x){
e[idx] = x;// 保存x的值 到 idx 这个指针的位置
ne[idx] = h;// idx 的下一个指针 为 头节点
h = idx++; // 头节点 = idx 指针 , idx ++
}
int main(){
int n ,x;
cin >> n;
init();
while(n--){
cin >> x;
add(x);
}
for(int i = h;i != -1; i = ne[i]){
cout << e[i] << " ";
}
return 0;
}
#include <iostream>
#include <cstring>
#include <vector>
using namespace std;
const int N = 1e3 + 233,INF = 0x3f3f3f3f;
int e[N],ne[N],h[N],w[N],idx;//数组模拟邻接表, h代表 n个头节点
// e存每条边,ne指向下一条边,h是链表头,w是每个顶点的权重,idx是索引值
void add(int a,int b,int c){// 采用头插法
e[idx] = b;
ne[idx] = h[a];
w[b] = c;
h[a] = idx++;
}
int n,m;
int main()
{
memset(h,-1,sizeof h);//初始化头节点,全为-1
int a,b,c;
cin>>n>>m;
while(m--)
{
cin >> a >> b >> c;
add(a,b,c); // 单向边
//add(b,a,c) 双向
}
for(int i = 0;i < n; ++i){
for(int j = h[i];j != -1;j = ne[j]){
int v = e[j];
cout << i <<" " <<v<<" "<<w[v]<<" ";
}
cout << endl;
}
return 0;
}
DFS (求连通块),求欧拉回路 与 哈密顿回路
例题 : https://vjudge.net/problem/UVA-572
#include <iostream>
#include <cstring>
using namespace std;
const int N = 233;
char g[N][N];
int n,m,ans;
int dx[8] = {0, 0,1,1, 1,-1,-1,-1}; // 方向数组
int dy[8] = {1,-1,0,1,-1, 0, 1,-1};
bool pd(int x,int y){ // 判断是否越界
if(x >= 1 && x <= m && y >= 1 && y <= n) return 1;
else return 0;
}
void dfs(int x,int y){ // dfs求连通块
for(int i = 0;i < 8; ++i){
int nx = x + dx[i];
int ny = y + dy[i];
if(pd(nx,ny) && g[nx][ny] == '@'){
g[nx][ny] = '*';
dfs(nx,ny);
}
}
}
int main(){
ios::sync_with_stdio(0);
cin.tie(0),cout.tie(0);
while(cin >> m >> n){
if(m == 0 && n == 0) break;
memset(g,0,sizeof g);
ans = 0;
for(int i = 1;i <= m; ++i){
for(int j = 1;j <= n; ++j)
cin >> g[i][j];
}
for(int i = 1;i <= m; ++i){
for(int j = 1;j <= n; ++j){
if(g[i][j] == '@'){
ans++; // 连通块个数++
g[i][j] = '*';
dfs(i,j);
}
}
}
cout << ans << endl;
}
return 0;
}
BFS (边权为 1 的最短路算法 ,队列实现 )
例题 : https://vjudge.net/problem/OpenJ_Bailian-3752
#include <iostream>
#include <cstring>
#include <queue>
using namespace std;
const int N = 50;
char g[N][N];
int r,c,ans;
int dx[4] = {0,0,1,-1};
int dy[4] = {1,-1,0,0};
bool vis[N][N]; // 判断是否访问过 点
struct Ponit{
int x,y,t; // x,y 坐标, t 代表当前的步数
Ponit(int x,int y,int t):x(x),y(y),t(t){} // 构造函数
};
bool pd(int x,int y){
if(x >= 1 && x <= r && y >= 1 && y <= c) return 1;
else return 0;
}
void bfs(){
queue<Ponit> q;
q.push({1,1,1}); // 初始化 队列
vis[1][1] = 1;
while(q.size()){
Ponit t = q.front();
q.pop();
for(int i = 0;i < 4; ++i){
int nx = t.x + dx[i];
int ny = t.y + dy[i];
if(nx == r && ny == c) {
ans = t.t + 1;
return ;
}
if(pd(nx,ny) && g[nx][ny] == '.' && !vis[nx][ny]){
vis[nx][ny] = 1;
q.push({nx,ny,t.t + 1});
}
}
}
}
int main(){
ios::sync_with_stdio(0);
cin.tie(0),cout.tie(0);
cin >> r >> c;
for(int i = 1;i <= r; ++i){
for(int j = 1;j <= c; ++j)
cin >> g[i][j];
}
bfs();
cout << ans;
return 0;
}
由BFS启发 得到的最短路算法
因为,BFS采用的是队列的思想,因此,可以想出一种基于队列的算法来处理,边权为任意值的算法
单源最短路
Dijkstra O(n^2) 只能处理正权边
算法主要特点:以起点为核心,逐层向外扩展,每次都会取一个最近点继续扩展,直到取完所有点为止。
算法步骤:
初始化距离 dist[1] = 0, dist[i] = +INF,从起点开始。
循环迭代 n 次,
集合 s 代表,已经确定最短路的点
for i 0 ~ n { // 每次循环,可以确定一个 点的距离,n次 ,确定n个点
? 在dist 中 找到不在 s 中的距离源点最近的点 , 设为 t // 基于贪心实现
? 把 t 加到 s里面去
? 用 t 来更新 ,其他所有点的距离 ( 更新 t 的出边 ) dist [x] > dist[t] + w;
}
例题 :
HDU 2544
acwing 849. Dijkstra求最短路 I
习题推荐:
HDU 1874,2066,2112,2680,
POJ 1797
#include <bits/stdc++.h>
using namespace std;
const int N = 510;
int g[N][N],n,m,dist[N];
bool st[N];
// dist[i] 代表 1 到 i的最短路
int dijkstra(){
memset(dist,0x3f,sizeof dist);
dist[1] = 0;
// 循环 n 次,每次确定一个最短路的值
for(int i = 0;i < n; ++i){
int t = -1;
// 在所有 st 为 false的点中,找到 dist最小的点
for(int j = 1;j <= n; ++j){
if(!st[j] && (t == -1 || dist[t] > dist[j])){
t = j;
}
}
st[t] = 1;
for(int j = 1;j <= n; ++j){
dist[j] = min(dist[j],dist[t] + g[t][j]);
// dist[t] + g[t][j] 表示, 1 - t的最短路 加上 t 到 j的距离,
// 等价于 1 - j 的距离
}
}
if(dist[n] == 0x3f3f3f3f) return -1;
else return dist[n];
}
int main(){
ios::sync_with_stdio(0);
cin.tie(0),cout.tie(0);
cin >> n >> m;
memset(g,0x3f,sizeof g);
while(m--){
int a,b,c;
cin >> a >> b >> c;
g[a][b] = min(g[a][b],c); // a 和 b之间可能有重边,取最小的边为权值
}
cout << dijkstra();
return 0;
}
堆优化dij
#include <iostream>
#include <cstring>
#include <queue>
using namespace std;
const int N = 1e5 + 233;
typedef pair<int,int> PII;
int dist[N],e[N],w[N],ne[N],h[N],idx,n,m,x,y,z;
bool st[N];
void add(int a,int b,int c){
e[idx] = b,w[idx] = c,ne[idx] = h[a],h[a] = idx ++;
}
int dij(){
memset(dist,0x3f,sizeof dist);
priority_queue<PII,vector<PII>,greater<PII>> heap; // 初始化堆
dist[1] = 0;
heap.push({0,1});
while(heap.size()){
auto t = heap.top();// log n 找到最小的距离
heap.pop();
int distance = t.first,ver = t.second;
if(st[ver]) continue;
st[ver] = true;
for(int i = h[ver];i != -1;i = ne[i]){
int j = e[i];
if(dist[j] > distance + w[i]){
dist[j] = distance + w[i];
heap.push({dist[j],j});
}
}
}
if(dist[n] == 0x3f3f3f3f) return -1;
else return dist[n];
}
int main(){
ios::sync_with_stdio(0);
cin.tie(0),cout.tie(0);
cin >> n >> m;
memset(h,-1,sizeof h);
while(m--){
cin >> x >> y >> z;
add(x,y,z);
}
cout << dij();
return 0;
}
Bell-Ford O(ne) 可以处理负权边,不能处理负权回路,负权回路没有最短路
// 只能存边
算法步骤:
进行n-1次松弛操作
每次循环 m 次 ,更新每条边的距离
#include <bits/stdc++.h>
using namespace std;
const int N = 1e5 + 23;
int dist[510],st[510];
int n , m;
struct edge{
int from,to,w;
}e[N];
void bf(){
memset(dist,0x3f,sizeof dist);
dist[1] = 0;
for(int i = 0;i < n-1; ++i){
for(int j = 0;j < m; ++j){
int a = e[i].from,b = e[i].to, t = e[i].w;
dist[b] = min(dist[b],dist[a] + t);
}
}
}
int main(){
cin >> n >> m;
for(int i = 0;i < m; ++i){
cin >> e[i].from >> e[i].to >> e[i].w;
}
bf();
for(int i = 1;i <= n; ++i){
cout << dist[i] <<" ";
}
return 0;;
}
SPFA (队列优化版本的 Bell-Ford) 可以判断负环,容易被网格图卡,
dist[b] = min(dist[b],dist[a] + t); // 优化 dist a
while(队列不空 ) , {
取出队头 t
删除队头
删掉队头的标记
更新遍历 t 的所有出边 {
更新每个点的距离,如果更新成功,并且当前的点没有入队,
那么就加入队列
一个点只能入队一次,不能多次入队。
}
}
#include <bits/stdc++.h>
using namespace std;
const int N = 1e5 + 23;
int dist[510],st[510];
int e[N],ne[N],w[N],h[N],idx,n,m;
int a, b,c;
void add(int a,int b,int c){
e[idx] = b;
w[idx] = c;
ne[idx] = h[a];
h[a] = idx++;
}
void spfa(){
memset(dist,0x3f,sizeof dist);
dist[1] = 0;
st[1] = 1;
queue<int> q; // 存所有变小了的节点
q.push(1);
while(q.size()){
int t = q.front();
q.pop();
st[t] = 0;
for(int i = h[t];i != -1; i = ne[i]){
int j = e[i];
if(dist[j] > dist[t] + w[i]){
dist[j] = dist[t] + w[i];
//cnt[j] = cnt[t] + 1; 判断负环
//if (cnt[j] >= n) return true; 直接跳出
if(st[j] == 0){
st[j] = 1;
q.push(j);
}
}
}
}
}
int main(){
cin >> n >> m;
memset(h,-1,sizeof h);
for(int i = 0;i < m; ++i){
cin >> a >> b >> c;
add(a,b,c);
}
spfa();
for(int i = 1;i <= n; ++i){
cout << dist[i] <<" ";
}
return 0;;
}
spfa https://blog.csdn.net/u011644423/article/details/38345631
? https://skywt.cn/posts/spfasummary/
POJ 1511,3259
多源汇最短路 DP思想,暴力 ,只能用 邻接矩阵存图
Floyed O(n^3), 还可以用来判断,图中的两个点是否相连
void floyd(){
for(int k = 1;k <= n; ++i){
for(int i = 1;i <= n; ++i){
for(int j = 1;j <= n; ++j){
g[i][j] = min(g[i][j],g[i][k] + g[k][j]);
}
}
}
}
习题推荐:
HDU 1869,3665,1596,1734
POJ 1125
最短路的应用 : 差分约束系统(不等式求解)
kuangbin最短路专题 ,https://vjudge.net/contest/324762
推荐阅读:
https://www.luogu.org/blog/wym483739/xue-tu-lun-ni-zhen-di-liao-xie-zui-duan-lu-ma-post
https://studyingfather.blog.luogu.org/some-coding-tips-for-oiers some tips for oiers
https://www.luogu.org/blog/chengni5673/tu-lun-di-xiao-ji-qiao-yi-ji-kuo-zhan 图论小技巧
https://www.luogu.org/blog/little-sun/dijkstra dij详解
https://www.luogu.org/blog/encore/io-you-hua-nei-suo-shi oi 优化
https://oi-wiki.org/graph oi-wiki-图论专题
标签:fat 时间 turn 申请 头结点 双向 -o 联通 spfa
原文地址:https://www.cnblogs.com/317zhang/p/11485681.html