最短路问题(dijkstral 算法)(优化待续)

时间:2023-03-09 19:06:46
最短路问题(dijkstral 算法)(优化待续)

迪杰斯特拉算法是由荷兰计算机科学家狄克斯特拉于1959 年提出的,因此又叫狄克斯特拉算法。是从一个顶点到其余各顶点的最短路径算法,解决的是有向图中最短路径问题。迪杰斯特拉算法主要特点是以起始点为中心向外层层扩展,直到扩展到终点为止。

算法复杂度为n^2

这里有一篇讲解的很清晰的文章:http://blog.chinaunix.net/uid-26548237-id-3834514.html

下面说说我个人的理解:

就以这张图为例:

最短路问题(dijkstral 算法)(优化待续)

要找出A点到其他点的最短路径,应该怎么找?

这个算法的思路是:

    1. 先找出离A直接连通,且最近的那个点。怎么找?遍历所有的点与A的距离,可以知道AB=6,AC=3。DEF没有与A相连,那AD就设为无穷大。如此一轮比较下来就能知道离A最近的点是C。
    2. 最近的点找到了,就以这个点出发,继续找离C最近的点。同上也是遍历所有点,但在这个过程中,同时比较A经过C到该点的距离与A直接与该点连通的距离。比如遍历到B点时,比较ACB和AB,如果ACB小于AB,则将A到B的距离保存为ACB。到D点时,比较ACD和AD,以此类推。一轮比较后,在找到离C最近的点的同时,更新了A到各个点的最短距离。直到最后一个点遍历完毕,则A与所有点的最短距离也就知道了。

具体的实现如下:

 #include <iostream>
using namespace std; #define MAXVEX 20
#define MAXEDGE 20
#define INFINITY 65535 typedef struct
{
int vexs[MAXVEX];
int arc[MAXVEX][MAXVEX];
int numVertexes,numEdges;
}MGraph; typedef int Patharc[MAXVEX];
typedef int ShortPathTable[MAXVEX]; void CreateGraph(MGraph* G)
{
cout<<"请输入边数和顶点数:\n";
int d,n,i,j;
cin>>d>>n;
G->numVertexes = n;
G->numEdges = d; //给顶点和边初始化
for(i = ;i<G->numVertexes;i++)
G->vexs[i] = i;
for(i = ;i<G->numVertexes;i++)
{
for(j = ;j<G->numVertexes;j++)
{
if(i==j)
G->arc[i][j] = ;
else
G->arc[i][j] = G->arc[j][i] = INFINITY;
}
} G->arc[][]=;
G->arc[][]=;
G->arc[][]=;
G->arc[][]=;
G->arc[][]=; G->arc[][]=;
G->arc[][]=;
G->arc[][]=;
G->arc[][]=;
G->arc[][]=; G->arc[][]=;
G->arc[][]=;
G->arc[][]=;
G->arc[][]=;
G->arc[][]=; G->arc[][]=; for(i = ;i<G->numVertexes;i++)
{
for(j = i;j<G->numVertexes;j++)
{
G->arc[j][i] = G->arc[i][j];
}
}
} /* Floyd算法,求网图G中各顶点v到其余顶点w的最短路径P[v][w]及带权长度D[v][w]。 */
void Dijkstra(MGraph G,int v0, Patharc *P, ShortPathTable *D)
{
int v,w,k,min; int final[MAXVEX];/* final[w]=1表示求得顶点v0至vw的最短路径 */ for(v = ;v<G.numVertexes;v++)
{
(*P)[v] = ;
/* 初始化P */
(*D)[v] = G.arc[v0][v];
/* D[v]值即为对应点间的权值 */
final[v] = ;
} (*D)[v0] = ;
final[v0] = ; /* 开始主循环,每次求得v0到某个v顶点的最短路径 */
for(v = ;v<G.numVertexes;v++)
{
min = INFINITY;/* 当前所知离v0顶点的最近距离 */
for(w = ;w<G.numVertexes;w++)/* 寻找离v0最近的顶点 */
{
if((*D)[w]<min && !final[w])
{
min = (*D)[w]; /* w顶点离v0顶点更近 */
k = w;
}
}
final[k] = ; /* 将目前找到的最近的顶点置为1 */ /* 修正当前最短路径及距离 */
for(w = ;w<G.numVertexes;w++)/* 寻找离v0最近的顶点 */
{
/* 如果经过v顶点的路径比现在这条路径的长度短的话 */
if(min+G.arc[k][w] < (*D)[w] && !final[w])
{
/* 说明找到了更短的路径,修改D[w]和P[w] */
(*D)[w] = min+G.arc[k][w];
(*P)[w] = k;
}
}
} } int main()
{
int v0,i,j; MGraph G; Patharc P;
ShortPathTable D; CreateGraph(&G);
v0 = ;
Dijkstra(G,v0,&P,&D); cout<<"最短路径倒序如下:\n";
for(i = ;i<G.numVertexes;i++)
{
cout<<"v0 - v"<<i<<" : ";
j = i;
while(P[j]!=)
{
cout<<P[j]<<" ";
j = P[j];
}
cout<<endl;
} cout<<"\n源点到各顶点的最短路径长度为:\n"; for(i=; i<G.numVertexes; ++i)
{
cout<<"v"<<G.vexs[]<<" - v"<<G.vexs[i]<<" : "<<D[i]<<endl;
}
return ; }

