我们登上并非我们所选择的舞台,演出并非我们所选择的剧本。|

乐池

园龄:3年4个月粉丝:0关注:7

2.3搜索与图论(最短路)

1.朴素Dijkstra算法

稠密图用邻接矩阵,稀疏图用邻接表存

1≤m≤10^5,所以显然这是个稠密图,由于图中可能存在重边和自环,于是我们用g[N][N]存储每条边的距离时,先要将其初始化为无穷,然后读取时比较一下其与原数组中的值的大小,取最小值存储。Dijkstra方法中,对于每个数都循环一次,先找到没有找到过的距离1点最近的点,把它的st数组中的值设为真,然后遍历所有数更新其距离。

#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
const int N = 510;
int g[N][N] , dist[N];//g[i][j]存储i到j的距离,dist表示每个点到1的距离
int n , m;
bool st[N];//表示每个点是否能确定到1的距离
int dijkstra()
{
memset(dist , 0x3f , sizeof dist);
dist[1] = 0;
for (int i = 0 ; i < n; i ++)
{
int t = -1;
for (int j = 1 ; j <= n ; j++)
{
if(!st[j] and (t = -1 or dist[j] < dist[t]))//j没有确定最短路并且(t没有复制或者t并不是最短的)
t = j;
}
st[t] = true;
for (int j = 1 ; j <= n ; j++)
{
dist[j] = min(dist[j] , dist[t] + g[t][j]);
}
}
if(dist[n]==0x3f3f3f3f) return -1;
return dist[n];
}
int main()
{
cin >> n >> m;
memset(g , 0x3f , sizeof g);//初始化成很大的数
while (m--)
{
int a , b , c;
scanf("%d%d%d" , &a , &b , &c);
g[a][b] = min(c , g[a][b]);
}
cout << dijkstra() <<endl;
return 0;
}

2.用堆实现的Dijkstra算法

用新开一个数组w[N]表示权重,有了新的add方法的写法。

用priority_queue<PII , vector<PII> , greater<PII>>即小根堆存储,因为每次循环过后,找的是除了能确定距离的点外距离最小的点,将其的st数组值转为可以确定距离,多以用小根堆从小到大排列的特性可以非常方便的找到那个点。

priority_queue<PII , vector<PII> , greater<PII>>第一个数是距离,因为要用距离来排,第二个数是代表这是第几个点。如果队列不空就一直循环,先把队头取出,如果队头代表的点已经取出过了,就continue取下一个点;如果没有更新一下布尔数组,再对与其相连的点进行枚举,如果现存的距离比dist[k]加两点相连的距离大就表示dist数组可以更新,并且其距离已经确定,加入小根堆中。

#include <iostream>
#include <cstring>
#include <queue>
using namespace std;
typedef pair<int , int> PII;
const int N = 150010;
int h[N] , e[N] , ne[N] , w[N] , idx;//采用邻接表来存储,不是g[N][N]邻接矩阵
int dist[N];
bool st[N];
int n , m;
void add(int a , int b ,int c)
{
e[idx] = b , w[idx] = c , ne[idx] = h[a] , h[a] = idx++;
//有权重的邻接表存储
}
int dijkstra()
{
memset(dist , 0x3f , sizeof dist);//初始化为无穷大;
dist[1] = 0;//编号为1 的点距离起点的距离为0;
priority_queue<PII , vector<PII> , greater<PII>> heap;//设置小根堆
heap.push({0,1});//小根堆要根据距离来排,即第一个数是距离,第二个数是他是哪个点
//插入1号点的距离为1
while (heap.size())//当堆不空
{
auto t = heap.top();//取出队头并删除队头
heap.pop();
int k = t.second , distance = t.first;//t是第k个点,到起点距离为distance
if (st[k]) continue;//此点冗余
st[k] = true;
for (int i = h[k] ; i != -1 ; i = ne[i])//循环
{
int j = e[i];
if (dist[j] > dist[k] + w[i])//如果dist值可以更新
{
dist[j] = dist[k] + w[i];//那就更新,并将其加入堆
heap.push({dist[j] , j});
}
}
}
if(dist[n] == 0x3f3f3f3f) return -1;
return dist[n];
}
int main()
{
cin >> n >> m;
memset(h , -1 , sizeof h);//常规操作,把表头指向-1;
while(m--)
{
int a, b ,c;
scanf("%d%d%d" , &a , &b , &c);
add(a , b , c); //采用堆就不用考虑重边的问题
}
cout << dijkstra() <<endl;
return 0;
}

 3. Bellman Ford算法

处理最多经过k条边,拥有负权回路的最短路

先枚举k次操作,每次操作枚举m条边,看看能不能更新

代码如下:

