首页 > 编程语言 >「代码随想录算法训练营」第五十二天 | 图论 part10

「代码随想录算法训练营」第五十二天 | 图论 part10

时间:2024-09-03 09:47:15浏览次数:10  
标签:int part10 随想录 next start 算法 grid 第五十二 节点

目录

Floyd算法

Floyd算法用于求解多源最短路问题(求多个起点到多个终点的多条最短路径)。在前面学习的dijkstra算法、Bellman算法都是求解单源最短路的问题(即只能有一个起点)。

注意:Floyd算法对边的权值正负没有要求,都可以处理。

Floyd的核心思想是动态规划

动态规划的五部曲:

  • 确定dp数组(dp table)以及下标的含义。
  • 确定递推公式。
  • dp数组如何初始化。
  • 确定遍历顺序。
  • 举例推导dp数组。

根据动态规划的五部曲来解释Floyd算法的遍历过程。

1. 确定dp数组(dp table)以及下标的含义。

把dp数组命名为grid数组,用来来存图。

grid[i][j][k] = m表示节点i到节点j中以[i...k]集合为中间节点的最短距离为m。

2. 确定递推公式。

分两种情况:

  1. 节点i到节点j的最短路径经过节点k。
  2. 节点i到节点j的最短路径不经过节点k。

对于第一种情况:grid[i][j][k] = grid[i][k][k - 1] + grid[k][j][k - 1]

对于第二种情况:grid[i][j][k] = grid[i][j][k - 1]

由于我们在求解最短路,因此对于这两种情况要取最小值:

grid[i][j][k] = min(grid[i][k][k - 1] + grid[k][j][k - 1], grid[i][j][k - 1])

3. dp数组如何初始化。

grid[i][j][k] = m,表示节点i到节点j以[1...k]集合为中间节点的最短距离为m。

4. 确定遍历顺序。

从递推公式:grid[i][j][k] = min(grid[i][k][k - 1] + grid[k][j][k - 1], grid[i][j][k - 1])可以看出,我们需要三个for循环,分别遍历i,j和k。而k依赖于k - 1,i和j的到并不依赖与i - 1或者j - 1等等。

其中遍历k的for循环一定是在最外面,这样才能一层一层去遍历。

暂不举例推导。

题目:97. 小明逛公园

题目链接:https://kamacoder.com/problempage.php?pid=1155
文章讲解:https://www.programmercarl.com/kamacoder/0097.小明逛公园.html
题目状态:看题解

思路:

Floyd算法

代码:

#include <iostream>
#include <vector>
#include <list>

using namespace std;

int main()
{
    int n, m, p1, p2, val;
    cin >> n >> m;

    // 因为边的最大距离是10^4
    vector<vector<vector<int>>> grid(n + 1, vector<vector<int>>(n + 1, vector<int>(n + 1, 10005)));
    for(int i = 0; i < m; ++i)
    {
        cin >> p1 >> p2 >> val;
        grid[p1][p2][0] = val;
        grid[p2][p1][0] = val; // 注意这里是双向图
    }
    
    // 开始Floyd
    for(int k = 1; k <= n; ++k)
    {
        for(int i = 1; i <= n; ++i)
        {
            for(int j = 1; j <= n; ++j)
            {
                grid[i][j][k] = min(grid[i][j][k - 1], grid[i][k][k - 1] + grid[k][j][k - 1]);
            }
        }
    }

    // 输出结果
    int z, start, end;
    cin >> z;
    while(z--)
    {
        cin >> start >> end;
        if(grid[start][end][n] == 10005) cout << -1 << endl;
        else cout << grid[start][end][n] << endl;
    }
}

空间优化:

我们只需要记录grid[i][j][1]grid[i][j][0]就好,之后就是grid[i][j][1]grid[i][j][0]交替滚动。

#include <iostream>
#include <vector>
using namespace std;

int main() {
    int n, m, p1, p2, val;
    cin >> n >> m;

    vector<vector<int>> grid(n + 1, vector<int>(n + 1, 10005));  // 因为边的最大距离是10^4

    for(int i = 0; i < m; i++){
        cin >> p1 >> p2 >> val;
        grid[p1][p2] = val;
        grid[p2][p1] = val; // 注意这里是双向图

    }
    // 开始 floyd
    for (int k = 1; k <= n; k++) {
        for (int i = 1; i <= n; i++) {
            for (int j = 1; j <= n; j++) {
                grid[i][j] = min(grid[i][j], grid[i][k] + grid[k][j]);
            }
        }
    }
    // 输出结果
    int z, start, end;
    cin >> z;
    while (z--) {
        cin >> start >> end;
        if (grid[start][end] == 10005) cout << -1 << endl;
        else cout << grid[start][end] << endl;
    }
}

