图的四种搜索方法

· · 算法·理论

dijkstra 算法

代码原理:

通过已知的点更新未知的点:

if(!vis[y] && dis[y]>dis[x]+d){//判断是否更新
  dis[y]=dis[x]+d;
  Q.push({y,dis[y]});
}

示例代码:

inline bool operator <(node a,node b){
    return a.y>b.y;
}
inline void add(int x,int y,int z){
    ver[++tot]=y,edg[tot]=z;
    nxt[tot]=head[x],head[x]=tot;
}
inline void dij(int x){
    priority_queue<node> Q;
    Q.push({x,0});
    memset(dis,0x3f,sizeof dis);//趋近于无穷大
    dis[x]=0;
    while(!Q.empty()){
        int x=Q.top().x;Q.pop();
        vis[x]=1;//标记
        for(int i=head[x];i;i=nxt[i]){
            int y=ver[i],d=edg[i];
            if(!vis[y] && dis[y]>dis[x]+d){//判断是否更新
                dis[y]=dis[x]+d;
                Q.push({y,dis[y]});
            }
        }
    }
}

适用情况:

dijkstra算法可行的条件是不存在负边权。

时间复杂度 O(n log)

spfa

实现原理:

计算所有的情况。 当有同样元素在队列中时,就不入队:

if(!vis[y] ){//如果有同样元素在队列里,就不入队。
  vis[y]=1;//标记入队。
  Q.push(y);
}

对于全是正边权的图,我们可以使用dijkstra处理最短路问题。但是如果带有负边权,需考虑是否存在负权环,如果起点与终点的路径经过负权环,则没有最短路。dijkstra算法无法保证节点出队时一定得到最短路,需要重新寻找方法。

显然,spfa的复杂度会比dijkstra高,一般为:O(km),k是一个常数,在稀疏图中一般<=2。 但是要卡spfa也比较简单,只需尽可能多得让节点重复入队。最坏复杂度可以做到O(nm)。在使用时需要特别注意。

示例代码:

inline void add(int x,int y,int z){
    ver[++tot]=y,edg[tot]=z;
    nxt[tot]=head[x],head[x]=tot;
}
inline void spfa(int xx){
    queue<int> Q;
    Q.push(xx);
    memset(dis,0x3f,sizeof dis);
    dis[xx]=0;
    while(!Q.empty()){
        int x=Q.front();Q.pop();
        vis[x]=0;//标记出队
        for(int i=head[x];i;i=nxt[i]){
            int y=ver[i],d=edg[i];
            if(dis[y]>dis[x]+d){
                dis[y]=dis[x]+d;
                if(!vis[y] ){//如果有同样元素在队列里,就不入队。
                    vis[y]=1;//标记入队。
                    Q.push(y);
                }

            }
        }
    }
}

判断负环:

inline void add(int x,int y,int z){
    ver[++tot]=y,edg[tot]=z;
    nxt[tot]=head[x],head[x]=tot;
}
inline void spfa(int xx){
    queue<int> Q;
    memset(dis,0x3f,sizeof dis);
    for(int i=1;i<=n;i++) {
        vis[i]=1;Q.push(i);
    }
    while(!Q.empty()){
        int x=Q.front();Q.pop();
        vis[x]=0;
        for(int i=head[x];i;i=nxt[i]){
            int y=ver[i],d=edg[i];
            if(dis[y]>dis[x]+d){
                dis[y]=dis[x]+d,cut[y]=cut[x]+1;
                if(cut[y]>n||YN){//判断是否便利超过n次
                    YN=1;return;
                }
                if(!vis[y] ){
                    vis[y]=1;
                    Q.push(y);
                }
            }
        }
    }
}

适用情况:

带负边权的最短路

bellman_ford

实现原理:

n 1 2 3
1 0 无限 无限
2 0 2 无限
3 0 2 3

示例代码:

void bellman_ford(int x){
    memset(dis,0x3f,sizeof dis);
    dis[x]=0;
    for(int i=1;i<=k;i++){
        memcpy(odis,dis,sizeof dis);//复制数组
        for(int j=1;j<=m;j++){
            dis[l[j].y]=min(dis[l[j].y],odis[l[j].x]+l[j].w);
        }
    }
}

判断负环:

bool bellman_ford(int x){
    memset(dis,0x3f,sizeof dis);
    dis[x]=0;
    for(int i=1;i<n;i++){
        memcpy(odis,dis,sizeof dis);//复制数组
        for(int j=1;j<=m;j++){
            dis[l[j].y]=min(dis[l[j].y],odis[l[j].x]+l[j].w);
        }
    }
    for(int i=1;i<=m;i++){
        if(dis[l[i].x]+l[i].w<dis[l[i].y]) return 0;
    }
    return 1;
}

适用情况:

有边数限制的最短路、

复杂度为O(nm)

floyd

实现原理:

floyd本质上就是通过不断为两点的路径上增加中间点(所以又称为插点法),进而对邻接矩阵进行更新迭代,每加入一个点,矩阵信息就可能变化一次,从而维护该最小距离矩阵。

如下:

if(l[i][j]>l[i][k]+l[k][j]){
    l[i][j]=l[i][k]+l[k][j];
}

初始表格: 1 2 3 4
1 0 2
2 1 0 6
3 8 3 0 11
4 0
加入1为中间点: 1 2 3 4
1 0 2
2 1 0 {\color{blue} 3 }
3 8 3 0 {\color{blue} 10 }
4 0
加入2为中间点: 1 2 3 4
1 0 2
2 1 0 {\color{blue} 3 }
3 {\color{red} 4 } 3 0 {\color{red} 6 }
4 0

示例代码:

    for(int k=1;k<=n;k++){
        for(int i=1;i<=n;i++){
            for(int j=1;j<=n;j++){
                if(l[i][j]>l[i][k]+l[k][j]){
                    l[i][j]=l[i][k]+l[k][j];
                    pre[i][j]=k;
                }
            }
        }
    }
}

判断负环:

int floyd(){
    for(int k=1;k<=n;k++){
        for(int i=1;i<=n;i++){
            for(int j=1;j<=n;j++){
                if(l[i][j]>l[i][k]+l[k][j]){
                    l[i][j]=l[i][k]+l[k][j];
                }
            }
        }
    }
    for(int k=1;k<=n;k++){
        for(int i=1;i<=n;i++){
            for(int j=1;j<=n;j++){
                if(l[i][j]>l[i][k]+l[k][j]){
                    return 1;
                }
            }
        }
    }
    return 0;
}

适用情况:

求任意两点之间的距离

时间复杂度:O(n^3)

“对于重要的事物,我们的眼光总是模糊不清。只有未来才能给予我们足够的远见。”————邓布利多