单源最短路径算法
算法流程:
对于图G(V,E)维护一个集合S,存放已经被访问过的顶点(准备期间只有源点s),每次从集合V-S中选择与起点s的距离最小的一个顶点(记为u),访问u并加入集合S,并令u为中介点,更新起点s与所有从u能达到的顶点v之间的最短距离。这样执行n次(n为顶点个数),直到集合S包含所有顶点。

适用范围:有向无负权图
1.优先队列版 复杂度O(ElogE)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61
| #define INF 0x3f3f3f3f
struct qnode{ int v,d; qnode(int _v=0,int _d=0):v(_v),d(_d){} friend bool operator <(const qnode &r)const{ return d>r.d; } };
struct Edge{ int v,cost; Edge(int _v=0,int _cost=0):v(_v),cost(_cost){} }; const int MAXN = 100010; vector<Edge> E[MAXN]; //是否访问标志 int vis[MAXN]; //到源点的最短距离,准备期间设置为无穷大,表示不可及。 int dis[MAXN]; //加边 void add_edge(int u,int v,int w){ E[u].push_back(Edge(v,w)); } //初始化(从0开始编号) void init(int n){ for(int i=0;i<n;i++){ E[i].clear(); } }
void Dijkstra(int s,int n){ for(int i=0;i<n;i++)vis[i] = 0; for(int i=0;i<n;i++)dis[i] = (i == s ? 0 : INF); priority_queue<qnode> q;//声明优先队列:每次从队列中取出的是具有最高优先权的元素。 //优先队列第一个参数为比较类型,第二个为容器类型,第三个为比较函数。 //greater实现小顶堆//less 实现大顶堆(默认为大顶堆) q.push(qnode(s,dis[s]));//先将源点推进优先队列 qnode temp; while(!q.empty()){//当队列空时所有边已被访问 temp = q.top();q.pop(); //当前顶点 int u = temp.v; if(vis[u])continue; vis[u] = true; //每一条与u相邻的边都要更新 for(int i=0;i<E[u].size();i++){ //邻点 int v = E[u][i].v; //权重 int cost = E[u][i].cost; //松弛操作,更新权重时机 if(!vis[v] && dis[u] + cost < dis[v]){ dis[v] = dis[u] + cost; //把每一个更新的长度加进队列 q.push(qnode(v,dis[v])); } } } }
|
2.邻接矩阵版 复杂度O(N^2) 记录路径版
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53
| const int MAXN = 10000; const int INF = 0x3f3f3f3f; bool vis[MAXN];//访问记录 int pre[MAXN];//父节点 int cost[MAXN][MAXN];//权重矩阵 int lowcost[MAXN];//记录最短路径
//初始化矩阵为无穷。 void Init(int N){ for(int i = 0; i < N; i++){ for(int j = 0; j < N; j++){ cost[i][j] = INF; } } //如果INF是满十六进制表示。如:const int INF = 0x3f3f3f3f; //则可以使用memset(cost,INF,sizeof(cost)); }
//cost:权重矩阵,lowcost:最短路径,n:数据范围,beg:源点 void Dijkstra(int cost[][MAXN],int lowcost[],int n,int beg){ //初始化各值 for(int i = 0; i < n; i++){ lowcost[i] = INF; vis[i] = false; pre[i] = -1; } //设置源点 lowcost[beg] = 0; for(int j = 0; j < n; j++){ int k = -1; int Min = INF; //找到目前最短路径数组中到源点最短的节点。 for(int i = 0; i < n; i++){ if(!vis[i] && lowcost[i] < Min){ Min = lowcost[i]; k = i; } } //找不到,说明节点都已经全部访问。 if(k == -1)break; //记录该节点。 vis[k] = true; //松弛操作。更新每条与该节点相连并且还未访问到的节点的路径。 for(int i = 0; i < n; i++){ if(!vis[i] && lowcost[k] + cost[k][i] < lowcost[i]){ //发现一条更短的路径,更新。 lowcost[i] = lowcost[k] + cost[k][i]; //更新父节点。 pre[i] = k; } } } }
|
解题通用思路
做关于Dijkstra算法的题,通常不会只出一个裸的寻找最短路径,而是会给出一个或多个次级标尺。通常不会超出三个维度:
通常是多个维度组合起来寻找最优解。
遇到这类问题,可通过将每条最短路径都保存下来,依次进行处理。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
| vector<int> paths[MAXN]; //在其松弛操作中,将路径保存 for(int j = 0; j < N; j++){ if(!vis[j] && G[k][j] != INF){ if(dis[j] > dis[k] + G[k][j]){ dis[j] = dis[k] + G[k][j]; paths[j].clear(); paths[j].push_back(k); }else if(dis[j] == dis[k] + G[k][j]){ paths[j].push_back(k); } } } vector<vector<int> > ans;//用于存放每一个最短路径 vector<int> p; //计算每条路径,注意,这样的路径是反序并且不包含源点的,如需要,则单独计算。 void makeMinPath(vector<vector<int> > &ans,vector<int> p,int j){ if(j == 0){ ans.push_back(p); return; } for(int i = 0; i < paths[j].size(); i++){ p.push_back(paths[j][i]); makeMinPath(ans,p,paths[j][i]); p.pop_back(); } }
|
之后就可以根据要求计算每一条路径,并挑出符合问题的解了。