Dikstra算法解决的是有向图上单源最短路径问题(无向图可以看成有相反的两条有向边),且要求边的权重都是非负值。
算法导论用了很多引理,性质来证明Dijstra算法的正确性,这里不说了,也表达不明白,只说我理解的过程。
有一个图G( V,E) ,选定一个源点s,维护一个集合Q=V-s, Q中点有一个d值表示此时从s到该点的已知距离,s.d=0 ;初始化都为正无穷,表明不可达。然后对s点所连接的点(设为点集M)进行松弛操作,就是设点m属于M,
m.d > s.d+ w(s,m) 则更新 m.d=s.d+w(s,m) 将其慢慢收敛。
Q中的点能够松弛的松弛完之后,取出此时d值最小的点的,把它看成上面的s点,对其所连接点的进行松弛操作,然后再取点,重复直到Q集合为空。s点到所有点的最短路径就找到了。
再来说说实现的问题:
1.Q集合用数组来实现,我在实现的时候Q数组存在的是对应点的d值,如果这个点已经被取出则变为1;这样所有的开销都在去最小值上了,因为每次都要扫描整个数组。取点要V次,一次扫描O(V) ,所以总运行时间O(V^2);
2.Q集合用优先队列实现,但是因为要有decrease-key操作,所以貌似不能用标准库的优先队列。所以只能自己实现一个最小堆的优先队列。因为松弛的时候是找到点,然后又要去更改Q中的对应点的d值,然后在进行调整,所以要有互相指向的句柄。
取点V次,每次Extract-min花费O(lgV)
decrease-key E次 ,每次花费 O(lgV)
总花费 O( (E+V)lgV )
数组方法的时候,代码不贴出来来了,带句柄的数组建最小优先队列的方法我贴出来,其实句柄就是一个指向元素在数组堆中的位置,因为要建堆,维护堆,取出最小值等操作会因为位置变化,所以要在几个操作里面更新句柄。
Dijkstra.cpp
#include<iostream>
#include<fstream>
#include<queue>
#include"MiniPriorityQueue.h"
using namespace std;
//Dijkstra算法解决的是带权重的单源最短路径问题,要求所有边的权重都是非负值
//图的结构,点的结构,查看.h文件。
//最小堆的结构实现也在.h文件
void printGraph(Graph g)
{
for(int i=1;i!=g.n+1;++i){
adjnode p=g.Adj[i];
while(p!=NULL){
cout<<p->adjvex<<" : "<<p->weight<<" ";
p=p->nextvex;
}
cout<<endl;
}
}
void printPaht1(Graph g,int s,int v){ //打印路线递归
if(s==v)cout<<s<<" ";
else if(v==g.vexs[v].predecessor){
cout<<"结点"<<s<<"到结点"<<v<<"不可达"<<endl;
}
else{
printPaht1(g,s,g.vexs[v].predecessor);
cout<<v<<" ";
}
}
void printPath2(Graph g,int s)
{ //
cout<<"源节点"<<s<<endl;
for(int i=1;i!=g.n+1;++i)
{
if(i!=s){
int t=g.vexs[i].predecessor;
if(t==i){
cout<<"结点"<<i<<" 不可达"<<endl;continue;
}
cout<<"结点"<<i<<" "<<"路径长度 "<<g.vexs[i].d<<" 路径过程为: "<<i<<" ";
while(t!=s&&t!=i){
cout<<t<<" ";
t=g.vexs[t].predecessor;
}
cout<<s<<endl;
}
}
}
void Dijkstra(Graph g,int s)
{ //Dijstra方法的主代码。
for(int i=1;i!=g.n+1;++i){ //初始化
g.vexs[i].d=100000;
g.vexs[i].predecessor=i;
g.vexs[i].n=i;
}
g.vexs[s].d=0; //源点的初始化
g.vexs[s].n=s;
g.vexs[s].predecessor=s;
MiniPriorityQueue Q(g); //堆初始化,
Q.BuildMinHeap(g);//建堆操作,需要更新句柄
vector<vex>S; //取出的点放入S
while(S.size()!=g.n){
vex t=Q.ExtractMin(g); //从堆中取出d值最小的元素, 需要更新句柄,
int th=t.n; //vex中的n 实际就是从堆中元素指回结点的句柄
S.push_back(g.vexs[th]); //插入S中
for(adjnode p=g.Adj[th];p!=NULL;p=p->nextvex) //松弛操作。
{
int m=p->adjvex;
if(g.vexs[m].d> p->weight+g.vexs[th].d){
g.vexs[m].d=p->weight+g.vexs[th].d;
g.vexs[m].predecessor=th;
//接着要去更新堆
int h=g.vexs[m].handle;
Q.DecreaseKey(h, g.vexs[m].d ,g);
}
}
}
cout<<"\n\n"<<endl;
for(int i=1;i!=g.n+1;++i)
{printPaht1(g,s,i);
cout<<" 代价:"<<g.vexs[i].d<<endl;
}
cout<<endl;
printPath2(g,s);
}
int main()
{
Graph G;
CreateGraph(G); //建图
printGraph(G);// 打印图
Dijkstra(G,1); //源点为1,找单源最短路径
return 0;}
MiniPriorityQueue.h:
#ifndef MINIPRIORITYQUEUE_H_INCLUDED
#define MINIPRIORITYQUEUE_H_INCLUDED
#include<iostream>
#include<fstream>
#include<queue>
using namespace std;
class node
{ //邻接链表结构
public:
node():adjvex(0),nextvex(NULL){}
int adjvex;
int weight; //邻接链表结点直接带权重值
node *nextvex;
};
class vex
{ //点结构
public:
int d;
int predecessor; //前驱
int n; //表明自己几号点
int handle; //句柄,指示在堆的位置,堆由数组建立
};
typedef node * adjnode;
class Graph
{ //图结构
public:
int n;//顶点个数
adjnode *Adj;
vex * vexs;
};
void CreateGraph(Graph &g)
{
ifstream infile("test.txt");
cin.rdbuf(infile.rdbuf());
if(g.Adj==NULL)cout<<1<<endl;
cout<<"请输入顶点数"<<endl;
int n,x,w;
cin>>n;
g.Adj=new adjnode[n+1];// 0号不用
g.vexs=new vex[n+1];
g.n=n;
cout<<"依次输入与每个顶点相邻的顶点号和权值,以-1为结束"<<endl;
for(int i=1;i!=n+1;++i){
g.Adj[i]=new node;
adjnode ne=g.Adj[i];
int ex=0;
while(true){
cin>>x>>w;
if(x==-1)
break;
else
if(ex!=0){
ne->nextvex=new node;
ne=ne->nextvex;
}
++ex;
ne->adjvex=x;
ne->weight=w;
}//while
}
}
class MiniPriorityQueue{ //自建堆
public:
MiniPriorityQueue(Graph g){ //初始化
Q=new vex[g.n+1];
length=g.n;
for(int i=1;i!=g.n+1;++i){
Q[i]=g.vexs[i];
g.vexs[i].handle=i; //更新句柄
}
}
void MinHeapFix(int i,Graph &g); //维护堆的性质
void BuildMinHeap(Graph &g);//建堆
void DecreaseKey(int j,int k,Graph &g); //更新d值
vex ExtractMin(Graph &g){ //取出最小值,
vex t=Q[1];
g.vexs[Q[length].n].handle=1;// 更新句柄
Q[1]=Q[length];
length--;
MinHeapFix(1,g);
return t;
}
public:
vex* Q;
int length;
};
void MiniPriorityQueue::DecreaseKey(int j,int k,Graph &g){ //更新d值
Q[j].d=k;
int i=j;
while(i>1 && Q[i/2].d>Q[i].d){
g.vexs[Q[i/2].n].handle=i;
g.vexs[Q[i].n ].handle=i/2;
swap(Q[i/2],Q[i]);
i=i/2;
}
}
void MiniPriorityQueue::MinHeapFix(int i,Graph &g){ //堆建立过程中也要时刻更新句柄
int l=i*2;
int r=i*2+1;
int minest;
if(l<=length&& Q[l].d<Q[i].d){
minest=l;
}
else{
minest=i;
}
if(r<=length&&Q[r].d < Q[minest].d){
minest=r;
}
if(minest!=i){
g.vexs[ Q[minest].n ].handle=i; //更新句柄。
g.vexs[Q[i].n ].handle=minest;
swap(Q[minest],Q[i]);
MinHeapFix(minest,g);
}
}
void MiniPriorityQueue::BuildMinHeap(Graph &g){
int start=length/2;
for(;start>=1;--start){
MinHeapFix(start,g);
}
}
#endif // MINIPRIORITYQUEUE_H_INCLUDEDtest.txt:
5
2 10 4 5 -1 -1
3 1 4 2 -1 -1
5 4 -1 -1
2 3 3 9 5 2 -1 -1
3 6 1 7 -1 -1
图的图片:
s=1 ,t=2 ,x=3 , y=4 ,z =5
运行结果:
最后我们可以发现,广度优先算法其实就是Dijkstra算法的简化,其中边的权重全部变为1,Q队列用先进先出队列实现,
原文:http://blog.csdn.net/y519476132/article/details/40899561