算法优化:

参考这篇文章:http://blog.csdn.net/zhongyanghu27/article/details/8221276

算法复杂度为n^2,我们可以发现,如果边数远小于n^2,对此可以考虑用这种数据结构进行优化,取出最短路径的复杂度降为O(1);每次调整的复杂度降为O(elogn);e为该点的边数,所以复杂度降为O((m+n)logn)。

实现

1. 将与源点相连的点加入,并调整堆。
2. 选出堆顶元素u(即代价最小的元素),从堆中删除,并对堆进行调整。
3. 处理与u相邻的,未被访问过的,满足三角不等式的顶点
1):若该点在堆里,更新距离,并调整该元素在堆中的位置。
2):若该点不在堆里,加入堆,更新堆。
4. 若取到的u为终点,结束算法;否则重复步骤2、3。
优先队列+vector优化:
引用此处:http://blog.163.com/jiongjiong_zier/blog/static/192279231201171934931645/
优先级队列本质上是由堆来实现的,既然是队列,我们能进行的操作无非是入队,出队,判空和大小,查找是不可以的。创建一个优先级队列时有两种申请方式:priority_queue<ElemType>q或者priority_queue<ElemType,vector<ElemType>,greater<ElemType> >q,前者默认的东西比较多,比如底层的实现方式以及堆的性质;第二种就说明的比较清晰了,第一个参数是队列中元素的类型;第二个就是队列底层的实现方式,这里是用vector实现的;第三个是堆的性质,greater对应小顶堆,less对应大顶堆,当然这两个只能适用于一些基本类型,于是优先级就是这些基本类型元素的大小。如果遇到一些复杂的类型,比如结构体或类,那么就要重新定义这个优先级,于是便要重载bool operator(),有意思的是,在重载时,>对应的是小顶堆而<对应的是大顶堆,至于为什么,哥真的不懂。。。。
然后就要说说优化后的dij了,虽然和朴素的dij的思路完全一样,可还是有些细节需要注意。比如优化的dij中,d[i]的初值都是INF,除了d[source],然后将源点入队但并不访问,实际上进行了n次循环。还有一点就是对于pair这个二元组的使用,真的很方便啊有木有,如果采用结构体还得引入变量,赋值后才能进行入队。。。STL真是个好东西,就是有时候效率低了点。。。。
 /*
Dijkstra的算法思想:
在所有没有访问过的结点中选出dis(s,x)值最小的x
对从x出发的所有边(x,y),更新
dis(s,y)=min(dis(s,y),dis(s,x)+dis(x,y))
*/ #include <iostream>
#include <cstdio>
#include <queue>
#include <vector>
using namespace std;
const int Ni = ;
const int INF = <<; //这里是位运算<<,1<<3=8,1<<27即2的27次方 typedef struct node
{
int x;
int d;
node(){}
node(int a,int b)
{x = a;d = b;} //>对应小顶堆,<对应大顶堆
bool operator <(const node& a) const
{
if(d == a.d)
return x<a.x;
else
return d>a.d;
}
}node; vector<node> eg[Ni];//eg是一个node数组,保存邻接关系 int dis[Ni],n;//dis使用1-n的部分 void Dijkstra(int s)
{
int i,j;
for(i=;i<=n;i++)//要到n
dis[i] = INF;
priority_queue<node> q;
dis[s] = ;
q.push(node(s,dis[s]));
while(!q.empty())
{
node x = q.top();
q.pop();
for(j = ;j<eg[x.x].size();j++)//遍历x的所有邻接点
{
node y = eg[x.x][j];//y是x的邻接点
if(dis[y.x]>x.d+y.d)
{
dis[y.x] = x.d+y.d;
q.push(node(y.x,dis[y.x]));
}
}
} } int main()
{
int m,a,b,d;//关系个数
while(cin>>n>>m,m+n)
{
for(int i = ;i<=n;i++)
eg[i].clear();//初始化
while(m--)
{
cin>>a>>b>>d;
eg[a].push_back(node(b,d));
eg[b].push_back(node(a,d));
}
Dijkstra();
for(int i = ;i<=n;i++)
cout<<dis[i]<<" ";
} return ;
}
/*
6 6
1 2 2
3 2 4
1 4 5
2 5 2
3 6 3
5 6 3
*/
 此外还可以使用pair代替结构
