Dijkstra专题——所有边权必须非负

目录

基础情况

邻接矩阵版dijkstra,无pre数组。O(V^2)

邻接表版dijkstra,无pre数组。O( V^2+E )

邻接矩阵版dijkstra,有pre数组

拓展情况(即增加第二标尺,第一标尺是距离),求第一标尺的同时求第二标尺【推荐】

新增边权(假设为花费)

新增点权(假设是城市中能收集到的物资)

求最短路径条数

先求所有最短路径,然后求第二标尺(dijkstra+dfs)【难】

记录所有最短路径,vector pre[MAXN]

遍历所有最短路径找出第二标尺最优的路径

dfs函数

calcu函数(计算边权或者点权):

模板完整版:


基础情况

邻接矩阵版dijkstra,无pre数组。O(V^2)

三步走:

  1. 初始化d数组
  2. 找n次当前离s最近的点加入
  3. 每次都要更新由于新加入点带来的变化
//邻接矩阵版dijkstra
const int maxn = 1010;
const int INF = 0x3fffffff;
int n, g[maxn][maxn];
int d[maxn];//起点到各店的最短路径长度
bool vis[maxn] = { false };
void dijkstra(int s) {//s是起点
        //1  初始化d数组
	fill(d, d + maxn, INF);//开始时顶点到所有点的距离都是INF
	d[s] = 0;

        //2  找n次到离s最近的点
	for (int i = 0; i < n; i++) {
		int u = -1;//可用于后面判断是否找到小于INF的d[u],找不到说明剩下的点和s不连通
		int minn = INF;


		for (int j = 0; j < n; j++) {//寻找未访问顶点中距离起点最近的点
			if (vis[j] == false && d[j] < minn) {
				u = j;//若最后u不变(仍未-1),说明与s连通的点都已找完
				minn = d[j];
			}
		}
		if (u == -1)return;

                //3  更新由于新加进结点产生的变化,包括vis数组和距离的变化
		vis[u] = true;//标记u为已访问
		for (int v = 0; v < n; v++)
			if (vis[v] == false && d[u] + g[u][v] < d[v])
				d[v] = d[u] + g[u][v];
	}
}

邻接表版dijkstra,无pre数组。O( V^2+E )

还是三步走:

  1. 初始化d数组
  2. 找n次当前离s最近的点加入
  3. 每次都要更新由于新加入点带来的变化,这里由于是node结构体,所以用adj [u][ j]. v表示可达点,用adj [u ][j ].dis表示距离
const int maxn = 1010;
const int INF = 0x3fffffff;
struct node {
	int v;//边的目标顶点
	int dis;//权值
};
vector<node> adj[maxn];//邻接表
int n, d[maxn];
bool vis[maxn] = { false };

void dijkstr(int s) {
	//1  初始化
	fill(d, d + maxn, INF);
	d[s] = 0;

	//2  找n次最近点
	for (int i = 0; i < n; i++) {
		int u = -1;
		int minn = INF;
		for (int j = 0; j < n; j++) {
			if (vis[j] == false && d[j] < minn) {
				u = j;
				minn = d[j];
			}
		}
		if (u == -1)return;

		//3  更新
		vis[u] = true;
		for (int j = 0; j < adj[u].size(); j++) {
			int v = adj[u][j].v;
			if (vis[v] == false && d[u] + adj[u][j].dis < d[v])
				d[v] = d[u] + adj[u][j].dis;
		}

	}
}

邻接矩阵版dijkstra,有pre数组


const int maxn = 1010;
const int INF = 0x3fffffff;
int n, g[maxn][maxn], d[maxn];
bool vis[maxn] = { false };
int pre[maxn];//记录前驱

void dijkstra(int s) {
	//1  
	fill(d, d + maxn, INF);
	d[s] = 0;

	//2
	for (int i = 0; i < n; i++) {
		int u = -1, minn = INF;
		for(int j=0;j<n;j++)
			if (vis[j] == false && d[j] < minn) {
				u = j;
				minn = d[j];
			}
		if (u == -1)return;

		//3
		vis[u] = true;
		for (int v = 0; v < n; v++) {
			if (vis[v] == false && d[u] + g[u][v] < d[v]) {
				d[v] = d[v] + g[u][v];
				pre[v] = u;//记录v的前驱为u
			}
		}
	}
}

void dfs(int s, int v) {
	if (v == s) {//若当前v是起点s,则输出起点并返回
		printf("%d\n", s);
		return;
	}
	dfs(s, pre[v]);
	printf("%d\n", v);//从最深处回来后,输出每层编号
}

