当前位置: 首页 > news >正文

基础算法--搜索与图论(2)

文章目录

  • 最短路
    • 单源最短路
      • dijkstra算法(朴素)
      • dijkstra算法(堆优化)
      • 存在负权边
        • Bellman-Ford算法
        • SPFA
    • 多源汇求最短路
      • Flyod
  • 最小生成树
    • Prim(朴素版)
    • Krusal算法
  • 二分图
    • 染色法
    • 匈牙利算法

最短路

n 表示点数量 m:边数量

稠密图:m和n^2是一个级别的

稀疏图:m和n一个级别

  • **单源最短路:**一个点到其他点的最短距离

    • 所有边权重都是正数:朴素Dijkstra算法 n^2,堆优化版Dijkstra算法 mlogn

      所以朴素的适合稠密图

    • 存在负权边:Bellman-Ford算法 nm ,SPFA 算法 一般是m,最坏nm

  • **多源汇最短路:**起点和终点都是不确定的

    • Foyld算法: n^3

单源最短路

dijkstra算法(朴素)

  1. 初始化距离:设一号点 dis[1] = 0 即到起点的距离为0,其他都为正无穷,dis[ i ] = +∞
  2. 循环n次,每次循环
    找到不在s(保存当前已经确定最短路径的点的集合)中的,距离最近的点t(这个t的现在的距离一定是最短路,具体可以证明),再将t加入集合s中,
    用t更新其他点的最短距离 查看dis[x] > dis[t] + t到x边的权重是否成立,是,就将x距离更新

每次循环就能确定一个点的最短路

模板:

public class Main{static final int N = 510;static int[]dist = new int[N];//每个点到起点的最短路static boolean[]st = new boolean[N];//这个点是否已经确定最短路static int[][]g = new int[N][N];//邻接矩阵存储稠密图static int MAX = 0x3f3f3f3f;static int n;//有几个点static int m;//有几个边static int dijkstra() {Arrays.fill(dist,MAX);dist[1] = 0;//初始化距离for(int i = 0;i < n;i++) {//找到一个距离起点最近且没确定最短路的点tint t = -1;for(int j = 1;j <= n;j++) {if(!st[j] && (t == -1 || dist[t] > dist[j])) t = j;}//这个t此时与起点的距离就是该点的最短路st[t] = true;//用这个t更新其他点的距离for(int j = 1;j <= n;j++) {dist[j] = Math.min(dist[j],dist[t] + g[t][j]);//这里如果t和j无关联,那么g[t][j]就是MAX,dist[j]仍然是MAX}}//dist[n]是MAX说明n点与起点不存在路径if(dist[n] == MAX) return -1;return dist[n];}public static void main(String[]args) {Scanner sc = new Scanner(System.in);n = sc.nextInt();m = sc.nextInt();for(int i = 1;i <= n;i++) {for(int j = 1;j <= n;j++) {if(i == j) g[i][j] = 0;//自环边else g[i][j] = MAX;}}while(m --> 0 ) {int a = sc.nextInt();int b = sc.nextInt();int c = sc.nextInt();g[a][b] = Math.min(g[a][b],c);//a和b之间可能有多条路,取最短的一条}System.out.println(dijkstra());}
}

dijkstra算法(堆优化)

复杂度为mlogn

使用最小堆来优化找到一个距离起点最近且没确定最短路的点t过程

import java.util.*;public class Main{static final int N = 150010;static int[]e = new int[N];static int[]h = new int[N];//使用邻接表来存稀疏图static int[]w = new int[N];static int[]ne = new int[N];static int[]dist = new int[N];static int M = 0x3f3f3f3f;static int idx;static int n;static int m;static PriorityQueue<int[]> heap = new PriorityQueue<>((a,b) -> a[1] - b[1]);static boolean[]st = new boolean[N];static int dijkstra() {Arrays.fill(dist,M);dist[1] = 0;heap.offer(new int[]{1,0});/** 不优化的算法是采取暴力的方式,每次都把所有点遍历一遍,找st[j]=false且离起点最近的* 这里借助堆,每次将t更新完的其他点放入堆中,从堆中取出时,再用取出的点更新其他点,再次放入堆中*/while(heap.size() != 0) {int[]a = heap.poll();int t = a[0];int distan = a[1];st[t] = true;for(int i = h[t];i != -1;i = ne[i]) {int j = e[i];if(dist[j] > distan + w[i]) {dist[j] = distan + w[i];heap.offer(new int[]{j,dist[j]});}}}if(dist[n] == M) return -1;return dist[n];}static void add(int a,int b,int c) {e[idx] = b;ne[idx] = h[a];w[idx] = c;h[a] = idx++;}public static void main(String[]args) {Arrays.fill(h,-1);Scanner sc = new Scanner(System.in);n = sc.nextInt();m = sc.nextInt();while(m --> 0) {int a = sc.nextInt();int b = sc.nextInt();int c = sc.nextInt();add(a,b,c);}System.out.println(dijkstra());}
}