ps:
pair定义在头文件utility,且在std命名空间中;删除后无影响,应该是引入其他头文件是间接引入了utility,比如map头文件。
我们知道map和multimap的作用,这两种数据类型在存储数据时,会根据pair<>的first成员进行排序,不同的时前者将不会插入对first成员重复的结构,后者可以。那如果我们只想存储pair对,而不需要对其排序,则需要用到vector
 /*
使用pair代替结构
*/ #include <iostream>
#include <cstdio>
#include <queue>
#include <vector>
using namespace std;
const int Ni = ;
const int INF = <<; typedef pair<int,int> pa; int dis[Ni],n;//dis使用1-n的部分 vector<pair<int,int> > eg[Ni]; void Dijkstra(int s)
{
int i,j;
for(i=;i<=n;i++)//要到n
dis[i] = INF;
priority_queue< pa,vector<pa>,greater<pa> >q; //优先级队列:小顶堆
dis[s] = ;
q.push(make_pair(s,dis[s])); while(!q.empty())
{
pa x = q.top();
q.pop();
int w = x.first;
for(j = ;j<eg[w].size();j++)//遍历x的所有邻接点
{
pa y = eg[w][j];//y是x的邻接点
int u = y.first;
if(dis[u]>x.second+y.second)
{
dis[u] = x.second+y.second;
q.push(make_pair(u,dis[u]));
}
}
} } int main()
{
int m,a,b,d;//关系个数
while(cin>>n>>m,m+n)
{
for(int i = ;i<=n;i++)
eg[i].clear();//初始化
while(m--)
{
cin>>a>>b>>d;
eg[a].push_back(make_pair(b,d));
eg[b].push_back(make_pair(a,d));
}
Dijkstra();
for(int i = ;i<=n;i++)
cout<<dis[i]<<" ";
} return ;
}
/*
6 6
1 2 2
3 2 4
1 4 5
2 5 2
3 6 3
5 6 3
*/
优化思路:
权值排序优化策略:
  问题:S集合中未确定最短路径的顶点无序存放在数组中,每一次选择权值最小的弧段必须将所有未选择顶点对应的数组元素完全扫描才能找到,在数据量较大的情况下,计算速度受到严重制约。
  优化策略:将要扫描的结点按其对应弧的权值进行顺序排列,每循环一次即可得到符合条件的结点,大大提高了算法的执行效率