#include <iostream>
#include <cstring>
#include <algorithm>
const int N = 510 , M = 10010;
int dist[N] , backup[N];
using namespace std;
struct Edge{ //结构体
int a , b , w;
}edges[M];
int n , m , k;
void bellman_ford()
{
memset(dist , 0x3f , sizeof dist);
dist[1] = 0;
for (int i = 0; i < k; i ++ ) //枚举k次
{
memcpy(backup , dist , sizeof dist);
for (int j = 0 ; j < m ; j ++) //枚举每条边
{
int a = edges[j].a , b = edges[j].b , w = edges[j].w;
dist[b] = min(dist[b] , backup[a] + w);
}
}
}
int main()
{
cin >> n >> m >> k;
int i = 0;
for (int i = 0; i < m; i ++ )
{
int a , b , w;
scanf("%d%d%d" , &a , &b , &w);
edges[i] = {a , b , w};
}
bellman_ford();
if (dist[n] > 0x3f3f3f3f / 2) puts("impossible"); //如果dist[n]依然很大
else cout << dist[n] << endl;
return 0;
}

4. spfa算法

代码跟堆优化的dijkstra比较相似,y总推荐使用

说很多正权边的最短路本来是用dijkstra算法,事实上可能spfa也可以过

处理边权可能为负数,不存在负权回路的最短路

代码如下:

#include <iostream>
#include <cstring>
#include <algorithm>
#include <queue>
using namespace std;
const int N = 100010;
int h[N] , e[N] , w[N] , ne[N] , idx;
int n , m;
int dist[N];
bool st[N]; //存储每个数在不在队列q中
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;
queue<int> q;
q.push(1);
while (q.size())
{
int t = q.front();
st[t] = false; //t出队,将其状态变更
q.pop();
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];
if (!st[j]) //如果j不在队列中
{
st[j] = true; //将j加入队列
q.push(j);
}
}
}
}
}
int main()
{
cin >> n >> m;
memset(h, -1, sizeof h);
while (m -- )
{
int a , b , c;
scanf("%d%d%d" , &a , &b , &c);
add(a , b , c);
}
spfa();
if (dist[n] == 0x3f3f3f3f) puts("impossible");
else cout << dist[n] << endl;
return 0;
}

4.1 spfa算法判负环

跟spfa本体大差不差

#include <iostream>
#include <cstring>
#include <algorithm>
#include <queue>
using namespace std;
const int N = 2010 , M = 10010;
int h[N] , e[M] , ne[M] , w[M] , idx;
int dist[N] , cnt[N];
bool st[N]; //该数是否进队列
int n , m;
void add(int a, int b, int c)
{
e[idx] = b, w[idx] = c, ne[idx] = h[a], h[a] = idx ++ ;
}
bool spfa()
{
queue<int>q;
//并不是求1到n的路径有没有负环,有可能1到不了,所以把所有点加进去
for (int i = 1; i <= n; i ++ )
{
q.push(i);
st[i] = true;
}
while (q.size())
{
int t = q.front();
q.pop();
st[t] = false;
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; //如果到j的路径经过大于等于n个点
if (!st[j])
{
st[j] = true;
q.push(j);
}
}
}
}
return false;
}
int main()
{
cin >> n >> m;
memset(h, -1, sizeof h);
while (m -- )
{
int a , b , c;
scanf("%d%d%d" , &a , &b , &c);
add(a, b, c);
}
if (spfa()) puts("Yes");
else puts("No");
return 0;
}

5. Floyd多源汇最短路

代码挺简单,三重循环

#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
const int N = 210;
int g[N][N];
int n , m , k;
void floyd()
{
for (int k = 1 ; k <= n ; k ++) //三重循环
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]);
}
int main()
{
cin >> n >> m >> k;
memset(g , 0x3f , sizeof g); //除了相同的点,其余边全部初始化成无穷
for (int i = 1 ; i <= n ; i ++)
g[i][i] = 0;
while (m -- )
{
int a , b , c;
scanf("%d%d%d" , &a , &b , &c);
g[a][b] = min(g[a][b] , c); //读入所有边
}
floyd();
while (k --)
{
int a , b;
scanf("%d%d" , &a , &b);
if (g[a][b] > 0x3f3f3f3f / 2) puts("impossible");
else printf("%d\n" , g[a][b]);
}
return 0;
}

 

本文作者:乐池

本文链接:https://www.cnblogs.com/ratillase/p/15968850.html

版权声明:本作品采用知识共享署名-非商业性使用-禁止演绎 2.5 中国大陆许可协议进行许可。

posted @   乐池  阅读(33)  评论(0编辑  收藏  举报
点击右上角即可分享
微信分享提示
评论
收藏
关注
推荐
深色
回顶
收起