存在负权边

Bellman-Ford算法

有边数限制的话,只能用它

图中不能存在负权回路,否则在回路中一直转圈,最短路就变成负无穷即不存在最短路

循环n次(表示最多不经过n条边)将dist复制给c,下边的循环用c中的数据更新dist(防止连路)循环所有边(a,b,w)dis[b] = min(dist[b],copy[a] + w);//松弛操作

这样循环n次后,对于所有的点都满足三角不等式dist[b] <= dist[a] + w ,可以证明

直接用一个数组存放所有的边

public static int bellmanFord() {Arrays.fill(dist,M);dist[1] = 0;for(int i = 0;i < k;i++) {int[] c = Arrays.copyOf(dist, N);for(int j = 1;j <= m;j++) {int a = e[j].a;int b = e[j].b;int w = e[j].w;dist[b] = Math.min(dist[b],c[a] + w);}}if(dist[n] > M / 2) return M;return dist[n];
}
SPFA

实际上是对Bellman-ford算法的优化,主要思路是记录哪些点被更新了,再将和这些点有关系的点更新

迭代时用一个队列存放刚被更新的点(先将起点放入队列)

只要队列不空

​ 将对头t取出

​ 遍历更新一下t的所有出边b

​ 将b加入队列(b在队列中不存在的话)

public class Main{static final int N = 100010;static int n;static int m;static int[]h = new int[N];static int[]w = new int[N];static int[]ne = new int[N];static int[]e = new int[N];static boolean[]st = new boolean[N];static int idx;static int[]dist = new int[N];static int M = 0x3f3f3f3f;static void add(int x,int y,int z) {e[idx] = y;ne[idx] = h[x];w[idx] = z;h[x] = idx++;}public static void main(String[]args) {Scanner sc = new Scanner(System.in);n = sc.nextInt();m = sc.nextInt();Arrays.fill(h,-1);while(m --> 0) {int x = sc.nextInt();int y = sc.nextInt();int z = sc.nextInt();add(x,y,z);}int t = spfa();if(t == M) System.out.println("impossible");else System.out.println(t);}public static int spfa() {Arrays.fill(dist,M);dist[1] = 0;Queue<Integer> queue = new LinkedList<>();queue.offer(1);st[1] = true;while(!queue.isEmpty()) {int t = queue.poll();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]) {queue.offer(j);st[j] = true;}}}}if(dist[n] == M) return M;return dist[n];}}

多源汇求最短路

Flyod

初始时用邻接矩阵存储边d[i,j]

for(k = 1;k <= n;k++)for(i = 1;i <= n;i++)for(j = 1;j <= n;j++)d[i,j] = min(d[i,j],d[i,k]+d[k,j]);

模版:

import java.util.Scanner;public class Main{static final int N = 210;static int[][]d = new int[N][N];static int M = 0x3f3f3f3f;static int n;static int m;public static void main(String[]args) {Scanner sc = new Scanner(System.in);n = sc.nextInt();m = sc.nextInt();int k = sc.nextInt();for(int i = 1;i <= n;i++) {for(int j = 1;j <= n;j++) {if(i == j) d[i][j] = 0;else d[i][j] = M;}}while(m --> 0) {int x = sc.nextInt();int y = sc.nextInt();int z = sc.nextInt();d[x][y] = Math.min(d[x][y],z);}floyd();while(k --> 0) {int x = sc.nextInt();int y = sc.nextInt();if(d[x][y] > M / 2) System.out.println("impossible");else System.out.println(d[x][y]);}}static void floyd() {for(int k = 1;k <= n;k++) {for(int i = 1;i <= n;i++) {for(int j = 1;j <= n;j++) {d[i][j] = Math.min(d[i][j],d[i][k] + d[k][j]);}}}}
}

最小生成树

最小生成树问题一般对应无向图

一般稠密图用朴素版的Prim算法,稀疏图用Kruskal算法

  • Prim算法
    • 朴素版(稠密图)On^2
    • 堆优化版(稀疏图)Omlogn
  • Kruskal算法 Omlogm

Prim(朴素版)

集合:指当前已经在连通块中的点

到集合的距离:指到集合中的最近的点的距离

  1. 把所有距离初始化成正无穷
  2. n次迭代,每次迭代时,找到不在集合中的距离最近的点t,把t加到集合中(st[ t ] = true),用t来更新其他点到集合的距离