A*算法优化策略
  问题:Dijkstra算法基于广度优先搜索策略,即从源点出发,通过权值迭代遍历所有其他结点后,最后得到从源点到其他各结点的最短路径。整个搜索好似一个圆形向外展开,直到到达目的地,这样的搜索方式是盲目的。很明显这样求解一定能找到最优解,但节点展开的数量和距离成级数增加,会导致大量无效点的搜索,大大的降低搜索的效率。
  优化策略:采用改进的Dijkstra算法——A*算法。A*算法是人工智能运用在游戏中的一个重要实践,它主要是解决路径搜索问题。A*算法实际是一种启发式搜索。发表论文。所谓启发式搜索,就是利用一个估价函数judge()评估每次决策的价值,决定先尝试哪一种方案。这样可以极大地优化普通的广度优先搜索。从Dijkstra算法到A*算法是判断准则的引入,如果这个判断条件不成立,同样地,只能采用Dijkstra算法。所以A*算法中的估价函数是至关重要[3]。

扇形优化策略
  问题:Dijkstra算法的搜索过程好似以源点为圆心的一系列同心圆向外展开,搜索过程中没有考虑到终点所在方向或位置,搜索是盲目的。这样导致大量的无用临时结点被反复搜索,成为实际应用中的瓶颈。
  优化策略:从尽量减少最短路径分析过程中搜索的临时结点数量,限制范围搜索和限定方向搜索考虑进行优化。那么这种有损算法是否可行呢?我们知道,现实生活中行进,不会向着目的地的相反方向行进,否则就是南辕北辙。所以,当所研究的网络可以抽象化为平面网络的条件下,也不必搜索全部结点,可以在以源点到终点所在直线为轴线的扇形区域内搜索最短路径。这样,搜索方向明显地趋向终点,提高了搜索速度,虽然抛弃了部分结点,但基本上不影响搜索的成功率[5]。

邻接点优化策略
  问题:Dijkstra算法在提取最短路径结点时需要访问所有的未确定最短路径的结点,算法的时间复杂度为O(n2),如果只希望找到从源点到某一特定的终点的最短路径也不例外。结点数n越大,算法的计算效率和存储效率越低。
  优化策略:只对最短路径上结点的邻接点作处理,不涉及其他结点。即(1)只从源点的邻接点集合中选择权值最小的结点作为转接点,将此转接点加入已确定最短路径的结点集合S中;(2)对此转接点的邻接点集合与S的差集中的结点的权值进行更新;(3)从S中所有结点的邻接点集合的并集与S的差集中选择权值最小的结点作为下一个转接点,并将此转接点加入S中。重复(2),(3)操作,直至所有的结点确定最短路径。优化算法在更新最短路径值与选择最短路径值最小的结点时,仅仅涉及相关结点的邻接点集合及S集合中所有结点的邻接点集合与S集合的差集,时间复杂度取决于转接点的邻接点的数量多少,减少了计算次数与比较次数

参考文献:
[1] 严蔚敏,吴伟民. 数据结构(C语言版)[M]. 北京:清华大学出版社,1997,186~190.
[2] 谢柏青,佘晓歌. 算法与数据结构[M]. 北京:高等教育出版社,2001,230~232.
[3] 陈益富,卢 潇,丁豪杰. 对Dijkstra算法的优化策略研究[J]. 计算机技术与发展,2006,16(9):73~75.
[4] 章永龙. Dijkstra最短路径算法优化[J]. 南昌工程学院学报,2006,25(3):30~33.
[5] 胡树玮,张修如,赵 洋.扇形优化Dijkstra算法[J]. 计算机技术与发展,2006,16(12):49~51.