A * 算法

Astar是一种广搜的改良版。有的是Astar是dijkstra的改良版。

其实只是场景不同而已 我们在搜索最短路的时候,如果是无权图(边的权值都是1)那就用广搜,代码简洁,时间效率和dijkstra差不多(具体要取决于图的稠密)

如果是有权图(边有不同的权值),优先考虑dijkstra。

而Astar关键在于启发式函数, 也就是影响广搜或者dijkstra从容器(队列)里取元素的优先顺序。

本博客中使用BFS版本的A*算法。

启发式函数要影响的就是队列里元素的排序。

这是影响BFS搜索方向的关键。

对队列里节点进行排序,就需要给每一个节点权值,如何计算权值呢?

每个节点的权值为F,给出公式为:F = G + H

  • G:起点达到目前遍历节点的距离
  • H:目前遍历的节点到达终点的距离

起点达到目前遍历节点的距离 + 目前遍历的节点到达终点的距离就是起点到达终点的距离。

本题的图是无权网格状,在计算两点距离通常有如下三种计算方式:

  • 曼哈顿距离,计算方式: d = abs(x1-x2)+abs(y1-y2)
  • 欧氏距离(欧拉距离) ,计算方式:d = sqrt( (x1-x2)^2 + (y1-y2)^2 )
  • 切比雪夫距离,计算方式:d = max(abs(x1 - x2), abs(y1 - y2))

x1, x2 为起点坐标,y1, y2 为终点坐标 ,abs 为求绝对值,sqrt 为求开根号,

选择哪一种距离计算方式 也会导致 A * 算法的结果不同。

动态模拟地址:https://kamacoder.com/tools/knight.html

题目:126.骑士的攻击

题目链接:https://kamacoder.com/problempage.php?pid=1203
文章讲解:https://www.programmercarl.com/kamacoder/0126.骑士的攻击astar.html
题目状态:看题解

思路:

A*算法

代码:

#include <iostream>
#include <queue>
#include <string.h>

using namespace std;

int moves[1001][1001];
int dir[8][2] = {-2, -1, -2, 1, -1, 2, 1, 2, 2, 1, 2, -1, 1, -2, -1, -2};
int b1, b2;

// F = G + H
// G = 从起点到该节点路径消耗
// H = 该节点到终点的预估消耗

struct Knight
{
    int x, y;
    int g, h, f;
    // 重载运算符,从小到大排序
    bool operator<(const Knight &k) const
    {
        return k.f < f;
    }
};

priority_queue<Knight> que;

// 欧拉距离
int Heuristic(const Knight &k)
{
    // 统一不开根号,这样可以提高精度
    return (k.x - b1) * (k.x - b1) + (k.y - b2) * (k.y - b2);
}

void astar(const Knight &k)
{
    Knight cur, next;
    que.push(k);
    while(!que.empty())
    {
        cur = que.top(); que.pop();
        if(cur.x == b1 && cur.y == b2) break;
        for(int i = 0; i < 8; ++i)
        {
            next.x = cur.x + dir[i][0];
            next.y = cur.y + dir[i][1];
            if(next.x < 1 || next.x > 1000 || next.y < 1 || next.y > 1000) continue;
            if(!moves[next.x][next.y])
            {
                moves[next.x][next.y] = moves[cur.x][cur.y] + 1;
                // 开始计算F
                next.g = cur.g + 5; // 统一不开根号,这样可以提高精度,马走日,1*1+2*2=5
                next.h = Heuristic(next);
                next.f = next.g + next.h;
                que.push(next);
            }
        }
    }
}

int main()
{
    int n, a1, a2;
    cin >> n;
    while(n--)
    {
        cin >> a1 >> a2 >> b1 >> b2;
        memset(moves, 0, sizeof(moves));
        Knight start;
        start.x = a1;
        start.y = a2;
        start.g = 0;
        start.h = Heuristic(start);
        start.f = start.g + start.h;
        astar(start);
        while(!que.empty()) que.pop(); // 队列清空
        cout << moves[b1][b2] << endl;
    }
    return 0;
}

最短路算法总结

image

标签:int,part10,随想录,next,start,算法,grid,第五十二,节点
From: https://www.cnblogs.com/frontpen/p/18393842