public class Main{static final int N = 510;static int[][]g = new int[N][N];static boolean[]st = new boolean[N];static int[]dist = new int[N];static int M = 0x3f3f3f3f;static int n;static int m;static int prim() {Arrays.fill(dist,M);int res = 0;//记录最小生成树的权重之和for(int i = 0;i < n;i++) {int t = -1;for(int j = 1;j <= n;j++) {if(!st[j] && (t == -1 || dist[j] < dist[t])) t = j;}st[t] = true;if(i != 0 && dist[t] == M) return M;//距离集合最近的点到集合的距离还是M,说明这个点和集合不连通,不存在最小生成树if(i != 0) res += dist[t];//这里将找到的一个结果加进结果集需要在根据t更新其他点到集合的距离之前,为了防止自环边的影响for(int j = 1;j <= n;j++) dist[j] = Math.min(dist[j],g[t][j]);}return res;}public static void main(String[]args) {Scanner sc = new Scanner(System.in);n = sc.nextInt();m = sc.nextInt();for(int i = 1;i <= n;i++) {for(int j = 1;j <= n;j++) {g[i][j] = M;}}while(m --> 0) {int x = sc.nextInt();int y = sc.nextInt();int z = sc.nextInt();g[x][y] = g[y][x] = Math.min(g[x][y],z);}int r = prim();if(r == M) System.out.println("impossible");else System.out.println(r);}
}

Krusal算法

