迪杰斯特拉,弗洛伊德,bellman-ford和SPFA的学习笔记

迪杰斯特拉算法

算法概念

迪杰斯特拉算法用于查找图中某个顶点到其它所有顶点的最短路径
但是有一个点,就是用迪杰斯特拉算法时所有边的权值为非负数

如何实现算法

算法流程图

在这里插入图片描述

算法详解

在这里插入图片描述
我们讲解一下哈这个是一个D–A的遍历
我们要记住要使用两个矩阵来存储,一个用啦存储点是否使用过,一个用来计算边是否使用过

首先我们以定点D为初始点,接下来D是不是有两个连接点也就是C,E,我们比较CE的3的带权值也就是3最小。

C又和F、D、E三个点连接,但是D点是已经用过了,接下来就不用了,我们选取E点,因为我们选最短的嘛。

可是这里有人就问了,到F点不是D–E–F更短么,这里我们接下来就会比较一次因为DE是有连接这里我们会把已经走过的C–D–E进行比较,如果是CDE小那就按照原来的路走,反过来就按DE走并且记录下到C的最小距离是3,然后接下来选取DE路径E点与F、G连接,选最短的F。

这里我们就已经遍历了4个点,并且得到了到这四个电最短的路径了,然后我们看F连接了剩下的3个点,我们选取最短的,这里图上有误应该是先选取B点。

我们直接看图上的第六部分,这里我们会发现DCB是13,DEFB也是13,我们来讲讲这里,我们还是先按照已经选定的最短路径来选,但是我们又发现BC是有一条路径的,我们就会在这里计算这条路径是否使用过,没有那么我们就用他来加上之前已经选过的最短的D到C的路径相加比较,从而得到最短的。

然后我们在看看B连接的点只有A了,所以我们就先计算这条路径,但是我们发现还有D点以及AF、AG、FG,CE还没遍历到,这里我们还是先继续选择路径也就是A-G,并且计算值,这里就剩下边AF和FG、CE没有使用了,在这我们是不是发现两个都有F点啊,所以我们就又以公共点为初始点开始重新计算得到DEFA小于DEFB,于是更新刚社数据,然后同时也计算F到G的距离并且用上边FG,得到一条更短的,更新数据,再用上最后一条,也就是EG得到最短的。这个图就遍历完了。
总结就是,先遍历点选最短的路径,然后标记边和点,最后遍历没有用上的边寻找最短的路径。

代码如下

#include <stdio.h>
#define V 20                   //顶点的最大个数
#define INFINITY 65535
typedef struct {
    int vexs[V];         //存储图中顶点数据
    int arcs[V][V];      //二维数组,记录顶点之间的关系
    int vexnum, arcnum;  //记录图的顶点数和弧(边)数
}MGraph;

//根据顶点本身数据,判断出顶点在二维数组中的位置
int LocateVex(MGraph * G, int v) {
    int i = 0;
    //遍历一维数组,找到变量v
    for (; i < G->vexnum; i++) {
        if (G->vexs[i] == v) {
            break;
        }
    }
    //如果找不到,输出提示语句,返回-1
    if (i > G->vexnum) {
        printf("no such vertex.\n");
        return -1;
    }
    return i;
}
//构造无向有权图
void CreateDG(MGraph *G) {
    printf("输入图的顶点数和边数:");
    scanf("%d %d", &(G->vexnum), &(G->arcnum));
    printf("输入各个顶点:");
    for (int i = 0; i < G->vexnum; i++) {
        scanf("%d", &(G->vexs[i]));
    }
    for (int i = 0; i < G->vexnum; i++) {
        for (int j = 0; j < G->vexnum; j++) {
            G->arcs[i][j] = INFINITY;
        }
    }
    printf("输入各个边的数据:\n");
    for (int i = 0; i < G->arcnum; i++) {
        int v1, v2, w;
        scanf("%d %d %d", &v1, &v2, &w);
        int n = LocateVex(G, v1);
        int m = LocateVex(G, v2);
        if (m == -1 || n == -1) {
            return;
        }
        G->arcs[n][m] = w;
        G->arcs[m][n] = w;
    }
}
//迪杰斯特拉算法,v0表示有向网中起始点所在数组中的下标
void Dijkstra_minTree(MGraph G, int v0, int p[V], int D[V]) {
    int final[V];//为各个顶点配置一个标记值,用于确认该顶点是否已经找到最短路径
    //对各数组进行初始化
    for (int v = 0; v < G.vexnum; v++) {
        final[v] = 0;
        D[v] = G.arcs[v0][v];
        p[v] = 0;
    }
    //由于以v0位下标的顶点为起始点,所以不用再判断
    D[v0] = 0;
    final[v0] = 1;
    int k = 0;
    for (int i = 0; i < G.vexnum; i++) {
        int min = INFINITY;
        //选择到各顶点权值最小的顶点,即为本次能确定最短路径的顶点
        for (int w = 0; w < G.vexnum; w++) {
            if (!final[w]) {
                if (D[w] < min) {
                    k = w;
                    min = D[w];
                }
            }
        }
        //设置该顶点的标志位为1,避免下次重复判断
        final[k] = 1;
        //对v0到各顶点的权值进行更新
        for (int w = 0; w < G.vexnum; w++) {
            if (!final[w] && (min + G.arcs[k][w] < D[w])) {
                D[w] = min + G.arcs[k][w];
                p[w] = k;//记录各个最短路径上存在的顶点
            }
        }
    }
}
int main() {
    MGraph G;
    CreateDG(&G);
    int P[V] = { 0 };   // 记录顶点 0 到各个顶点的最短的路径
    int D[V] = { 0 };   // 记录顶点 0 到各个顶点的总权值
    Dijkstra_minTree(G, 0, P, D);
  
    printf("最短路径为:\n");
    for (int i = 1; i < G.vexnum; i++) {
        printf("%d - %d的最短路径中的顶点有:", i, 0);
        printf(" %d-", i);
        int j = i;
        //由于每一段最短路径上都记录着经过的顶点,所以采用嵌套的方式输出即可得到各个最短路径上的所有顶点
        while (P[j] != 0) {
            printf("%d-", P[j]);
            j = P[j];
        }
        printf("0\n");
    }
    printf("源点到各顶点的最短路径长度为:\n");
    for (int i = 1; i < G.vexnum; i++) {
        printf("%d - %d : %d \n", G.vexs[0], G.vexs[i], D[i]);
    }
    return 0;
}

