算法导论-第24章 Dijkstra算法

           Dikstra算法解决的是有向图上单源最短路径问题(无向图可以看成有相反的两条有向边),且要求边的权重都是非负值(如果有负权,已经确定的最短路径到v点有可能会被右面发现的点有条负权值的边到v点而导致v点的最短路径不成立)。

           算法导论用了很多引理,性质来证明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
#include
#include
#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<adjvex<<" : "<weight<<"      ";
        p=p->nextvex;
      }
      cout<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"<

MiniPriorityQueue.h:

#ifndef MINIPRIORITYQUEUE_H_INCLUDED
#define MINIPRIORITYQUEUE_H_INCLUDED

#include
#include
#include
 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<>n;

 g.Adj=new adjnode[n+1];// 0号不用
 g.vexs=new vex[n+1];
 g.n=n;

cout<<"依次输入与每个顶点相邻的顶点号和权值,以-1为结束"<>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=1;--start){
    MinHeapFix(start,g);
    }
    }



#endif // MINIPRIORITYQUEUE_H_INCLUDED

我的图建立重定向了:

test.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

图的图片:

算法导论-第24章 Dijkstra算法_第1张图片

s=1   ,t=2  ,x=3 , y=4 ,z =5


运行结果:

算法导论-第24章 Dijkstra算法_第2张图片

最后我们可以发现,广度优先算法其实就是Dijkstra算法的简化,其中边的权重全部变为1,Q队列用先进先出队列实现,


你可能感兴趣的:(算法导论)