算法笔记p370_Dijkstra算法

本文详细介绍了Dijkstra算法的策略、具体实现、伪代码,以及在考虑边权、点权和多路径情况下的扩展,包括邻接矩阵表示法和求解最短路径的例题背景。
摘要由CSDN通过智能技术生成

Dijkstra算法的策略

设置集合S存放已被访问的顶点,然后执行n次下面的两个步骤:

  1. 每次从未被访问的顶点集合V-S中选择与起点s的最短距离最小的一个顶点(记为u),访问并加入集合S。
  2. 之后,令顶点u为中介点,优化起点s与所有从u能到达的顶点v之间的最短距离。

Dijkstra算法的具体实现

  1. 集合S可以用一个bool型数组visit[]来实现,即当visit[i] == true时,表示顶点Vi(0 ≤ i ≤ n - 1)已被访问,当visit[i] == false时,表示顶点Vi未被访问。
  2. 令int型数组dis[]表示起点s到达顶点Vi的最短距离,初始时除了起点s的dis[s]赋为0,其余顶点都赋为一个很大的数(初学者可以用1000000000,即10^9;稍微懂点二进制编码的话可以使用十六进制0x3fffffff,但不要使用0x7fffffff,因为两个这样的数相加可能会超过int的表示范围)来表示inf,即不可达。

Dijkstra算法的伪代码

Dijkstra(G, dis[], s) {
    初始化;
    for (循环n次) {
        u = 使dis[u]最小的还未被访问的顶点的标号;
        记u已被访问;
        for (从u出发能到达的所有顶点v)
            if (v未被访问 && 以u为中介点使s到达顶点v的最短距离dis[v]更优)
                优化dis[v];
    }    
}

Dijkstra算法的代码模板

全局变量

#define false 0
#define true 1
typedef int bool;
const int MaxV = 1000;      // 最大顶点数
const int INF = 1000000000; // 设INF为一个很大的数 

邻接矩阵版

int n, G[MaxV][MaxV];       // n为顶点数,MaxV为最大顶点数
int dis[MaxV];              // 起点到达各点的最短路径长度
bool visit[MaxV] = {false}; // 标记数组,visit[i] == true表示已访问. 初值均为false

void Dijkstra(int s) {      // s为起点
    /*
     * 初始化
     *  1. 将整个dis数组赋为INF
     *  2. 起点s到达自身的距离为0
     * */
    for (int i = 0; i < MaxV; ++i)
		dis[i] = INF;
    dis[s] = 0;

    /*
     * Dijkstra算法
     *  1. 设u为dis[u]最小的且还未被访问的顶点的标号;
     *  2. 记u已被访问
     *  3. 以u为中介点,优化所有u能到达且未被访问过的顶点的路径
     * */
    for (int i = 0; i < n; ++i) {   // 循环n次        
        int u = -1, MIN = INF;      // u使得d[u]最小,MIN存放该最小的dis[u]
        for (int j = 0; j < n; ++j) {
            // 找到未访问的顶点中dis[u]最小的
            if (visit[j] == false && dis[j] < MIN) {
                u = j;
                MIN = dis[j];
            }
        }
        // 找不到小于INF的dis[u],说明剩下的顶点和起点s不连通
        if (u == -1) return;

        visit[u] = true;    // 标记u为已访问

        for (int v = 0; v < n; ++v)
            // 如果v未访问 && u能到达v && 以u为中介点可以使dis[v]更优
            if (visit[v] == false && G[u][v] != INF && dis[v] > dis[u] + G[u][v])
                dis[v] = dis[u] + G[u][v];  // 优化dis[v]
    }
}

求最短路径

伪代码部分

  1. 设置数组pre[],令pre[v]表示从起点s到顶点v的最短路径上v的前一个顶点(即前驱结点)的编号。
  2. 这样,以u为中介点,当dis[v]可以被优化时,就可以将u赋给pre[v],最终就能把最短路径上每一个顶点的前驱结点记录下来。
	if (v未被访问 && 以u为中介点使s到达顶点v的最短距离dis[v]更优){
	    优化dis[v];
	    令v的前驱为u;
	}

求最短路径代码模板

// 省略
int pre[MaxV];              // pre[v]表示从起点到顶点v的最短路径上v的前一个顶点(新添加)

void Dijkstra(int s) {      // s为起点
    /*
     * 初始化
     *  1. 将整个dis数组赋为INF
     *  2. 起点s到达自身的距离为0
     *  3. 将pre数组中每个元素的前驱设为自身(新添加)
     * */
    // 省略
    // 初始状态设每个点的前驱为自身(新添加)
    for (int i = 0; i < n; ++i)
    	pre[i] = i;

    /*
     * Dijkstra算法
     *  1. 设u为dis[u]最小的且还未被访问的顶点的标号;
     *  2. 记u已被访问
     *  3. 以u为中介点,优化所有u能到达且未被访问过的顶点的路径
     *  4. 记录可优化顶点的前驱为u(新添加)
     * */
     // 省略
	for (int v = 0; v < n; ++v)
		// 如果v未访问 && u能到达v && 以u为中介点可以使dis[v]更优
		if (visit[v] == false && G[u][v] != INF && dis[v] > dis[u] + G[u][v]) {
			dis[v] = dis[u] + G[u][v];  // 优化dis[v]
			pre[v] = u;                 // 记录v的前驱顶点是u(新添加)
		}
}

// 递归求最短路径
void DFS(int s, int v) {            // s为起点编号,v为当前访问的顶点编号(从终点开始递归)
    if (v == s) {                   // 如果当前已经达到顶点s,则输出起点并返回
        printf("%d\n", s);
        return;
    }
    DFS(s, pre[v]);                 // 递归访问v的前驱结点pre[v]
    printf("%d\n", v);      		// 从起点返回后,输出每一层的顶点号
}

第二标尺

当起点到终点的最短距离最小的路径不止一条时,题目会给出一个第二标尺(第一标尺是距离),要求在所有最短路径中选择第二标尺最优的一条路径。第二标尺常见的是一下三种出题方法或其组合:

  1. 每条边增加一个边权,然后在最短路径有多条时,要求路径上的边权之和最大或最小。
  2. 每个点增加一个点权,然后在最短路径有多条时,要求路径上的点权之和最大或最小。
  3. 直接问有多少条最短路径

对这三种出题方法,都只需要增加一个数组来存放新增的边权或点权或最短路径条数,然后在Dijkstra算法中修改优化dis[v]的那个步骤即可,其他部分不需要改动。

新增边权

  1. 以新增边权代表花费为例,用cost[u][v]表示u→v的花费(由题目输入)
  2. 并增加一个数组c[],令起点s到达顶点u的最少花费为c[u],初始化时只有c[s]为0、其余c[u]均为INF。
// 省略
int cost[MaxV][MaxV];
int c[MaxV];

void Dijkstra(int s) {
    /*
     * 初始化
     * */
	// 省略
    for (int u = 0; u < n; ++u)
        c[u] = INF;
    c[s] = 0;

    /*
     * Dijkstra算法
     * */
	// 省略
    for (int v = 0; v < n; ++v) {
    	// 如果v未访问 && u能到达v
        if (visit[v] == false && G[u][v] != INF) {
            if (dis[v] > dis[u] + G[u][v]) {    // 以u为中介点可以使dis[v]更优
                // 优化dis[v]和c[v]
                dis[v] = dis[u] + G[u][v];
                c[v] = c[u] + cost[u][v];
            } else if (dis[v] == dis[u] + G[u][v] && c[v] > c[u] + cost[u][v]) {
                c[v] = c[u] + cost[u][v];		// 最短距离相同时优化c[v]
            }
        }
    }
}

新增点权

  1. 以新增点权代表物资为例,用weight[u]表示城市u中的物资数目(由题目输入)
  2. 并增加一个数组w[],令起点s到达顶点u可以收集到的最大物资为w[u],初始化时只有w[s]为weight[s]、其余w[u]均为0。
// 省略
int weight[MaxV];
int w[MaxV];

void Dijkstra(int s) {
    /*
     * 初始化
     * */
	// 省略
    for (int u = 0; u < n; ++u)
        w[u] = 0;
	w[s] = weight[s];

    /*
     * Dijkstra算法
     * */
	// 省略
    for (int v = 0; v < n; ++v) {
    	// 如果v未访问 && u能到达v
        if (visit[v] == false && G[u][v] != INF) {
            if (dis[v] > dis[u] + G[u][v]) {    // 以u为中介点可以使dis[v]更优
                // 优化dis[v]和w[v]
                dis[v] = dis[u] + G[u][v];
                w[v] = w[u] + weight[v];
            } else if (dis[v] == dis[u] + G[u][v] && w[v] < w[u] + weight[v]) {
                w[v] = w[u] + weight[v];		// 最短距离相同时优化w[v]
            }
        }
    }
}

求最短路径条数

  1. 只需要增加一个数组num[],令从起点s到达顶点u的最短路径条数为num[u],初始化时只有num[s]为1、其余nun[u]均为0。
  2. 以u为中介点,当dis[v]可被优化时,s→v的最短路径条数等于s→u的最短路径条数;当s→v(不经过u)的距离和s→u→v(以u为中介点)的距离相同时,s→v的最短路径条数 = s→v的最短路径条数(不经过u)+ s→u的最短路径条数。
// 省略
int num[MaxV];

void Dijkstra(int s) {
    /*
     * 初始化
     * */
	// 省略
    for (int u = 0; u < n; ++u)
        num[u] = 0;
    num[s] = 1;

    /*
     * Dijkstra算法
     * */
	// 省略
    for (int v = 0; v < n; ++v) {
    	// 如果v未访问 && u能到达v
        if (visit[v] == false && G[u][v] != INF) {
            if (dis[v] > dis[u] + G[u][v]) {    // 以u为中介点可以使dis[v]更优
                dis[v] = dis[u] + G[u][v];		// 优化dis[v]
                num[v] = num[u];                // num[v]继承num[u]
            } else if (dis[v] == dis[u] + G[u][v]) {
                num[v] += num[u];				// 最短距离相同时累加num
            }
        }
    }
}

例题

  • 38
    点赞
  • 31
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值