相关文章

  • 代码随想录算法训练营|Day01 LeetCode 704.二分查找,27.移除元素,977.有序数组的平方
    数组理论基础数组是存放在连续空间上的相同类型数据的集合数组的元素是不能删的,只能覆盖704.二分查找LeetCode:704.有序数组的平方classSolution{public:intsearch(vector<int>&nums,inttarget){intlength=nums.size();inti=0......
  • 代码随想录算法训练营,9月2日 | 242.有效的字母异位词,349. 两个数组的交集,202. 快乐数,1
    哈希表理论基础1.根据关键码的值而直接进行访问的数据结构(直白来讲其实数组就是一张哈希表,哈希表中关键码就是数组的索引下标,然后通过下标直接访问数组中的元素);2.哈希表都是用来快速判断一个元素是否出现集合里;3.哈希函数:把值对应到哈希表的函数;哈希碰撞:映射到哈希表同一个索引......
  • 【代码随想录Day6】哈希表Part01|判断一个元素是否出现集合里
    哈希表理论基础文章讲解:哈希表理论基础要快速判断一个元素是否出现集合里的时候,就要考虑哈希法。242.有效的字母异位词题目链接/文章讲解/视频讲解:有效的字母异位词定义一个哈希表record,遍历s,记录s中每个字母出现的次数,遍历t,减去t中每个字母出现的次数,遍历......
  • 代码随想录day48 || 739, 每日温度 496, 下一个更大元素 I 503, 下一个更大元素II
    739每日温度funcdailyTemperatures(temperatures[]int)[]int{ //双指针 varres=make([]int,len(temperatures)) fori:=0;i<len(temperatures);i++{ forj:=i+1;j<len(temperatures);j++{ iftemperatures[j]>temperatures[i]{ res[i]=j......
  • 代码随想录算法day4 - 哈希表2
    题目1454.四数相加II给你四个整数数组nums1、nums2、nums3和nums4,数组长度都是n,请你计算有多少个元组(i,j,k,l)能满足:0<=i,j,k,l<nnums1[i]+nums2[j]+nums3[k]+nums4[l]==0示例1:输入:nums1=[1,2],nums2=[-2,-1],nums3=[-1,2],nums4......
  • 代码随想录刷题day13丨二叉树理论基础,递归遍历,迭代遍历,统一迭代,层序遍历
    代码随想录刷题day13丨二叉树理论基础,递归遍历,迭代遍历,统一迭代,层序遍历1.二叉树理论基础1.1二叉树种类满二叉树概述:如果一棵二叉树只有度为0的结点和度为2的结点,并且度为0的结点在同一层上,则这棵二叉树为满二叉树。这棵二叉树为满二叉树,也可以说深度为k,有2^k-1个节......
  • 「代码随想录算法训练营」第五十一天 | 图论 part9
    目录Bellman_ford算法模拟过程题目:94.城市间货物运输IBellman_ford队列优化算法(又名SPFA)模拟过程题目:94.城市间货物运输IBellman_ford算法之判断负权回路题目:95.城市间货物运输IIBellman_ford算法之单源有限最短路题目:96.城市间货物运输IIIBellman_ford算法Bellman_ford算法......
  • 代码随想录算法day5 - 哈希表1
    题目1242.有效的字母异位词给定两个字符串*s*和*t*,编写一个函数来判断*t*是否是*s*的字母异位词。字母异位词是通过重新排列不同单词或短语的字母而形成的单词或短语,通常只使用所有原始字母一次。示例1:输入:s="anagram",t="nagaram"输出:true示例2:......
  • 「代码随想录算法训练营」第五十天 | 图论 part8
    目录拓扑排序题目:117.软件构建dijkstra(朴素版)题目:47.参加科学大会dijkstra算法和prim算法的区别dijkstra(堆优化版)题目:47.参加科学大会拓扑排序拓扑排序概括来说就是给出一个有向无环图,把这个有向无环图转成线性的排序,就叫拓扑排序。使用广度优先搜索(BFS)即可。如上图,当我们......
  • 代码随想录算法训练营,8月31日 | 24. 两两交换链表中的节点,19.删除链表的倒数第N个节点
    24.两两交换链表中的节点题目链接:24.两两交换链表中的节点文档讲解︰代码随想录(programmercarl.com)视频讲解︰两两交换链表中的节点日期:2024-08-31做前思路:用上虚拟头指针,从头开始,先指向2再到1,再到3,但要注意保留原本的结点。Java代码如下:classSolution{publicListN......