弗洛伊德算法

算法概念

弗洛伊德算法定义了两个二维矩阵:

矩阵D记录顶点间的最小路径
例如D[0][3]= 10,说明顶点0 到 3 的最短路径为10;
矩阵P记录顶点间最小路径中的中转点
例如P[0][3]= 1 说明,0 到 3的最短路径轨迹为:0 -> 1 -> 3。
它通过3重循环,k为中转点,v为起点,w为终点,循环比较D[v][w] 和 D[v][k] + D[k][w] 最小值,如果D[v][k] + D[k][w] 为更小值,则把D[v][k] + D[k][w] 覆盖保存在D[v][w]中。

算法讲解

在这里插入图片描述
顶点名称和下标的对应
A B C D E F G
0 1 2 3 4 5 6

第2步:
以A为中间点,原D矩阵中,D[B][G]的值为INF,即不存在B->G的最小路径,但是通过A为中间点,D[B][A] + D[A][G] = 12 + 14 = 26 小于 D[B][G] = INF, 所以D[B][A] + D[A][G] 为 B -> G的最小值,因此覆盖D[B][G] 为 26。

第3步:
以B为中间点,第2步后的D矩阵中,D[A][C]的值为INF, 但是通过B,D[A][B] + D[B][C] = 12 + 10 = 22 小于 D[A][C] = INF,所以D[A][B] + D[B][C] 为 A->C的最小路径,覆盖D[A][C]的值为22, 以此类推。

代码如下

#include <stdio.h>
#include <stdlib.h>

#define MAXN 10 
#define INF = 1000

typedef struct struct_graph{
    char vexs[MAXN];
    int vexnum;//顶点数 
    int edgnum;//边数 
    int matirx[MAXN][MAXN];//邻接矩阵 
} Graph;

int pathmatirx[MAXN][MAXN];//记录对应点的最小路径的前驱点,例如p(1,3) = 2 说明顶点1到顶点3的最小路径要经过2 
int shortPath[MAXN][MAXN];//记录顶点间的最小路径值

void short_path_floyd(Graph G, int P[MAXN][MAXN], int D[MAXN][MAXN]){
    int v, w, k;
    //初始化floyd算法的两个矩阵 
    for(v = 0; v < G.vexnum; v++){
        for(w = 0; w < G.vexnum; w++){
            D[v][w] = G.matirx[v][w];
            P[v][w] = w;
        }
    }

    //这里是弗洛伊德算法的核心部分 
    //k为中间点 
    for(k = 0; k < G.vexnum; k++){
        //v为起点 
        for(v = 0 ; v < G.vexnum; v++){
            //w为终点 
            for(w =0; w < G.vexnum; w++){
                if(D[v][w] > (D[v][k] + D[k][w])){
                    D[v][w] = D[v][k] + D[k][w];//更新最小路径 
                    P[v][w] = P[v][k];//更新最小路径中间顶点 
                }
            }
        }
    }

    printf("\n初始化的D矩阵\n");
    for(v = 0; v < G.vexnum; v++){
        for(w = 0; w < G.vexnum; w++){
            printf("%d ", D[v][w]);
        }
        printf("\n");
    }

    printf("\n初始化的P矩阵\n");
    for(v = 0; v < G.vexnum; v++){
        for(w = 0; w < G.vexnum; w++){
            printf("%d", P[v][w]);
        }
        printf("\n");
    }

    v = 0;
    w = 3;
    //求 0 到 3的最小路径
    printf("\n%d -> %d 的最小路径为:%d\n", v, w, D[v][w]);
    k = P[v][w];
    printf("path: %d", v);//打印起点
    while(k != w){
        printf("-> %d", k);//打印中间点
        k = P[k][w]; 
    }
    printf("-> %d\n", w);
}

