您的位置:首页 > 其它

关于最短路径算法的理解

2015-07-19 18:17 183 查看
最短路径的算法主要是解决两个问题:

1.单源最短路

2.任意两点间的最短路

主要有三个算法来解决这两个问题:

1.单源最短路:dijkstra算法,spfa算法

2.任意两点间的最短路:floyd算法

----------------------------------------dijkstra单源最短路算法----------------------------------------------------------------

应用范围:边权非负的有向图和无向图均可

算法思想:

1.因为边权非负,所以任意两条边相加的和一定不小于其中任意一条边,我们设源点为s,那么要求所有点到达s的最短路径,那么我们定义一个点集S,定义一个数组dis[],表示某个点只与点集中的点连接的情况到达源点的最短距离,如果暂时不能到达,记为INF,初始情况下点集中只有源点s,那么与源点直接相连的点中边权最小的点一定是最短路径,设最小边连接的点为v,u为除s和v外的任意一点,因为dis[v] < dis[u]且边权非负,所以dis[v]<dis[u]+一条或多条边的边权,所以当前的决策一定是最短路,且之后一定不能更新出更短的路径。

2.那么点v的最短路径已经得到,我们可以把它添加到S点集,然后还要继续维护dis[]数组的性质,也就是如果当前的v点与S点集外的一点u相连,如果dis[v]+edge[v,u] < dis[u],那么点u可以dis[u]就应该更新,因为定义中是定义的经过点集中的点得到的最短的路径,然后这个S点集我们就可以缩点为一个点了,然后又变为了1的情况,知道处理的所有点都计入S点集,也就是整个图缩成了一个点,那么整个图到达s点的最短路径就处理出来了

下面给出O(n^2)的实现方法

邻接矩阵的实现方法:

void dijkstra(int s){  
     memset(vis , 0 , sizeof(vis));  
     memset(father , 0 , sizeof(father));  
     /*初始化dis数组*/  
     for(int i = 1 ; i<= n ; i++)  
          dis[i] = INF;  
     dis[s] = 0;  
     for(int i = 1 ; i <= n ; i++){/*枚举n个顶点*/  
        int pos;  
        pos = -1;  
        for(int j = 1 ; j <= n ;j++){/*找到未加入集合的最短路点*/  
           if(!vis[j] && (pos == -1 || dis[j] < dis[pos]))  
              pos = j;  
        }  
        vis[pos] = 1;/*把这个点加入最短路径集合*/  
        for(int j = 1 ; j <= n ; j++){/*更新dis数组*/  
           if(!vis[j] && (dis[j] > dis[pos] + value[pos][j])){  
             dis[j] = dis[pos] + value[pos][j];  
             father[j] = pos;  
           }  
        }  
     }  
}


主要是两个操作:

1.找出当前S集外的dis[v]最小的点v(可以利用堆优化)

2.将v加入点集S,然后更新dis数组

下面是利用堆优化的O(n*loge)

struct cmp  
{  
    bool operator ( ) ( const int&a , const int&b ) const   
    {  
        return dis[a] > dis[b];  
    }  
};  
  
void dijkstra ( )  
{  
    memset ( dis , 0x3f , sizeof ( dis ) );  
    memset ( used , 0 , sizeof ( used ) );  
    priority_queue<int , vector<int> , cmp > q;  
    dis[s] = 0;  
    q.push ( s );  
    while ( !q.empty( ) )  
    {  
        int u = q.top();  
        q.pop();  
        used[u] = true;  
        for ( int v = 1 ; v <= n ; v++ )  
            if ( !used[v]&&dis[v] > dis[u] + mp[u][v] )  
            {  
                dis[v] = dis[u] + mp[u][v];  
                q.push ( v );  
            }  
    }  
}


---------------------------------------------spfa求单源最短路-------------------------------------------------------

适用范围:有负权的图,可以判断负环,如果某个点松弛次数大于等于n,那么说明有负环

算法思想:很直观的宽搜的思想,近似于暴力,复杂度O(ke),k是每个点平均入队次数,可以证明出K<=2

dis数组,记录到源点的最短路径,如果当前不是最短,那么一定能够被某个点点松弛,相反,如果当前点不能够被松弛,那么它就一定是最短路径了

首先科普一个操作:就是松弛操作,这个操作就是利用当前的点的的dis值取更新它能够到达的点的dis值

在宽搜的过程中我们维护一个队列,这个队列中存放的刚刚被松弛过路径的点,初始的时候将源点放入队列中,因为当一个点松弛过后,它的最短路径变短,可能会导致其他点能够被松弛,所以当前点入队,当队列为空也就是没有点能够松弛其他点了,也就是说一个点最多直接间接地被其他n-1个点松弛一遍,所以当松弛数大于等于n的时候证明出现了负环,因为负环能够不断的松弛一条最短路径

代码如下:

struct Edge  
{  
    int v,w,next;  
}e[MAX*MAX];  
  
int head[MAX];  
int cc;  
  
void add ( int u , int v , int w )  
{  
    e[cc].v = v;  
    e[cc].w = w;  
    e[cc].next = head[u];  
    head[u] = cc++;  
}  
  
bool used[MAX];  
int dis[MAX];  
int cnt[MAX];  
  
bool spfa ( int s , int d )  
{  
    memset ( used , false , sizeof ( used ) );  
    memset ( dis , -1 , sizeof ( dis ) );  
    memset ( cnt , 0 ,sizeof ( cnt ) );  
    queue<int> q;  
    q.push ( s );  
    cnt[s]++;  
    used[s] = true;  
    dis[s] = 0;  
    while ( !q.empty())  
    {  
        int u = q.front();  
        q.pop ( );  
        used[u] = false;  
        for ( int i = head[u] ; ~i ; i = e[i].next )  
        {  
            int v = e[i].v;  
            if ( dis[v] != -1 && dis[v] <= dis[u] + e[i].w ) continue;  
            dis[v] = dis[u] + e[i].w;  
            if ( used[v] ) continue;  
            used[v] = true;  
            q.push ( v );  
            if ( ++cnt[v] > n ) return false;  
        }  
    }  
    return true;  
}
---------------------------------------------floyd求任意两点间的最短路-----------------------------------------------

适用范围:存在负权的图不能用

算法思想:n^3的复杂度不算优秀,但是容易实现。

首先floyd是一个传递闭包的思想,比如A,B,C三个点,A和B存在边,B和C存在边,那么A和C也可以推出直接相连的边

我们枚举这个n个点,然后对于每个点,枚举任意两点,然后更新dis[i][j],就相当于传递闭包当中利用两条边去推导出第三条边,但是floyd求最短时,每两点之间只保留一条边,最小那条,也就是如果本身存在边,那么新添的边与其进行比较,大的那条边被覆盖。整个floyd过程下来相当于整个传递闭包求了一遍,然后所有的边保留下来均是在每次覆盖操作后留下来的最小的,可能有的人会问?如果当前利用k点更新了dis[i][j] = dis[i][k] + dis[k][j],之后又出现了一个v更新了dis[i][k],还是最短的吗?其实是这样的,因为k在更新dis[i][j]的同时一定也会更新dis[v][j],那么到枚举v的时候dis[i][v]和dis[v][j]更新dis[i][j],就相当于用更小的dis[i][k]进行了松弛。

代码如下:

for ( int k = 0 ; k < n ; k++ )
            for ( int i = 0 ; i < n ; i++ )
                for  ( int j = 0 ; j < n ; j++ )
                        dis[i][j] = max ( dis[i][j] , dis[i][k] + dis[k][j] );
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: