数据存储:
一般涉及最短路径的图都有大量数据需要存储处理,因此一定要合理选择存储结构,并能够正确初始化,只有这些准备工作做好了,下面的算法才有进行下去的可能性。一般有邻接矩阵和邻接表两种形式来存储图的与边相关的信息。特别的,使用邻接表时要单独创建结构体来存储信息。而存储点权只能使用一维数组。此外,还要设置访问标志数组,前驱数组,记录结点不同松弛变量对应值的数组,或有时开辟动态数组以记录路径。
const int maxv = 1010;
const int INF = 0x3fffffff;
struct edge{//此处为使用邻接表存储图的边权时构造的结构体
int v, dis, cost;
};
int n;
int weight[maxv]; //记录点权
int d[maxv], c[maxv], w[maxv]; //记录不同松弛变量对应结点的值
int pre[maxv]; //记录每个结点的前驱来恢复路径
int num[maxv]; //SPFA判断负环时使用
int pathCot[maxv]; //用于统计最短路径的条数
bool vis[maxv], inq[maxv]; //分别为访问标志和入队标志
int dis[maxv][maxv], cost[maxv][maxv]; //记录不同的边权
vector temp, ans;
set pre[maxv]; //此处供SPFA重新统计最短路径条数时使用
Dijkstra算法:
Dijkstra解决的是单源最短路问题,且基于松弛变量的优化策略是针对到达同一结点存在不同最短路径时使用的,是到达同一结点不同路径的纵向比较。但有时可能存在不同结点之间选择最优路径,故还要在算法结束后进行横向比较。
void dijkstra(int s){
//开辟的所有数组都要初始化
fill(d, d+maxv, INF);
fill(c, c+maxv, INF);
fill(w, w+maxv, 0);
fill(vis, vis+maxv, false);
//根据源结点初始化
d[s] = 0;
c[s] = 0;
w[s] = weight[s];
//进入算法
for(int i = 0; i < n; i++){//共进行n轮,一轮确定一个点
int u = -1, Min = INF;
for(int j = 1; j <= n; j++){//这个要根据具体结点编号情况来看,一般是1-n
if(!vis[j] && d[j] < Min){
u = j;
Min = d[j];
}
}
if(u == -1) break;
vis[u] = true;
for(edge x: G[u]){//此处给出了邻接表形式的简洁遍历方式
int v = x.v, dis = x.dis, cost = x.cost;
...
}
for(int v = 1; v <= n; v++){
if(!vis[v] && dis[u][v] != INF){
if(d[u] + dis[u][v] < d[v]){//下面有几个松弛变量就有几个判定,注意更新相应数组的值
}else if(...){
...
}else if(...){
...
}
}
}
}
}
SPFA算法:
该算法主要用于判断负环,且形式看上去更简洁。一般非必要还是不建议使用该算法,因为若需要统计最短路径条数时,该算法很麻烦,每次更新都要重新统计,详细做法如下所示。
bool SPFA(int s){
fill(d, d+maxv, INF);
fill(c, c+maxv, INF);
fill(w, w+maxv, 0);
fill(num, num+maxv, 0);
fill(pathCot, pathCot+maxv, 0);
fill(inq, inq+maxv, false);
d[s] = 0;
c[s] = 0;
w[s] = weight[s];
pathCot[s] = 1;
queue q;
q.push(s);
inq[s] = true;
num[s]++;
while(!q.empty()){
int u = q.front();
q.pop();
inq[u] = false;
for(int v = 1; v <= n; v++){
if(dis[u][v] != INF){
if(d[u] + dis[u][v] < d[v]){//下面仅给出对最短路径条数的更新
...
pathCot[v] = pathCot[u];
pre[v].clear();
pre[v].insert(u);
...
}else if(...){
...
pathCot[v] = 0;
pre[v].insert(u);
for(auto it = pre[v].begin(); it != pre[v].end(); it++){//重新统计最短路径条数
pathCot[v] += pathCot[*it];
}
...
}else if(...){
...
}
if(...){//此处判定条件是前面是否更新过
if(!inq[v]){
q.push(v);
inq[v] = true;
num[v]++;
if(num[v] >= n) return false;
}
}
}
}
}
return true;
}
DFS算法:
该方法使用范围很广,几乎可以解决所有图论相关问题,且搭配回溯剪枝使用效果更佳。但是此法比较灵活,没有固定的模板来套,且有许多细节要去处理,故没有十足把握或非必须建议还是使用Dijkstra算法,毕竟当做模板直接用。使用DFS时,回溯时恢复相应变量的值很重要,要非常小心。一般DFS针对性较强,有确定的源点和终点。
void dfs(int now, int ...){//一般dfs的形参中可以加入相关的累加量,从而在回溯时不用恢复
if(...) return; //回溯剪枝
if(...){//到达指定结点,进行判定
}
for(...){//寻找下一个符合条件的递归结点
//加入路径
temp.push_back(now);
//设置访问
vis[i] = true;
//消耗相应的信息
...
//递归进入下一层
dfs(..., ...);
//恢复相应的信息
...
//设为未访问
vis[i] = false;
//从路径中弹出
temp.pop_back();
}
}
int main(){
...
temp.push_back(0);
vis[0] = true;
dfs(0, ...);
vis[0] = false;
temp.pop_back();
...
return 0;
}
Floyd算法:
用来解决全源最短路问题,但因为是三次方的时间复杂度,故一般要求结点数小于200,也因此适合使用邻接矩阵。
void floyd(){
for(int k = 1; k <= n; k++){//注意作为更新最短路径的中间结点一定要放置在最外一层循环
for(int i = 1; i <= n; i++){
for(int j = 1; j <= n; j++){
if(dis[i][k] != INF && dis[k][j] != INF && dis[i][k] + dis[k][j] < dis[i][j]){//更新两点间的最短路径
dis[i][j] = dis[i][k] + dis[k][j];
}
}
}
}
}