int main(){
    int v, w;
    Graph G;
    printf("请输入顶点数:\n");
    scanf("%d", &G.vexnum);
    printf("请输入初始矩阵值:\n");
    for(v = 0; v < G.vexnum; v++){
        for(w = 0; w < G.vexnum; w++){
            scanf("%d", &G.matirx[v][w]);
        }
    }
    printf("\n输入的矩阵值:\n");
    for(v = 0; v < G.vexnum; v++){
        for(w = 0; w < G.vexnum; w++){
            printf("%d ", G.matirx[v][w]);
        }
        printf("\n");
    }
    short_path_floyd(G, pathmatirx, shortPath);
}

SPFA算法

算法概念

由已知向未知更新,如果一个结点发生了更新,则需要对这个点的相邻的点进行更新。入队表示这个结点需要对其相邻的点进行更新。

代码如下

#include<iostream>
#include<cstring>
#include<queue>
 
using namespace std;
 
const int N = 100010;
 
int n,m;
int h[N],w[N],e[N],ne[N],idx;
int dist[N];
bool st[N];
 
void add(int a,int b,int c)
{
    e[idx]=b;
    w[idx]=c;
    ne[idx]=h[a];
    h[a]=idx++;
}
 
int spfa()
{
    memset(dist,0x3f,sizeof dist);
    dist[1] = 0;
    
    queue<int> q;
    q.push(1);
    st[1]=true;
    
    while(q.size())
    {
        int t=q.front();
        q.pop();
        
        st[t]=false;
        
        for(int i=h[t];i!=-1;i=ne[i])
        {
            int j = e[i];
            //如果一个结点发生了更新,则需要对这个点的相邻的点进行更新
            if(dist[j]>dist[t]+w[i])
            {
                dist[j] = dist[t] + w[i];
                if(!st[j])
                {
                    q.push(j);
                    st[j]=true;
                }
            }
        }
    }
    
    return dist[n];
}
int main()
{
    scanf("%d%d",&n,&m);
    
    memset(h,-1,sizeof(h));
    
    while(m--)
    {
        int a,b,c;
        scanf("%d%d%d",&a,&b,&c);
        add(a,b,c);
    }
    
    int t = spfa();
    
    if(t == 0x3f3f3f3f)puts("impossible");
    else printf("%d\n",t);
    
    return 0;
}

bellman-ford算法

算法概念

算法描述:由已知向未知更新,每次循环都只能更新已知的有最小距离的点的相邻的点的距离,因为存在负权边,所以循环k次只能求得1号点到 n号点的最多经过 k条边的最短距离。

算法讲解

在这里插入图片描述

代码如下
#include<iostream>
#include<cstring>
 
 
using namespace std;
const int N = 510,M=10010;
struct Edge
{
    int a,b,c;
}edges[M];
int dist[N];
int last[N];
 
int n,m,k;
 
void bellman_ford()
{
    memset(dist,0x3f,sizeof(dist));
    
    dist[1]=0;
    for(int i=0;i<k;i++)
    {
        memcpy(last,dist,sizeof(dist));
        //对m条边进行遍历
        for(int j=0;j<m;j++)
        {
            auto e = edges[j];
            //dist[e.b]要么就是>0x3f3f3f3f/2,要么就是离原点的最小距离
            //每次循环都只能更新已知的有最小距离的点的相邻的点的距离
            dist[e.b]=min(dist[e.b],last[e.a]+e.c);
        }
    }
}
 
int main()
{
    scanf("%d%d%d",&n,&m,&k);
    
    for(int i=0;i<m;i++)
    {
        int a,b,c;
        scanf("%d%d%d",&a,&b,&c);
        edges[i] = {a,b,c};
    }
    
    bellman_ford();
    
    if(dist[n] > 0x3f3f3f3f/2)puts("impossible");
    else printf("%d\n",dist[n]);
    
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值