拓展情况(即增加第二标尺,第一标尺是距离),求第一标尺的同时求第二标尺【推荐】

有三种:给每条边增加一个边权,给每个点增加一个点权,直接问有多少条路径。

三种情况都是增加一个数组来存放新增的边权或者点权或者最短路径条数,然后修改以上代码的”优化(第三步)"部分。其他地方不用动。

新增边权(假设为花费)


const int maxn = 1010;
const int INF = 0x3fffffff;
int n, g[maxn][maxn], d[maxn];
bool vis[maxn] = { false };
int cost[maxn][maxn], c[maxn];//记录花费
void dijkstra(int s) {
	//1  
	fill(d, d + maxn, INF);
	d[s] = 0;

	//2
	for (int i = 0; i < n; i++) {
		int u = -1, minn = INF;
		for(int j=0;j<n;j++)
			if (vis[j] == false && d[j] < minn) {
				u = j;
				minn = d[j];
			}
		if (u == -1)return;

		//3
		vis[u] = true;
		for (int v = 0; v < n; v++) {
			if (vis[v] == false) {
				if (d[u] + g[u][v] < d[v]) {//第一标尺
					d[v] = d[v] + g[u][v];
					c[v] = c[u] + cost[u][v];
				}
				else if (d[u] + g[u][v] == d[v] && c[u] + cost[u][v] < c[v])//第一标尺相同时,比较第二标尺
					c[v] = c[u] + cost[u][v];
			}
		}
	}
}

新增点权(假设是城市中能收集到的物资)


const int maxn = 1010;
const int INF = 0x3fffffff;
int g[maxn][maxn], n, d[maxn];
bool vis[maxn] = { false };
int weight[maxn];//每个点的权值,输入给出
int w[maxn];//起点到每个点可收集到的最大物资,计算而得

void dijkstra(int s) {
	//1
	fill(d, d + maxn, INF);
	d[s] = 0;

	//2
	for (int i = 0; i < n; i++) {
		int u = -1, minn = INF;
		for(int j=0;j<n;j++)
			if (vis[j] == false && d[j] < minn) {
				u = j;
				minn = d[j];
			}
		if (u == -1)return;

		//3
		vis[u] = true;
		for (int v = 0; v < n; v++) {
			if (vis[v] == false) {
				if (d[u] + g[u][v] < d[v]) {//第一标尺,距离更小,没得说直接更新第一标尺数组和第二标尺数组
					d[v] = d[u] + g[u][v];
					w[v] = w[u] + weight[v];
				}
				else if (d[u] + g[u][v] == d[v] && w[u] + weight[v] > w[v])//第一标尺相同时,若第二标尺更好,则更新第二标尺数组
					w[v] = w[u] + weight[v];
			}
		}
	}
}

求最短路径条数

增加一个数组num[],令从起点s到达顶点u的最短路径条数为num[u],初始化时只有num[s]=1,其余为0;

当d[u]+g[u][v]<d[v]时更新d,并让num[v]继承num[u];

而当d[u]+g[u][v]=d[v]时,将num[u]加到num[v]上。

const int maxn = 1010;
const int INF = 0x3fffffff;
int g[maxn][maxn], n, d[maxn];
bool vis[maxn] = { false };
int num[maxn];//起点到每个点的最短路径长度

void dijkstra(int s) {
	//1
	fill(d, d + maxn, INF);
	d[s] = 0;

	//2
	for (int i = 0; i < n; i++) {
		int u = -1, minn = INF;
		for(int j=0;j<n;j++)
			if (vis[j] == false && d[j] < minn) {
				u = j;
				minn = d[j];
			}
		if (u == -1)return;

		//3
		vis[u] = true;
		for (int v = 0; v < n; v++) {
			if (vis[v] == false) {
				if (d[u] + g[u][v] < d[v]) {//最短路径唯一
					d[v] = d[u] + g[u][v];
					num[v] = num[u];
				}
				else if (d[u] + g[u][v] == d[v])//s到v的最短路径多了num[u]个
					num[v] += num[u];
			}
		}
	}
}

先求所有最短路径,然后求第二标尺(dijkstra+dfs)【难】

先在dijkstra算法中记录下所有最短路径(只考虑距离),然后从这些最短路径中选出一条第二标尺最优的路径。

记录所有最短路径,vector<int> pre[MAXN]

