图论——最短路径问题

廉价感情. 提交于 2020-02-27 15:57:41

之前自己看过和总结过相关的东西,今天再总结说一下;

 

感觉权值问题,是否无向有向,权值正负可以直接用不同的算法进行解决;

这次还是主要说一说四种算法:迪杰斯特拉算法、BF算法、SPFA算法、弗洛伊德算法;

 

迪杰斯特拉算法:

老生常谈最简单的一种算法,从某一点出发,建立Distance数组,不断地寻找当前最近节点,并且选择该节点后,以该节点作为中介,更行其他的全不distance长度;

个人认为适合单源到多点的路径问题;

之前做的总结https://blog.csdn.net/InNoVaion_yu/article/details/86629313

 

但是仍然需要注意的是该算法的衍生问题:多路径问题;

对于多路径问题往往加限定条件;

 

不加任何条件的整体代码:

#include<stdlib.h>
#include<stdio.h>
#include<algorithm>
#include<string>
#include<iostream>
#include<vector>
using namespace std;
using std::vector;
const int MAXV=1000;
const int INF=1000000000;
struct Node{
    int v,dis;
};
vector<Node>Adj[MAXV];//图的存储
int d[MAXV];//储存点的距离
bool vis[MAXV]={false};//访问判定
void Dijkstra(int s){
    fill(d,d+MAXV,INF);//距离初始化;
    d[s]=0;//开始访问的点;
    for(int i=0;i<n;i++){
        int u=-1,MIN=INF;
        for(int j=0;j<n;j++){
            if(vis[j]==false&&d[j]<MIN){
                //目的是找到邻近中距离最小的相邻点;
                u=j;
                MIN=d[j];
            }
        }
        if(u==-1)
            return;
        vis[u]=true;
        for(int v=0;v<n;v++){
            //进行d[v]的优化;
            if(vis[v]==false&&d[u]+Adj[u][j].dis<d[v]){
                d[v]=d[u]+Adj[u][j].dis;
            }
        }
    }
}
int main(){
    system("pause");
}

 

特殊条件1:

但是迪杰斯特算法往往并不只要求最短距离,也往往会让输出一条最短的路径,如果使用DFS确实是很好解决,但是对于迪杰斯特拉算法,需要额外开一个数组来进行记录,类似于静态的单叉树数组;

整体代码:

void Dijkstra(int st,int n) {
    //起始节点,进行初始化;
    vis[st] = false;
    for (int i = 0; i < n; i++)
        path[i] = i;
    fill(dis, dis + maxn, INF);
    dis[st] = 0;
    for (int i = 0; i < n; i++) {
        //进行n-1次迪杰斯特拉遍历;
        int index = -1;//最近路径节点;
        int min = INF;
        for (int i = 0; i < n; i++)
            if (vis[i] && dis[i]<min) {
                //如果由路径且没访问过;
                index = i;
            }
        if (index == -1)
            return;
        vis[index] = false;
        for (int j = 0; j < n; i++) {
            if (vis[j] && mat[index][j] != INF&& mat[index][j] + dis[index] < dis[j]) {
                //如果以index做终结点,可以使得到j的距离更小;
                dis[j] = dis[index] + mat[index][j];
                path[j] = path[index];
            }
        }
    }
    
}

 

这里在每次找到新的最短路径的时候,都会把新更新的节点指向新的前驱;

此时可以根据递归dfs遍历进行求解,且头节点必定索引和对应的值相同;

void dfs(int st, int v) {
    if (v == st) {
        cout << st << endl;
        return;
    }
    dfs(st, path[v]);
    cout << v << endl;
}

 

特殊条件2:

如果再路径相等的情况下附加新的条件,例如结点权值问题,可以直接在迪杰斯特拉算法判定相等的时候进行处理:

 

 

 

特殊条件3:

当需要统计相同路径长度的时候,此时需要数组辅助.但是特别需要注意的是,数组内的意义;

开辟一个num[]数组,其中num[i]代表从初始节点到该节点有几条路;

for (int j = 0; j < n; i++) {
	if (vis[j] && mat[index][j] != INF) {
		if (mat[index][j] + dis[index] < dis[j]) {
			dis[j] = dis[index] + mat[index][j];
			num[j] = num[index];
		}
		else if (mat[index][j] + dis[index] == dis[j]) {
			//当逻辑相等的时候进行逻辑在判断
			num[j] += num[index];
			}
		}
	}
}

 在初始化的时候,将num[st]=1,其余为0.当出现相同路径的时候,就会把中间节点有几条路径加起来.这个要注意理解,可以画个图自己看一看.言语不太好描述.

 

Floyd算法:

弗洛伊德算法更像是从矩阵来运算,但是时间复杂度比较高,适合用于邻接链表;

 

基本思想:在Floyd算法内,dis数组为2维,首先对于一对点u,v,进行中间节点x的枚举.如果出现了:

d[u][v]>d[u][x]+d[x][v],则说明两者间的路径应该经过x.

所以可以看到,大致时间复杂度应该在O(n^3)左右;

 

主体函数如下所示:

void floyd() {
    for (int k = 0; k < n; k++) {
        for (int i = 0; i < n; i++) {
            for (int j = 0; 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];
                }
            }
        }
    }
}

注意点:

1.注意所有dis数组初始化都要变为INF,这样比较接近正无穷的实际含义;

2.在进行中间节点距离更新的时候,若d[i][j]中没有路径,表示为正无穷,可以直接进行更新,但是i-k和k-j之间没有油路径一定要进行判定;

 

 

Bellman-Ford算法:

考的可能性不太大,考前看一看,喝前摇一摇,毕竟到现在也没见到PAT有这方面的题;

自己之前写的https://blog.csdn.net/InNoVaion_yu/article/details/86647091

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!