- 单源
- 权不为负数
一个讲的挺好的b站视频 https://www.bilibili.com/video/BV1zz4y1m7Nq?from=search&seid=8790821936431970385&spm_id_from=333.337.0.0
Dijkstra算法邻接矩阵代码模板 O(n2)
适合图的点数不超过1000的情况
//全局变量部分
const int MAXN = 1000; //图的最大顶点数
const int INF = 1000000000; //设立INF为一个极大数,
//这里这个数也可以设置为0x3f3f3f3f,这代表无穷大,即1061109567这一数值
int n; //当前图的点数
int vis[MAXN]; //标记数组用于记录图中各个点的被访问情况,0为未访问,1为已访问
int dis[MAXN]; //记录起点到各个顶点的最短路径长度
int m[MAXN][MAXN]; //用一个邻接矩阵来记录图中各个点的连接情况
void Dijkstra(int s){ //s为起点
fill(vis, vis+MAXN, 0); //将标记数组初始化为0即未被访问状态,此处可用memset
fill(dis, dis+MAXN, INF); //将最短路数组初始化为一个很大的数,此处要注意不可用memset
dis[s]=0; //首先将起点s到达自身的最短路设为0
for(int i=0;i<n;i++){ //n次循环,遍历完n个点
int node = -1; //node记录当前能找到的从起点到此没被访问的最短的一个点
int minn = INF; //minn记录到达那个点的最短路径
for(int j=0;j<n;j++){ //每个点逐步寻找没被访问的最短的一个点
if(vis[j]==0 && dis[j]<minn){
minn = dis[j];
node = j;
}
}
if(node==-1){ //找不到小于INF的一个点,说明其他结点与定点不连通
return ;
}
vis[node] = 1; //将当前点标记为1
for(int j=0;j<n;j++){
if(vis[j]==0 && m[node][j]!=INF && (dis[node]+m[node][j])<dis[j]){
//如果结点未被访问且 node能到达此结点 并且从起点到此结点过node中介点更近
//在此标尺只有距离,如果有两条距离相等或多条相等,那么就只需在此进行修改
//如路径相同第二标尺为花费时,则可通过在增加花费数组,在此if后加elseif判断距离相等情况花费不同条件。
//下题就有两标尺。
dis[j] = dis[node]+m[node][j];
}
}
}
}
二维矩阵+堆优化:个人认为最好写最实用
总结一下,Dijkstra算法的流程就是,不断取出离顶点最近而没有被访问过的点,松弛它和它能到达的所有点。
如何取出离顶点最近的点?如果暴力寻找,那就是朴素的Dijkstra算法,时间复杂度是O^2
但我们可以采取堆优化。具体而言,我们可以用一个优先队列(或手写堆,那样更快)来维护所有节点。这样可以实现 mlogm 的算法
int dis[maxn], vis[maxn] = {0};
vector<pair<int, int>> E[maxn];//存图 <u, d> E[i]到u点距离为d
typeof pair<int ,int> PAIR; //<起点到x点的距离, x>
#define MP make_pair
priority_queue<PAIR, vector<PAIR>, greater<PAIR>> Q;//距离短的优先级高,也就是 Q.top()
void Dij(int s){
fill(dis, dis + maxn, INT_MAX);
fill(vis, vis + maxn, 0);
dis[s] = 0;
Q.push(MP(0,s));
while(!Q.empty()){
int u = Q.top().second;Q.pop();
if(vis[u])continue;
vis[u] = 1;
for(auto it : E[u]){
if(dis[it.first] > it.second + dis[u]){
dis[it.first] = it.second + dis[u];//更新dis
//或许还有其他的操作
}else if(){}//有时还有其他的判断条件
if(!vis[it.first]) Q.push(MP(dis[it.first], it.first));
}
}
}
优化:链式前向星+堆优化 O(mlogm)
链式前向星
用一个优先队列(或手写堆,那样更快)来维护所有节点。
typedef pair<int, int> Pair; //<x点到起点的距离, x>
priority_queue<Pair, vector<Pair>, greater<Pair> > Q; // // 这里是greater, 使得距离短的先出队
void Dij(int s) //传入起点编号
{
fill(vis, vis+MAXN, 0);
fill(dis, dis+MAXN, INF);
dist[s] = 0;
Q.push(make_pair(0, s));
while (!Q.empty())
{
int p = Q.top().second;
Q.pop();
if (vis[p])
continue;
vis[p] = 1;
for (int e = head[p]; e != 0; e = edges[e].next)
{
int to = edges[e].to;
dist[to] = min(dist[to], dist[p] + edges[e].w);
if (!vis[to])
Q.push(make_pair(dist[to], to));
}
}
}
题目
L2-001 紧急救援 (25 分)
#include <bits/stdc++.h>
using namespace std;
const int INF = 1e8;
//N<500
int saveTeam[505];//各点的存储的救援人数
int st[505];//按路径能召集到的最多人数
int mm[505][505];//地图
int number[505];//到该点最短路径的数量
int vis[505];//是否标记为node过
int dis[505];//起点到该点的距离
int father[505];//最短路径中,该点的上一个节点
int n,m,s,d;//城市数量、路线数量、起点、终点
void Dijkstra(){
//初始化
fill(dis,dis+n,INF);
fill(vis,vis+n,0);
fill(number,number+5,0);
dis[s]=0;
st[s]=saveTeam[s];
number[s]=1;
for(int i=0; i<n;i++){
int minn = INF,node = -1;
for(int j =0;j<n;j++){
if(!vis[j] && dis[j]<minn){
minn = dis[j];
node = j;
}
}
vis[node]=1;
if(node == -1)return;
for(int j =0;j<n;j++){
if(vis[j]==0 && mm[node][j]!=-1 && (dis[node]+mm[node][j]) <dis[j]){
dis[j]=dis[node]+mm[node][j];
st[j] = st[node]+saveTeam[j];
father[j]=node;
number[j] = number[node];
}else if(vis[j]==0 && mm[node][j]!=-1 && (dis[node]+mm[node][j])==dis[j]){
if((st[node]+saveTeam[j]) > st[j]){
st[j] = st[node]+saveTeam[j];
father[j]=node;
}
number[j]+=number[node];
}
}
}
}
int main(){
vector<int> pathV;
cin>>n>>m>>s>>d;
for(int i = 0;i<n;i++){
cin>>saveTeam[i];
}
//初始化地图
for(int i = 0;i<n;i++){
for(int j = 0;j<n;j++){
mm[i][j] = -1;
}
}
//读入地图
for(int i =0;i<m;i++){
int n1,n2,val;
cin>>n1>>n2>>val;
mm[n1][n2]=val;
mm[n2][n1]=val;
}
Dijkstra();
int p =d;
while(1){
if(p==father[p]){//应该是0,即没有father
// cout<<"pfather"<<p<<" "<<father[p]<<"\n";
pathV.push_back(p);break;
}
pathV.push_back(p);
// cout<<"pfather"<<p<<" "<<father[p]<<"\n";
p = father[p];
}
cout<<number[d]<<" "<<st[d]<<endl;
// cout<<pathV.size();
for(int i = pathV.size()-1; i>=0;i--){
if(i==pathV.size()-1){
cout<<pathV[i];
}else{
cout<<" "<<pathV[i];
}
}
}
L3-005 垃圾箱分布 (30 分)
题目大意:
- 从m个垃圾站里面选取1个站点,让他离居民区的最近的人最远,并且没有超出服务范围ds之内。
- 如果有很多个最远的垃圾站,输出距离所有居民区距离平均值最小的那个。
- 如果平均值还是一样,就输出按照顺序排列垃圾站编号最小的那个
思路:
- 垃圾站之间也是彼此有路连接的,最短路径计算也要把垃圾站算上。
- 所以我们就是对 n+m 个点进行Dijkstra计算最短路径,计算出1~m号垃圾站距离其他站点的最短路径。
- 遍历dis数组,如果dis存在一个距离大于服务范围ds的距离,那么我们就舍弃这个垃圾站。
- 取最短的路径,这就是距离它最近的垃圾站mindis。如果mindis > ansdis,更新。
G开头的,编号设为n+G后面的数字
#include <cstdio>
#include <algorithm>
#include <iostream>
#include <string>
using namespace std;
const int inf = 999999999;
int n, m, k, ds, station;
int e[1020][1020], dis[1020];
bool visit[1020];
int main() {
//初始化地图,没路的距离就是inf
fill(e[0], e[0] + 1020 * 1020, inf);
fill(dis, dis + 1020, inf);
scanf("%d%d%d%d", &n, &m, &k, &ds);
for(int i = 0; i < k; i++) {
int tempdis;
string s, t;
cin >> s >> t >> tempdis;
int a, b;
//注意处理字符串//这样处理最后一个点会错误——没注意是小于“等于”10情况...
// if(s[0] == 'G') {
// a = s[1] - '0' + n;
// } else {
// a = stoi(s);
// }
// if(t[0] == 'G') {
// b = t[1] - '0' + n;
// } else {
// b = stoi(t);
// }
if(s[0] == 'G') {
s = s.substr(1);
a = n + stoi(s);
} else {
a = stoi(s);
}
if(t[0] == 'G') {
t = t.substr(1);
b = n + stoi(t);
} else {
b = stoi(t);
}
//将数据添入地图
e[a][b] = tempdis;
e[b][a] = tempdis;
}
int ansid = -1;//存储最终选择的候选编号,如果没有符合要求的 就是-1
double ansdis = -1, //最终的最短路径
ansaver = inf;//最终的平均路径长度
//对每个候选点进行dij算法:将其作为起点,算出到各个居民的距离——存储在dis中
for(int index = n + 1; index <= n + m; index++) {
double mindis = inf, //候选点到各个居民的路径中最短的长度
aver = 0; //该候选点到各点的平均长度
fill(dis, dis + 1020, inf); //重置dis
fill(visit, visit + 1020, false); //重置vis
dis[index] = 0; //起点到自身距离自然为0
for(int i = 0; i < n + m; i++) {
int u = -1, //本轮距离起点最近的点
minn = inf;//最近距离
for(int j = 1; j <= n + m; j++) {
if(visit[j] == false && dis[j] < minn) {
u = j;
minn = dis[j];
}
}
if(u == -1) break;//没其他点能到起点了
visit[u] = true;
for(int v = 1; v <= n + m; v++) {
if(visit[v] == false && dis[v] > dis[u] + e[u][v])
dis[v] = dis[u] + e[u][v];
}
}
for(int i = 1; i <= n; i++) {
if(dis[i] > ds) { //有距离过远,不符合题意
mindis = -1;
break;
}
if(dis[i] < mindis) mindis = dis[i]; //最短路径
aver += 1.0 * dis[i];
}
if(mindis == -1) continue;
aver = aver / n;
//判断一下
if(mindis > ansdis) {
ansid = index;
ansdis = mindis;
ansaver = aver;
} else if(mindis == ansdis && aver < ansaver) {
ansid = index;
ansaver = aver;
}
}
if(ansid == -1)
printf("No Solution");
else {
printf("G%d\n", ansid - n);
printf("%.1f %.1f", ansdis, ansaver);//输出一位小数
}
return 0;
}
L3-011 直捣黄龙
三个条件:最快>城镇最多>有效杀伤最多
- 存储:最合适路径、最快路数、最短进攻距离、歼敌总数
- 数据量:城镇数∈[2, 200]
- 将字符串与数字做映射,方便操作
- Dijskra
#include <bits/stdc++.h>
using namespace std;
#define ll long long
#define FOR(i, n, m) for (int i = n; i < m; ++i)
struct node
{
int id, dis;
//方便后面的优先队列
bool friend operator<(const node &a, const node &b)
{
return a.dis > b.dis;
}
};
int n, k, s, t, cnt, d,
enemyNum[205], //各个大本营敌军数量
enemyAll[205], //到该点的总共的歼敌数量
pre[205], //到该大本营的上一个点
Dis[205], //起点到各点的最短距离
vis[205],
sum[205], //到该点路线数量
liberation[205]; //到该店解放的大本营数
//映射
map<string, int> ntoi; //name->id
map<int, string> iton; //id->name
vector<pair<int, int>> E[205]; //存图, <u,d>:E[i]到u点,权为d
vector<int> ans;
string S, T, u, v;
void Dijskra()
{
fill(Dis, Dis + 205, 1e9);
Dis[s] = 0;
sum[s] = 1;
priority_queue<node> Q;
Q.push(node{s, 0});
while (!Q.empty())
{
int now = Q.top().id, nowD = Q.top().dis;
Q.pop();
if (vis[now])
continue;
vis[now] = 1;
for (auto it : E[now])
{
int itV = it.first, itD = it.second;
if (Dis[itV] > nowD + itD)
{
Dis[itV] = nowD + itD;
enemyAll[itV] = enemyAll[now] + enemyNum[itV];
sum[itV] = sum[now];
liberation[itV] = liberation[now] + 1;
Q.push(node{itV, Dis[itV]});
pre[itV] = now;
}
else if (Dis[itV] == nowD + itD)
{
sum[itV] += sum[now];
if (liberation[itV] < liberation[now] + 1)
{
liberation[itV] = liberation[now] + 1;
enemyAll[itV] = enemyAll[now] + enemyNum[itV];
Q.push(node{itV, Dis[itV]});
pre[itV] = now;
}
else if (liberation[itV] == liberation[now] + 1)
{
if (enemyAll[itV] < enemyAll[now] + enemyNum[itV])
{
enemyAll[itV] = enemyAll[now] + enemyNum[itV];
Q.push(node{itV, Dis[itV]});
pre[itV] = now;
}
}
}
}
}
}
int main()
{
ios::sync_with_stdio(false);
cin >> n >> k >> S >> T;
for (int i = 0; i < n - 1; i++)
{
cin >> u >> d;
ntoi[u] = ++cnt;
iton[cnt] = u;
enemyNum[cnt] = d;
}
//起点的映射
ntoi[S] = 0, iton[0] = S;
//终点
t = ntoi[T];
for (int i = 0; i < k; i++)
{
cin >> u >> v >> d;
//存边
E[ntoi[u]].push_back({ntoi[v], d});
E[ntoi[v]].push_back({ntoi[u], d});
}
Dijskra();
d = t;
while (d)
{
ans.push_back(d);
d = pre[d];
}
cout << iton[0];
for (int i = ans.size() - 1; i >= 0; i--)
{
cout << "->" << iton[ans[i]];
}
cout << "\n"
<< sum[t] << " " << Dis[t] << " " << enemyAll[t] << "\n";
return 0;
}