const int maxn = 1010;
const int INF = 0x3fffffff;
vector<int> pre[maxn];
int n;
int d[maxn], g[maxn][maxn];
bool vis[maxn] = { false };
void dijstra(int s) {
	//1
	fill(d, d + maxn, INF);
	d[s] = 0;

	//2
	for (int i = 0; i < n; i++) {
		int u = -1, minn = INF;
		for(int j=0;j<n;j++)
			if (vis[j] == false && d[j] < minn) {
				u = j;
				minn = d[j];
			}
		if (u == -1)return;

		//3
		vis[u] = true;
		for (int v = 0; v < n; v++) {
			if (vis[v] == false) {
				if (d[u] + g[u][v] < d[v]) {
					d[v] = d[u] + g[u][v];
					pre[v].clear();//找到更短的路径,则之前的应全部清空
					pre[v].push_back(u);//将这个更短路径上v的前驱,即u,送入pre[v]
				}
				else if (d[u] + g[u][v] == d[v])
					pre[v].push_back(u);//找到一样短的路径,直接把u送入pre[v]
			}
		}
	}
}

遍历所有最短路径找出第二标尺最优的路径

dfs函数

void dfs(int v) {
	if (v == st) {//递归边界,即到达递归树的叶子节点

		//1  当前节点插入,看是否更新答案,又弹出
		temppath.push_back(v);//人为地将起始节点st加入到临时路径,因为在第一步中无法更新起点的pre
		//可能更新答案
		int value = calcu();
		if (value > optvalue) 
			{optvalue = value; anspath = temppath;}
		//当前节点弹出
		temppath.pop_back();//再将起点弹出
		return;
	}

	//未到递归边界时,即递归式
	//2  当前结点插入,看是否有更优前驱,又弹出
	temppath.push_back(v);//将当前结点加入到临时路径后
	for (int i = 0; i < pre[v].size(); i++)
		dfs(pre[v][i]);//递归尝试是否有更优前驱结点
	temppath.pop_back();//已遍历完所有前驱结点,然后将v弹出
}

calcu函数(计算边权或者点权):


int calcu() {//边权之和
	int value = 0;
	for (int i = temppath.size() - 1; i > 0; --i) {//倒着访问节点
		int id = temppath[i], idnext = temppath[i - 1];
		value += edge_weight[id][idnext];
	}
	return value;
}

int calcu() {//计算点权之和
	int value = 0;
	for (int i = temppath.size() - 1; i > 0; --i)
		value += vertex_weight[temppath[i]];
	return value;
}

模板完整版:

#include <iostream>
#include<cstdio>
#include<cmath>
#include<algorithm>
#include<string.h>
using namespace std;
#pragma warning(disable:4996)
#include<string>
#include<stack>
#include<map>
#include<queue>

const int maxn = 1010;
const int INF = 0x3fffffff;
int g[maxn][maxn], n, st, d[maxn];
int vertex_weight[maxn][maxn], edge_weight[maxn];
int optvalue;
bool vis[maxn] = { false };
vector<int> pre[maxn];
vector<int> anspath, temppath;


void dijkstra(int s) {
	//1
	fill(d, d + maxn, INF);
	d[s] = 0;

	//2
	for (int i = 0; i < n; i++) {
		int u = -1, minn = INF;
		for(int j=0;j<n;j++)
			if (vis[u] == false && d[j] < minn) {
				u = j;
				minn = d[j];
			}
		if (u == -1)return;

		//3
		vis[u] = true;
		for (int v = 0; v < n; v++) {
			if (vis[v] == false) {
				if (d[v] < d[u] + g[u][v]) {
					d[v] = d[u] + g[u][v];
					pre[v].clear();
					pre[v].push_back(u);
				}
				else if (d[v] == d[u] + g[u][v])
					pre[v].push_back(u);
			}
		}
	}
}
int calcu() {
	int value = 0;
	for (int i = temppath.size() - 1; i > 0; i--) {
		int id = temppath[i], idnext = temppath[i - 1];
		value += edge_weight[id][idnext];
	}
	return value;
}
void dfs(int v) {
	//边界
	if (v == st) {
		//1.1
		temppath.push_back(v);
		//1.2
		int value = calcu();
		if (value > optvalue) {
			optvalue = value;
			anspath = temppath;
		}
		//1.3
		temppath.pop_back();
		return;
	}

	//递归式
	//2.1
	temppath.push_back(v);
	//2.2
	for (int i = 0; i < pre[v].size(); i++)
		dfs(pre[v][i]);
	//2.3
	temppath.pop_back();
}

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值