算法随笔:Floyd

Floyd算法是一种对所有点对最短路径算法、多源最短路径算法,以此计算能得到图中每一对节点之间的最短路径。Floyd不仅可以用来求多源最短路,也可以用于解决传递闭包问题。

算法思想:

Floyd求最短路径用的是“从小图到全图”的动态规划思想,定义状态dp[k][i][j],i、j、k都为节点编号,范围为1~n。状态dp[k][i][j]表示在包含1~k点的子图上,点对i、j的最短路径。当从子图1~k-1扩展成子图1~k时,状态转移方程为:

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

dp[k-1][i][j]是不考虑经过k点的旧的i到j的路径,dp[k-1][i][k]+dp[k-1][k][j]是考虑经过k点的新的i到j的路径,较小者就是新的dp[k][i][j]。

当k从1逐步扩展到n时,最后得到的dp[n][i][j]就是点对i、j之间的最短路径长度。若i、j是直连的,初值dp[0][i][j]就是边长,若不直连,初值为无穷大

在具体实现时,我们利用滚动数组技巧将dp[][][]缩成dp[][],因为dp[k][][]只和dp[k-1][][]有关,所以可以省略掉k这一维。由于k是动态规划子问题的“阶段”,即k是从点1开始逐步扩大到n的,所以k循环必须在i、j循环的外层

关键代码:

void floyd()
{for (int k = 0; k < n; k++)  // 不能将最外层的k循环放到内层,这会导致结果出错{for (int i = 0; i < n; i++){for (int j = 0; j < n; j++){if(d[i][k] != INF && d[k][j] != INF && d[i][k] + d[k][j] < d[i][j]){d[i][j] = d[i][k] + d[k][j];  // 找到更短的路径}}}}
}

或者

void floyd()
{for (int k = 0; k < n; k++)  // 不能将最外层的k循环放到内层,这会导致结果出错{for (int i = 0; i < n; i++){for (int j = 0; j < n; j++){d[i][j] = min(d[i][j], d[i][k]+d[k][j]);}}}
}

算法特征:

(1)能一次性求得所有节点之间的最短距离。

(2)效率不高,时间复杂度为O(n^3),n为节点数量。

(3)用邻接矩阵存储最合适,因为Floyd算法求的是所有点对之间的最短距离,本身就需要n*n的存储空间。

(4)可用于负权边,可以判断负环。因为Floyd基于动态规划思想而非贪心思想,所以即使图中存在负权边也可以保证从局部最优可以推导到全局最优。如果图中存在负环,那么每在这个负环上绕一圈,总长度就更小,从而使得能与这个负环连通的点对的最短距离都将成为负无穷大。而Floyd算法很容易判断负环,只要在算法运行过程中出现任意dp[i][i]<0,就说明有负环。因为dp[i][i]是从i出发,经过其他中转点绕一圈回到自己的最短路径,如果小于0,即存在负环。

算法的常见应用场景以及例题:

(1)图的规模n<300。时间复杂度O(n^3)限制了图的规模。

(2)可能多次查询不同点对之间的最短路径。

        例题:hdu 1385

        题意:非常简单的模板题,该题需要输出具体的路径点。

        思路:求最短路就是最简单的Floyd模板,我们讨论打印具体路径的方法,用path[][]记录路径,path[i][j]=u表示起点为i,终点为j的最短路径,从i出发下一个点是u。一个完整的路径是从s出发,查询path[s][j]=u找到下一个点为u,然后从u出发,查询path[u][j]=v,下一个点是v,重复这个操作,直到最后到达终点j。路径的具体计算见代码。

        代码:

#include <iostream>
#include <cstdio>
#include <cstring>
#include <cstring>
using namespace std;const int maxn=5000;
const int INF=100000000;int n;
int node[maxn];
int dist[maxn][maxn];
int path[maxn][maxn];void floyd()
{for(int i=1;i<=n;i++)//初始化 有一种后驱的感觉for(int j=1;j<=n;j++)path[i][j]=j;for(int k=1;k<=n;k++)for(int i=1;i<=n;i++)for(int j=1;j<=n;j++){int temp=dist[i][k]+dist[k][j]+node[k];if(dist[i][j]>temp){dist[i][j]=temp;path[i][j]=path[i][k];}if(dist[i][j]==temp){if(path[i][j]>path[i][k])path[i][j]=path[i][k];}}
}int main()
{int a,be,en;while(scanf("%d",&n)&&n){for(int i=1;i<=n;i++)for(int j=1;j<=n;j++){scanf("%d",&a);if(a!=-1) dist[i][j]=a;else dist[i][j]=INF;}for(int i=1;i<=n;i++) scanf("%d",&node[i]);floyd();int kcase=0;while(1){if(kcase!=0) printf("\n");kcase++;scanf("%d%d",&be,&en);if(be==-1&&en==-1) break;printf("From %d to %d :\n",be,en);printf("Path: ");printf("%d",be);int temp=be;while(temp!=en){printf("-->%d",path[temp][en]);temp=path[temp][en];}printf("\n");printf("Total cost : %d\n",dist[be][en]);}}return 0;
}

(3)问题的解决和中转点有关。这是Floyd算法的核心思想,算法用DP方法遍历中转点计算最短路径。

        例题:洛谷P1119 灾后重建

        个人题解,戳这

(4)路径在“兜圈子”,一个点可能多次经过。这是Floyd算法的特点,其他路径算法都不行

        例题:洛谷P1613 跑路

        个人题解,戳这

(5)解决传递闭包问题。传递闭包是离散数学的概念,给定一个集合,以及若干对元素之间的传递关系,传递闭包问题是求所有元素之间的传递(连通)关系。例如,包括3个元素的集合{a,b,c},给定传递关系,a->b,b->c,那么可以推导出a->c。在图论中可以把传递闭包问题转换为:给定一个有向图,其中有n个点和m条边,求所有点对之间的连通性关系。传递闭包是“多源”路径问题。

        例题1:hdu1704 Rank

        题意:有m场比赛,每场比赛由两人决胜负。已知一些比赛的成绩,现在要查询任意两人之间的胜负情况,要求输出有多少个查询的胜负是不能被确定的。在本题中我们认为胜负关系具有传递性,即若A赢了B,B赢了C,那么得出A赢了C。n,m >= 500。

        思路:把参赛人员和胜负关系建模为一个有向图,有向边A->B表示A赢了B。然后使用Floyd算法求解传递闭包矩阵,矩阵中等于1代表能确定胜负,为0代表不能确定。统计Floyd后,矩阵中为0的数量即可。本题n=500,比较小,简单优化即可通过。如果n=1000,则需要使用bitset优化,减少一层循环。bitset是一种类似数组的数据结构,它的每个元素用1b存储,只能是0或者1。使用了bitset后,Floyd算法在传递闭包的特殊情况下时间复杂度可以优化到接近O(n^2)。

        代码:

#include <bits/stdc++.h>
using namespace std;
#define endl '\n'
typedef long long ll;
typedef unsigned long long ull;
const int maxn = 1e3 + 10;
const int INF = 0x3fffffff;
const int mod = 1000000007;
int dis[maxn][maxn];
int n, m;void floyd() {for (int k = 1; k <= n; k++) {for (int i = 1; i <= n; i++) {if (!dis[i][k])continue;for (int j = 1; j <= n; j++) {if (!dis[k][j])continue;dis[i][j] = 1;}}}
}void solve() {cin >> n >> m;for (int i = 1; i <= n; i++) {      // 初始化for (int j = 1; j <= n; j++) {dis[i][j] = (i == j);}}for (int i = 0; i < m; i++) {int u, v;cin >> u >> v;dis[u][v] = 1;}floyd();int ans = 0;for (int i = 1; i <= n; i++) {for (int j = i + 1; j <= n; j++) {if (dis[i][j] == 0 && dis[j][i] == 0) {ans++;}}}cout << ans << endl;
}int main() {ios::sync_with_stdio(false);cin.tie(0);cout.tie(0);cout << fixed;cout.precision(18);int t;cin >> t;while (t--)solve();return 0;
}

 下面也给出bitset优化的代码(bitset的作用是省去了最后一层j的循环,改用位运算或的方式对整个bitset数组的每一位一次性计算):

#include <bits/stdc++.h>
using namespace std;
#define endl '\n'
typedef long long ll;
typedef unsigned long long ull;
const int maxn = 1e3 + 10;
const int INF = 0x3fffffff;
const int mod = 1000000007;
bitset<maxn> dis[maxn];
int n, m;void floyd() {for (int k = 1; k <= n; k++) {for (int i = 1; i <= n; i++) {if (!dis[i][k])continue;dis[i] |= dis[k];}}
}void solve() {cin >> n >> m;for (int i = 1; i <= n; i++) {      // 初始化for (int j = 1; j <= n; j++) {dis[i][j] = (i == j);}}for (int i = 0; i < m; i++) {int u, v;cin >> u >> v;dis[u][v] = 1;}floyd();int ans = 0;for (int i = 1; i <= n; i++) {for (int j = i + 1; j <= n; j++) {if (dis[i][j] == 0 && dis[j][i] == 0) {ans++;}}}cout << ans << endl;
}int main() {ios::sync_with_stdio(false);cin.tie(0);cout.tie(0);cout << fixed;cout.precision(18);int t;cin >> t;while (t--)solve();return 0;
}

        例题2:POJ1127

        题意:给出n条线段,线段相交即连通,判断任意两条线段是否连通

        思路:首先用计算几何的算法求出线段相交,将线段之间的相交构建成二维矩阵,就可以转换成传递闭包问题了,用Floyd求出所有线段之间的连通性。

        代码:

#include <cstdio>
#include <iostream>
#include <cmath>
using namespace std;
const int maxn = 20;
const int maxm = 1e4 + 10;
double EPS = 1e-10;// 考虑误差的加法运算
double add(double a, double b)
{if(abs(a + b) < EPS * (abs(a) + abs(b)))return 0;return a + b;
}// 二维向量结构体
struct P
{double x, y;P() {}P(double x, double y){this->x = x;this->y = y;}void input(){cin >> x >> y;}P operator + (P &p){return P(add(x, p.x), add(y, p.y));}P operator - (P &p){return P(add(x, -p.x), add(y, -p.y));}P operator * (double d){return P(x * d, y * d);}double dot(P p)  // 内积{return add(x * p.x, y * p.y);}double det(P p)  // 外积{return add(x * p.y, -y * p.x);}
};// 判断两条直线是否平行
bool isParallel(P p1, P p2, P q1, P q2)
{return (p1 - p2).det(q1 - q2) == 0;
}// 判断点q是否在线段p1p2上
bool on_seg(P p1, P p2, P q)
{return (p1 - q).det(p2 - q) == 0 && (p1 - q).dot(p2 - q) <= 0;
}// 计算直线p1p2与直线q1q2的交点
P intersection(P p1, P p2, P q1, P q2)
{// return p1 + (p2 - p1) * ((q2 - q1).det(q1 - p1) / (q2 - q1).det(p2 - p1));double t = ((q2 - q1).det(q1 - p1) / (q2 - q1).det(p2 - p1));P x = ((p2 - p1) * t);return p1 + x;
}int n, m;
P p[maxn], q[maxn];  // 一个线段的起点和终点
int A[maxm], B[maxm];  // 题目要询问哪些木棍的连接情况
bool G[maxn][maxn];  // 线段之间的相连关系void floyd()
{for (int k = 1; k <= n; k++){for (int i = 1; i <= n; i++){for (int j = 1; j <= n; j++){G[i][j] |= (G[i][k] && G[k][j]);}}}
}void solve()
{// 先判断所有的线段两两之间是否有直接的公共点for (int i = 1; i <= n; i++){G[i][i] = true;for (int j = 1; j < i; j++){if(i == j) continue;// 判断线段i和线段j是否有公共点if(isParallel(p[i], q[i], p[j], q[j])){  // 平行时,我们检查任一端点是否在另一条线段上,来判断两条线段是否有公共点G[i][j] = G[j][i] = on_seg(p[i], q[i], p[j]) || on_seg(p[i], q[i], q[j]) || on_seg(p[j], q[j], p[i]) || on_seg(p[j], q[j], q[i]);}else{P cross = intersection(p[i], q[i], p[j], q[j]);G[i][j] = G[j][i] = on_seg(p[i], q[i], cross) && on_seg(p[j], q[j], cross);}}}// 通过floyd算法判断任意两条线段之间是否连通(传递闭包问题)floyd();// 输出结果for (int i = 1; i <= m; i++){G[A[i]][B[i]] ? cout << "CONNECTED\n" : cout << "NOT CONNECTED\n";}
}int main()
{ios::sync_with_stdio(false);cin.tie(0);cout.tie(0);cin >> n;while(n != 0){for (int i = 1; i <= n; i++){p[i].input();q[i].input();// cout << p[i].x << " " << p[i].y << " " << q[i].x << " " << q[i].y << endl;}for (int i = 1; ; i++){int a, b;cin >> a >> b;if(a == 0 && b == 0)break;m = i;A[i] = a;B[i] = b;// cout << A[i] << " " << B[i] << endl;}solve();cin >> n;}return 0;
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/50922.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【1-3章】Spark编程基础(Python版)

课程资源&#xff1a;&#xff08;林子雨&#xff09;Spark编程基础(Python版)_哔哩哔哩_bilibili 第1章 大数据技术概述&#xff08;8节&#xff09; 第三次信息化浪潮&#xff1a;以物联网、云计算、大数据为标志 &#xff08;一&#xff09;大数据 大数据时代到来的原因…

Flowable 7.0.0.M2 版本功能

CMMN 支持批量迁移重复支持案例重新激活支持停止内务处理批处理Http 任务支持 HTTP HEAD and OPTIONS for the Http Tasks移除了 Spring Boot 启动器 flowable-spring-boot-starter-basic - 切换为 flowable-spring-boot-starter-process flowable-spring-boot-starter-rest-ap…

Unity之 Vector3 的详细介绍以及方法的介绍

文章目录 总的介绍小试牛刀相关的描述的参数看个小例子 总的介绍 当涉及到Unity中的Vector3类时&#xff0c;以下是一些常用的方法和操作&#xff1a; magnitude 方法&#xff1a;返回向量的长度。 float length vector.magnitude;sqrMagnitude 方法&#xff1a;返回向量的平…

恒运资本:信创概念再度活跃,华是科技再创新高,南天信息等涨停

信创概念21日盘中再度活跃&#xff0c;截至发稿&#xff0c;华是科技涨超17%&#xff0c;盘中一度触及涨停再创新高&#xff0c;中亦科技涨超13%亦创出新高&#xff0c;久其软件、南天信息、新炬网络、英飞拓均涨停。 音讯面上&#xff0c;自8月3日以来&#xff0c;财政部官网连…

Dockerfile制作镜像与搭建LAMP环境

1、编写Dockerfile制作Web应用系统nginx镜像&#xff0c;生成镜像nginx:v1.1&#xff0c;并推送其到私有仓库。 具体要求如下&#xff1a; &#xff08;1&#xff09;基于centos基础镜像&#xff1b; &#xff08;2&#xff09;指定作者信息&#xff1b; &#xff08;3&#x…

ChatGPT在智能旅行和行程规划中的应用如何?

ChatGPT在智能旅行和行程规划领域具有广泛的应用潜力。作为一种强大的自然语言处理模型&#xff0c;ChatGPT可以通过与用户进行实时的语音或文本交互&#xff0c;提供个性化的旅行建议、行程规划、旅游信息等服务。本文将详细探讨ChatGPT在智能旅行和行程规划中的应用&#xff…

Window Server 与 Windows 系统开关机日志查看方法

目录 Windows/Windows Server 查看日志Windows 系统常用的事件 ID 环境&#xff1a;Windows Server 2019 &#xff08;也适用于 Windows 其他系统&#xff09;。 不同版本的 Windows 图标可能有所不同&#xff0c;但是服务器级 Windows Server 与普通桌面级 Windows 还会有些操…

CTFshow——web入门——反序列化web254-web278 详细Writeup

前言 在做题之前先简要总结一下知识点 private变量会被序列化为&#xff1a;\x00类名\x00变量名 protected变量会被序列化为: \x00\*\x00变量名 public变量会被序列化为&#xff1a;变量名__sleep() &#xff1a;//在对象被序列化之前运行__wakeup() //将在反序列化之后立即…

Redis的数据持久化(概念版)

前言 本文主要介绍Redis的三种持久化方式、AOF持久化策略等 什么是持久化 持久化是指将数据在内存中的状态保存到非易失性介质&#xff08;如硬盘、固态硬盘等&#xff09;上的过程。在计算机中&#xff0c;内存中的数据属于易失性数据&#xff0c;一旦断电或重启系统&#…

vue uniapp 防止按钮多次点击(类似于防抖节流)

common文件并创建anti-shake.js文件 // 防止处理多次点击 function noMultipleClicks(methods, info) {// methods是需要点击后需要执行的函数&#xff0c; info是点击需要传的参数let that this;if (that.noClick) {// 第一次点击that.noClick false;if(info && inf…

Spring6.0官方文档示例:(27)配置文件中bean定义的继承关系

一、实体类 package cn.edu.tju.domain;public class DerivedTestBean {private String name;private int age;public String getName() {return name;}public void setName(String name) {this.name name;}public int getAge() {return age;}public void setAge(int age) {t…

洗涤护理门店小程序DIY制作教程

随着移动互联网的快速发展&#xff0c;小程序成为了各行各业推广和服务的新平台。对于干洗店来说&#xff0c;拥有一个专属的洗护小程序不仅可以提升用户体验&#xff0c;还能增加店铺的曝光度和销售额。那么&#xff0c;如何DIY制作一个干洗店洗护小程序呢&#xff1f; 首先&a…

SpringBoot利用ConstraintValidator实现自定义注解校验

一、前言 ConstraintValidator是Java Bean Validation&#xff08;JSR-303&#xff09;规范中的一个接口&#xff0c;用于实现自定义校验注解的校验逻辑。ConstraintValidator定义了两个泛型参数&#xff0c;分别是注解类型和被校验的值类型。在实现ConstraintValidator接口时&…

Android中正确使用Handler的姿势

在Android中&#xff0c;Handler是一种用于在不同线程之间传递消息和任务的机制。以下是在Android中正确使用Handler的一些姿势&#xff1a; 1. 在主线程中创建Handler对象 在Android中&#xff0c;只有主线程&#xff08;也称为UI线程&#xff09;可以更新UI。因此&#xff…

控制Unity发布的PC包的窗体

大家好&#xff0c;我是阿赵。   用Unity发布PC包接入某些渠道时&#xff0c;有时候会收到一些特殊的需求&#xff0c;比如控制窗口最大化(比如某些情况强制显示窗体)、最小化(比如老板键)、强制规定窗体置顶等。虽然我一直认为这些需求都是流氓软件行为&#xff0c;但作为一…

【微信小程序】小程序之间的跳转方式总结

想要从该小程序跳转到其他小程序怎么做&#xff1f; 方式 小程序之间的跳转方法有&#xff1a; wx.navigateTo&#xff1a;保留当前页面&#xff0c;跳转到应用内的某个页面&#xff0c;然后从该页面返回上一页的时候使用wx.navigateBack返回。wx.switchTab&#xff1a;跳转…

基于Spark+django的国漫推荐系统--计算机毕业设计项目

近年来&#xff0c;随着互联网的蓬勃发展&#xff0c;企事业单位对信息的管理提出了更高的要求。以传统的管理方式已无法满足现代人们的需求。为了迎合时代需求&#xff0c;优化管理效率&#xff0c;各种各样的管理系统应运而生&#xff0c;随着各行业的不断发展&#xff0c;基…

【Java架构-包管理工具】-Maven基础(一)

本文摘要 Maven作为Java后端使用频率非常高的一款依赖管理工具&#xff0c;在此咱们由浅入深&#xff0c;分三篇文章&#xff08;Maven基础、Maven进阶、私服搭建&#xff09;来深入学习Maven&#xff0c;此篇为开篇主要介绍Maven概念、模型、安装配置、基本命令 文章目录 本文…

动态规划入门之洛谷创始人kk偷懒摆烂

P2392 kkksc03考前临时抱佛脚 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn) 这个题目的难点在于我们该怎么尽量让左右大脑均分题目时间呢&#xff0c;我们这样想&#xff0c;我们先将所有题目的时间总和累加起来&#xff0c;取其一半。我们知道如果将这n道题目进行时间的尽量…

DP读书:鲲鹏处理器 架构与编程(十)鲲鹏软件生态与云服务

十秒带你了解鲲鹏软件生态与云服务 鲲鹏软件生态与云服务ARM授权机制在传统的PC领域&#xff0c;半导体厂商的业务类型主要分为两种&#xff1a;在移动领域&#xff0c; ARM服务器生态鲲鹏服务器软件生态1. 鲲鹏计算产业2. 鲲鹏软件生态兼容性3. openEluer操作系统4. 鲲鹏软件栈…