  1. 将所有边按权重从小到大排序(该算法的一个瓶颈)
  2. 枚举每条边ab和权重c,如果a,b不连通(不在同一个集合中),将这条边加入集合 // 使用并查集
import java.util.*;
class Edge implements Comparable{int a;int b;int w;public Edge(int a,int b,int w) {this.a = a;this.b = b;this.w = w;}@Overridepublic int compareTo(Object o) {Edge e = (Edge)o;return this.w - e.w;}
}public class Main {static final int N = 100010;static int[]p = new int[N];static int find(int x) {if(x != p[x]) p[x] = find(p[x]);return p[x];}public static void main(String[]args) {Scanner sc = new Scanner(System.in);int n = sc.nextInt();for(int i = 1;i <= n;i++) p[i] = i;int m = sc.nextInt();Edge[]e = new Edge[m];for(int i = 0;i < m;i++) {int u = sc.nextInt();int v = sc.nextInt();int w = sc.nextInt();e[i] = new Edge(u,v,w);}Arrays.sort(e);//将所有边按权重从小到大排序int res = 0;int cnt = 0;for(int i = 0;i < m;i++) {//如果两点之间根本不连通(没有边),那么根本不会枚举不存在的边int a = e[i].a;int b = e[i].b;int w = e[i].w;a = find(a);b = find(b);if(a != b) {//若两者根父节点不同,则两点不在同一个集合中p[a] = b;//并查集合并集合res += w;cnt++;}}if(cnt < n - 1) System.out.println("impossible");else System.out.println(res);}
}

二分图

一个图是二分图当且仅当图中不含奇数环。

二分图:图中所有点都可以分成两部分,每一部分之间没有边相连。

  • 如何判别一个图是不是二分图:染色法 On+m
  • 匈牙利算法 Onm,实际运行时间一般远小于nm

染色法

保证一条边的两个端点属于不同的集合,总共两个集合(即两种颜色)

for(int i = 1;i <= n;i++) {if (i 未染色) dfs(i,k)  // 将i点颜色定为k,用深度优先遍历将i相连的点的颜色全部确定
}
import java.util.*;public class Main{static int n;static int m;static final int N = 100010;static final int M = 200010;static int[]h = new int[N];static int[]e = new int[M];static int[]ne = new int[M];static int idx;static int[]col = new int[N];static void add(int a,int b) {e[idx] = b;ne[idx] = h[a];h[a] = idx++;}static boolean dfs(int k,int color) {col[k] = color;for(int i = h[k];i != -1;i = ne[i]) {int j = e[i];if(col[j] == 0) {if(!dfs(j,3 - color)) return false;}else if(col[j] == color) return false;}return true;}public static void main(String[]args) {Scanner sc = new Scanner(System.in);n = sc.nextInt();m = sc.nextInt();Arrays.fill(h,-1);while(m --> 0) {int u = sc.nextInt();int v = sc.nextInt();add(u,v);add(v,u);}for(int i = 1;i <= n;i++) {if(col[i] == 0) {if(!dfs(i,1)) {System.out.println("No");return;}}}System.out.println("Yes");}
}

练习:acwing 关押罪犯

匈牙利算法

没有两条边共用一个点称为成功匹配

匈牙利算法可以返回成功匹配最大的情况

public class Main{static final int N = 1010;static final int M = 100010;static int[]h = new int[N];static int[]e = new int[M];static int[]ne = new int[M];static int idx;static int n1;static int n2;static int m;static boolean[]st = new boolean[N];static int[]match = new int[N];//每个索引代表一个女嘉宾,索引下的值代表对应男嘉宾static void add(int a,int b) {e[idx] = b;ne[idx] = h[a];h[a] = idx++;}public static void main(String[]args) {Scanner sc = new Scanner(System.in);n1 = sc.nextInt();n2 = sc.nextInt();m = sc.nextInt();Arrays.fill(h,-1);while(m --> 0) {int u = sc.nextInt();int v = sc.nextInt();add(u,v);}int res = 0;for(int i = 1;i <= n1;i++) {Arrays.fill(st,false);if(find(i)) res++;}System.out.println(res);}static boolean find(int x) {for(int i = h[x];i != -1;i = ne[i]) {int j = e[i];if(!st[j]) {st[j] = true;//这里将他设置为true是为了在下面劝拥有这个妹子的男生找其他的妹子时不要重复找了这个妹子if(match[j] == 0 || find(match[j])) {//妹子待字闺中 或 成功分手match[j] = x;//将这个妹子匹配给这个男生 牵手成功return true; }}}return false;}
}

相关文章:

  • 盘古信息IMS OS 数垒制造操作系统+ 产品及生态部正式营运
  • 黑马程序员-瑞吉外卖-day5
  • SpringBoot中从HikariCP迁移到Oracle UCP指南
  • STM32 PWM驱动设计
  • OJ_阶乘的和
  • 【重点问题】攻击面发现及管理
  • SpringBoot 整合RabbitMQ 之延迟队列实验
  • Jenkins上跑自动化项目,case出现错误时,导致项目运行时间过长,该如何处理?
  • diffusion 和 gan 的优缺点对比
  • Python系列(9)—— 比较运算符
  • 知识笔记(九十七)———什么是实体符???
  • 【算法专题】动态规划之回文子串问题
  • c#定义特性,通过反射获取特性
  • 基于SSM的网络办公系统(有报告)。Javaee项目。ssm项目。
  • 探索Gin框架:快速构建高性能的Golang Web应用
  • Angular 4.x 动态创建组件
  • Github访问慢解决办法
  • jdbc就是这么简单
  • Python 基础起步 (十) 什么叫函数?
  • RxJS: 简单入门
  • Spring-boot 启动时碰到的错误
  • SpringBoot 实战 (三) | 配置文件详解
  • 二维平面内的碰撞检测【一】
  • 猫头鹰的深夜翻译:JDK9 NotNullOrElse方法
  • 每天一个设计模式之命令模式
  • 爬虫模拟登陆 SegmentFault
  • 如何设计一个微型分布式架构?
  • 想写好前端,先练好内功
  • 硬币翻转问题,区间操作
  • 用Node EJS写一个爬虫脚本每天定时给心爱的她发一封暖心邮件
  • JavaScript 新语法详解:Class 的私有属性与私有方法 ...
  • ​2020 年大前端技术趋势解读
  • # 达梦数据库知识点
  • #鸿蒙生态创新中心#揭幕仪式在深圳湾科技生态园举行
  • $().each和$.each的区别
  • (转)mysql使用Navicat 导出和导入数据库
  • (轉貼) VS2005 快捷键 (初級) (.NET) (Visual Studio)
  • * CIL library *(* CIL module *) : error LNK2005: _DllMain@12 already defined in mfcs120u.lib(dllmodu
  • .NET 中让 Task 支持带超时的异步等待
  • .netcore 获取appsettings
  • .net企业级架构实战之7——Spring.net整合Asp.net mvc
  • .NET下ASPX编程的几个小问题
  • .Net下使用 Geb.Video.FFMPEG 操作视频文件
  • [ C++ ] 继承
  • [Android] 240204批量生成联系人,短信,通话记录的APK
  • [BZOJ1060][ZJOI2007]时态同步 树形dp
  • [C#]winform制作仪表盘好用的表盘控件和使用方法
  • [C/C++]数据结构 深入挖掘环形链表问题
  • [C++]模板与STL简介
  • [ccc3.0][数字钥匙] UWB配置和使用(二)
  • [FC][常见Mapper IRQ研究]
  • [HackMyVM]靶场 Wild
  • [HNOI2006]鬼谷子的钱袋
  • [LeetCode 127] - 单词梯(Word Ladder)
  • [LeetCode]—Implement strStr() 寻找子串匹配第一个位置 (KMP)