图_基础算法

news2024/12/22 23:29:07

图这种数据结构还有一些比较特殊的算法,比如二分图判断,有环图无环图的判断,拓扑排序,以及最经典的最小生成树,单源最短路径问题,更难的就是类似网络流这样的问题。

先看拓扑排序(有环无环):la总微信文章的链接:https://mp.weixin.qq.com/s?__biz=MzAxODQxMDM0Mw==&mid=2247491897&idx=1&sn=c2d77dd649548d077815af3c976b61d1&scene=21#wechat_redirect
然后看二分图
然后看并查集
然后最小生成树dijkstra 单源最短路径

基本概念:

  1. 大部分都是以邻接表的形式存储:
// 记得每一个都要初始化一下为new ArrayList<>()或者LinkedList;
List<Integer>[] graph;

拓扑排序

  1. 拓扑排序的对象,就是有向无环图(DAG)。一个有向无环图的拓扑排序结果 不止一种。

给定一个包含 n个节点的有向图 G,我们给出它的节点编号的一种排列,如果满足:

对于图 G 中的任意一条有向边 (u,v),u 在排列中都出现在 v的前面。

那么称该排列是图 G 的「拓扑排序」

在这里插入图片描述

  1. 先说一下怎么判断图有没有环(力扣207 课程表)。
    BFS很简单,直接把所有入度为0的入队列遍历一遍,adj度数减1,要是入度为0就继续入队列,最后还有度数不为0的节点(也可以每次遍历queue计数,最后判断计数结果等不等于n),就说明有环。
// 207题 BFS实现
class Solution {
    public boolean canFinish(int numCourses, int[][] prerequisites) {
        int[] indegrees = new int[numCourses];
        List<List<Integer>> adjacency = new ArrayList<>();
        Queue<Integer> queue = new LinkedList<>();
        // 初始化图
        for(int i = 0; i < numCourses; i++)
            adjacency.add(new ArrayList<>());
        // 注意cp[0]前置为cp[1],所以先上cp[1]才能继续走到cp[0],即 箭头指向为1->0
        for(int[] cp : prerequisites) {
            indegrees[cp[0]]++;
            adjacency.get(cp[1]).add(cp[0]);
        }
        // 把所有入度为0的加进来
        for(int i = 0; i < numCourses; i++)
            if(indegrees[i] == 0) queue.add(i);
        // 开始bfs
        while(!queue.isEmpty()) {
            int pre = queue.poll();
            numCourses--;
            for(int cur : adjacency.get(pre))
            // 别忘了减入度
                if(--indegrees[cur] == 0) queue.add(cur);
        }
        // 根据n是否减为0判断是否全部节点都被遍历了一遍,如果有环就说明n不为0
        return numCourses == 0;
    }
}
dfs判断的方式更简单了,在开始进入遍历cur的adj之前 标记onPath为true,遍历完adj之后把onPath恢复为false(恢复现场)。下一次递归开始时发现onPath已经为true就说明有环,类似贪吃蛇咬到了自己。
onPath数组和visited数组可以合为一个数组,用int标识不同的情况。例如初始化flag数组都是0,然后进入递归置为1,结束递归置为-1.这样,每次进入一个节点的时候就判断如果flag==-1就返回;flag为1就说明有环。
// DFS判断是否有环
boolean[] onPath;

boolean hasCycle = false;
boolean[] visited;

void traverse(List<Integer>[] graph, int curIndex) {
    if (onPath[curIndex]) {
        // 发现环!!!
        hasCycle = true;
    }
    if (visited[curIndex]) {
        return;
    }
    // 将节点 s 标记为已遍历
    visited[curIndex] = true;
    // 开始遍历节点 s
    onPath[curIndex] = true;
    for (int adj : graph[curIndex]) {
        traverse(graph, adj);
    }
    // 节点 s 遍历完成
    onPath[curIndex] = false;
}

注意由于可能 一次traverse并不能遍历完所有的节点,所以要遍历nums,从0-n都当成curIndex传入。

// 207课程表 dfs实现判断是否有环,以flag为标识 return true或者false
class Solution {
    public boolean canFinish(int numCourses, int[][] prerequisites) {
        List<List<Integer>> adjacency = new ArrayList<>();
        for(int i = 0; i < numCourses; i++)
            adjacency.add(new ArrayList<>());
        int[] flags = new int[numCourses];
        for(int[] cp : prerequisites)
            adjacency.get(cp[1]).add(cp[0]);
        for(int i = 0; i < numCourses; i++)
            if(!dfs(adjacency, flags, i)) return false;
        return true;
    }
    private boolean dfs(List<List<Integer>> adjacency, int[] flags, int i) {
        if(flags[i] == 1) return false;
        if(flags[i] == -1) return true;
        flags[i] = 1;
        for(Integer j : adjacency.get(i))
            if(!dfs(adjacency, flags, j)) return false;
        flags[i] = -1;
        return true;
    }
}

然后借机说一下递归怎么进行拓扑排序,以力扣的210课程表II 为例,由于有依赖的前置课程,所以我们要先完成依赖的课程,也就是树的根节点。再代入后序遍历的思路,拓扑排序的结果其实就是这个多叉树后序遍历的数组反转之后的结果。
当然,如果要实现拓扑排序,前提一定是要先判断是否有环的。可以在遍历的时候判断,也可以直接把207的代码copy过来

boolean[] visited;
// 记录后序遍历结果
List<Integer> postorder = new ArrayList<>();

int[] findOrder(int numCourses, int[][] prerequisites) {
    // 先保证图中无环
    if (!canFinish(numCourses, prerequisites)) {
        return new int[]{};
    }
    // 建图
    List<Integer>[] graph = buildGraph(numCourses, prerequisites);
    // 进行 DFS 遍历
    visited = new boolean[numCourses];
    for (int i = 0; i < numCourses; i++) {
        traverse(graph, i);
    }
    // 将后序遍历结果反转,转化成 int[] 类型
    Collections.reverse(postorder);
    int[] res = new int[numCourses];
    for (int i = 0; i < numCourses; i++) {
        res[i] = postorder.get(i);
    }
    return res;
}

void traverse(List<Integer>[] graph, int s) {
    if (visited[s]) {
        return;
    }

    visited[s] = true;
    for (int t : graph[s]) {
        traverse(graph, t);
    }
    // 后序遍历位置
    postorder.add(s);
}

为什么310的最小高度树的解法(把入度为1也就是叶子结点入队列 然后每次去掉一圈叶子结点之后就是根节点了)是拓扑排序,可能是因为满足拓扑排序的性质:后序遍历。

并查集 Union-Find

并查集比较简单,也可以直接用并查集判断是否有环,这里直接附上并查集的代码

class UF {
            private int count;
            private int parent[];

            public UF(int n) {
                parent = new int[n+1];
                // 初始化的时候 全都是独立的根节点 指向自己
                for (int i = 0; i <= n; i++) {
                    parent[i] = i;
                }
                // 计数count
                count = n;
            }

            public int getUFCount() {
                return count;
            }

            public int findRoot(int x) {
                // 注意回溯的话要用if。循环的话要用while
               if (parent[x] != x) {
                    parent[x] = findRoot(parent[x]);
                }
                return parent[x];
                /*while (parent[x] != x) {
                    // 进行路径压缩
                    parent[x] = parent[parent[x]];
                    x = parent[x];
                }
                return x;*/
            }

            public void union(int a, int b) {
                int rootA = findRoot(a);
                int rootB = findRoot(b);
                if (rootA == rootB) {
                    return;
                }
                parent[rootA] = rootB;

                // 别忘了count--
                count--;
            }

            public boolean connected(int a, int b) {
                return findRoot(a) == findRoot(b);
            }
        }

并查集相关题目:
785 判断二分图
1319 连通网络的操作次数 这题用UF做可以;用DFS类似于判断拓扑排序是否有环也可以,具体看一下题解。
886 可能的二分法

在这里插入图片描述

二分图

接着说一下二分图

定义:如果能将一个图的节点集合分割成两个独立的子集 A 和 B ,并使图中的每一条边的两个节点一个来自 A 集合,一个来自 B 集合,就将这个图称为 二分图 。

二分图也可以用并查集UF来解决,把所有cur的adj都union,遍历某个adj的时候如果发现adj和cur已经相连了connected了,就说明不可分为两个独立子集。
例题:785 判断二分图
还有一种就是染色法,遍历adj如果未染色就染成和cur不一致的颜色,如果发现adj颜色不是初始状态且和cur颜色一致,就说明不可二分。

染色可以初始化RED/BLUE之类的,也可以直接用int标识。例如0;1;-1

/**
         * BFS广度优先遍历-染色法
         *
         * @param graph
         * @return
         */
        private boolean useBfs(int[][] graph) {
            int n = graph.length;
            Deque<Integer> queue = new ArrayDeque<>(n);

            // 用visit数组表示染色,visited[i]为0表示还未被染色,初次染色为1,其邻接点染色时被赋值为-1
            int visited[] = new int[n];
            // 每个节点未被染色前都要进队列
            for (int i = 0; i < n; i++) {
                if (visited[i] != 0) {
                    continue;
                }
                visited[i] = 1;
                queue.addLast(i);
                while (!queue.isEmpty()) {
                    int item = queue.removeFirst();
                    for (int adj : graph[item]) {
                        // 未被染色 就处理为-visited[item];
                        if (visited[adj] == 0) {
                            visited[adj] = -visited[item];
                            queue.addLast(adj);
                        }
                        // 已被染色且和当前颜色相等 就返回false
                        else if (visited[adj] == visited[item]) {
                            return false;
                        }
                    }
                }
            }
            return true;
        }

最小生成树

Kruskal 算法

一开始的时候就把所有的边排序,然后从权重最小的边开始挑选属于最小生成树的边,组建最小生成树。

prim算法

原理:对于任意一个节点,切分他的连接点之后,横切边上权重最小的边,一定是构成最小生成树的一条边。
实现:用优先级队列结合BFS动态获取权重最小边
为了防止重复切,需要用一个变量判断是否已经被加入过结果集(最小生成树)中了。
在这里插入图片描述

class Prim {
    // 核心数据结构,存储「横切边」的优先级队列
    private PriorityQueue<int[]> pq;
    // 类似 visited 数组的作用,记录哪些节点已经成为最小生成树的一部分
    private boolean[] inMST;
    // 记录最小生成树的权重和
    private int weightSum = 0;
    // graph 是用邻接表表示的一幅图,
    // graph[s] 记录节点 s 所有相邻的边,
    // 三元组 int[]{from, to, weight} 表示一条边
    private List<int[]>[] graph;

    public Prim(List<int[]>[] graph) {
        this.graph = graph;
        this.pq = new PriorityQueue<>((a, b) -> {
            // 按照边的权重从小到大排序
            return a[2] - b[2];
        });
        // 图中有 n 个节点
        int n = graph.length;
        this.inMST = new boolean[n];

        // 随便从一个点开始切分都可以,我们不妨从节点 0 开始
        inMST[0] = true;
        cut(0);
        // 不断进行切分,向最小生成树中添加边
        while (!pq.isEmpty()) {
            int[] edge = pq.poll();
            int to = edge[1];
            int weight = edge[2];
            if (inMST[to]) {
                // 节点 to 已经在最小生成树中,跳过
                // 否则这条边会产生环
                continue;
            }
            // 将边 edge 加入最小生成树
            weightSum += weight;
            inMST[to] = true;
            // 节点 to 加入后,进行新一轮切分,会产生更多横切边
            cut(to);
        }
    }

    // 将 s 的横切边加入优先队列
    private void cut(int s) {
        // 遍历 s 的邻边
        for (int[] edge : graph[s]) {
            int to = edge[1];
            if (inMST[to]) {
                // 相邻接点 to 已经在最小生成树中,跳过
                // 否则这条边会产生环
                continue;
            }
            // 加入横切边队列
            pq.offer(edge);
        }
    }

    // 最小生成树的权重和
    public int weightSum() {
        return weightSum;
    }

    // 判断最小生成树是否包含图中的所有节点
    public boolean allConnected() {
        for (int i = 0; i < inMST.length; i++) {
            if (!inMST[i]) {
                return false;
            }
        }
        return true;
    }
}

dijkstra最短路径

说到了这里,狄杰斯特拉算法其实非常简单,就是一个BFS算法的进阶使用,先用一个对象State保存当前节点ID、距离start节点的距离distFromStart。每次用优先级队列,按照distFromStart从小到大排序。然后遍历队列中cur的adj,把最小路径加入结果集中即可。

// 返回节点 from 到节点 to 之间的边的权重
int weight(int from, int to);

// 输入节点 s 返回 s 的相邻节点
List<Integer> adj(int s);

// 输入一幅图和一个起点 start,计算 start 到其他节点的最短距离
int[] dijkstra(int start, List<Integer>[] graph) {
    // 图中节点的个数
    int V = graph.length;
    // 记录最短路径的权重,你可以理解为 dp table
    // 定义:distTo[i] 的值就是节点 start 到达节点 i 的最短路径权重
    int[] distTo = new int[V];
    // 求最小值,所以 dp table 初始化为正无穷
    Arrays.fill(distTo, Integer.MAX_VALUE);
    // base case,start 到 start 的最短距离就是 0
    distTo[start] = 0;

    // 优先级队列,distFromStart 较小的排在前面
    Queue<State> pq = new PriorityQueue<>((a, b) -> {
        return a.distFromStart - b.distFromStart;
    });

    // 从起点 start 开始进行 BFS
    pq.offer(new State(start, 0));

    while (!pq.isEmpty()) {
        State curState = pq.poll();
        int curNodeID = curState.id;
        int curDistFromStart = curState.distFromStart;

        if (curDistFromStart > distTo[curNodeID]) {
            // 已经有一条更短的路径到达 curNode 节点了
            continue;
        }
        // 将 curNode 的相邻节点装入队列
        for (int nextNodeID : adj(curNodeID)) {
            // 看看从 curNode 达到 nextNode 的距离是否会更短
            int distToNextNode = distTo[curNodeID] + weight(curNodeID, nextNodeID);
            if (distTo[nextNodeID] > distToNextNode) {
                // 更新 dp table
                distTo[nextNodeID] = distToNextNode;
                // 将这个节点以及距离放入队列
                pq.offer(new State(nextNodeID, distToNextNode));
            }
        }
    }
    return distTo;
}

这里有一个优化点,不用visited数组判断是否会走回头路,因为每个adj都先判断加上cur-adj的权重之后是否小于之前已加入结果集的最小路径,小的话才会更新结果集并加入队列。
因为两个节点之间的最短距离(路径权重)肯定是一个确定的值,不可能无限减小下去,所以队列一定会空,不会无限循环。队列空了之后,distTo数组中记录的就是从start到其他节点的最短距离。

上述代码是为了找到从start到所有节点的最小路径(结果集为distTo[]),如果指定了到end节点,在while循环中判断curNodeId==end即可结束while循环,return curDistFromStart(因为每次从优先级队列中拿出来的一定是最小的路径权重)。

相关题目:
743 题「网络延迟时间
第 1514 题「概率最大的路径」

看一下1631 最小体力消耗路径 应该和并查集、二分、dijkstra最短路径都有关系

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

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

相关文章

[DDR5 Jedec 3]DDR5 SDRAM 状态图 和 基本功能

3000字, 依公知及经验整理,原创保护,禁止转载。 专栏 《深入理解DDR》 3.1 简化状态图 来源: Jedec Proposed DDR5 Full spec 缩写功能ACT激活PRE预充电PRE_A全部预充电MRS寄存器集模式REF刷新TEN边界扫描模式ReadRD, RDS4, RDS8Read ARDA, RDS4A, RDS8AwriteWR, WRS4…

配置docker阿里云镜像地址

一、安装docker的步骤&#xff1a; 1.yum install -y yum-utils 2.yum-config-manager --add-repo http://mirrors.aliyun.com/docker- ce/linux/centos/docker-ce.repo --配置阿里云仓库3.yum makecache fast4.yum install docker-ce -y5.docker version …

动态代理,反射,注解的复习笔记

1.动态代理的作用 动态代理最主要的用途就是在各种框架中&#xff0c;很方便的在运行期间生成代理类&#xff0c;通过代理类就可以完成AOP、过滤器、拦截器等操作 &#xff08;注&#xff1a;代理就是被代理者没有能力或者不愿意去完成某件事情&#xff0c;需要找个人代替自己…

IDEA 将多个微服务Springboot项目Application启动类添加到services标签,统一启动、关闭服务

IDEA 将多个微服务Springboot项目Application启动类添加到services标签&#xff0c;统一启动、关闭服务 首先在Views > Tool Windows > Services 添加services窗口 点击services窗口&#xff0c;首次需要添加配置类型&#xff0c;我们选择Springboot 默认按照运行状态分…

Apache Hive 安装与配置的详细教程

1. Hive简介 Hive是基于Hadoop的一个数据仓库工具&#xff0c;用来进行数据提取、转化、加载&#xff0c;这是一种可以存储、查询和分析存储在Hadoop中的大规模数据的机制。hive数据仓库工具能将结构化的数据文件映射为一张数据库表&#xff0c;并提供SQL查询功能&#xff0c;能…

【QGIS入门实战精品教程】5.3:CGCS2000转Lambert投影

参考阅读: 【GlobalMapper精品教程】081:WGS84/CGCS2000转Lambert投影 文章目录 一、加载实验数据二、投影转换三、批量投影转换一、加载实验数据 加载配套实验数据,如下图所示:图层为长沙市范围、长沙市酒店宾馆分布点位、湖南省酒店分布点位矢量数据。 双击图层,打开信…

Java||Springboot读取本地目录的文件和文件结构,读取服务器文档目录数据供前端渲染的API实现

目录 一、用Springboot读取本地工作目录的文件和文件结构 1.1、需求介绍 1.2、后端传递数据 1.2.1、语言框架 1.2.2、本地工作目录示范 1.2.3、后端代码 ①controller层 ②service层 ③响应的数据 二、总结 一、用Springboot读取本地工作目录的文件和文件结构 1.1、…

5.23.12 计算机视觉的 Inception 架构

1. 介绍 分类性能的提升往往会转化为各种应用领域中显着的质量提升&#xff0c;深度卷积架构的架构改进可用于提高大多数其他计算机视觉任务的性能&#xff0c;这些任务越来越依赖于高质量的学习视觉特征。在 AlexNet 功能无法与手工设计、制作的解决方案竞争的情况下&#xf…

Spring框架学习笔记(四):手动实现 Spring 底层机制(初始化 IOC容器+依赖注入+BeanPostProcessor 机制+AOP)

1 Spring 整体架构示意图 2 阶段 1 -- 编写自己的 Spring 容器&#xff0c;实现多层扫描包 编写自己的 Spring 容器&#xff0c;实现多层扫描包&#xff0c;排除包下不是bean的&#xff0c; 得到 bean 对象&#xff0c;放入到临时ioc容器中 代码实现&#xff1a; &#xff0…

闲鱼电商运营高级课程,一部手机学会闲鱼开店赚钱(34节课)

课程目录 1&#xff0c;闲鱼更货出售主要核心原理.mp4 2、闲鱼前期开店准备.mp4 3.账号基础信息设置1.mp4 4、提升账号权重.mp4 5、注意避免违规行为.mp4 6、实接课 应该怎么选择爆款产品.mp4 7、分析商品的闲鱼市场.mp4 8、寻找最低价货源.mp4 9、怎么寻我优质的货源…

把自己的垃圾代码发布到官方中央仓库

参考博客&#xff1a;将组件发布到maven中央仓库-CSDN博客 感谢这位博主。但是他的步骤有漏缺&#xff0c;相对进行补充 访问管理页面 网址&#xff1a;Maven Central 新注册账号&#xff0c;或者使用github快捷登录&#xff0c;建议使用github快捷登录 添加命名空间 注意&…

【会议征稿,IEEE出版】第九届信息科学、计算机技术与交通运输国际学术会议(ISCTT 2024,6月28-30)

第九届信息科学、计算机技术与交通运输国际学术会议&#xff08;ISCTT 2024&#xff09;将于2024年6月28-30日在中国绵阳举行。 ISCTT 2024将围绕 “信息科学”、"计算机技术”、“交通运输” 等最新研究领域&#xff0c;为来自国内外高等院校、科学研究所、企事业单位的专…

【idea】idea2024最新版本下载_安装_破解

1、下载 下载地址&#xff1a;下载 IntelliJ IDEA – 领先的 Java 和 Kotlin IDE 下载完成&#xff1a; idea破解脚本下载链接&#xff1a;https://pan.baidu.com/s/1L5qq26cRABw8XuEn_CngKQ 提取码&#xff1a;6666 下载完成&#xff1a; 2、安装 1、双击idea的安装包&…

MGR集群从库出现RECOVERING

一、MGR集群问题 说明&#xff1a; 1、启动MGR集群&#xff0c;发现从库转态是&#xff1a;RECOVERING&#xff0c;导致数据不同步。 2、查看MGR日志报错信息&#xff0c;发现提示从库以存在数据库linux&#xff0c;导致无法创建。 3、报错信息如下图所示&#xff1a; 二、解决…

数组-在两个长度相等的有序数组中找到上中位数

题目描述 解题思路 此题目直接遍历两个列表&#xff0c;时间复杂度为O(n)&#xff1b;使用二分法去比较两个递增列表的中位数&#xff0c;缩小两个数组中位数范围&#xff0c;时间复杂度O(logn)&#xff0c;这里我们的算法实现使用二分法。 通过举例子来说明解题算法&#xf…

git revert 和 git reset

文章目录 工作区 暂存区 本地仓库 远程仓库需求&#xff1a;已推送到远程仓库&#xff0c;想要撤销操作git revert &#xff08;添加新的提交来“反做”之前的更改&#xff0c;云端会残留上次的提交记录&#xff09;git reset&#xff08;相当于覆盖上次的提交&#xff09;1.--…

lvgl无法显示中文

环境&#xff1a; VS2019、LVGL8.3 问题&#xff1a; VS2019默认编码为GB2312&#xff0c; 解决&#xff1a; VS2022设置编码方式为utf-8的三种方式_vs utf8-CSDN博客 我用的方法2&#xff0c;设置为 utf-8无签名就行。

Java+Spring+ IDEA+MySQL云HIS系统源码 云HIS适合哪些地区的医院?

JavaSpring IDEAMySQL云HIS系统源码云HIS适合哪些地区的医院&#xff1f; 云HIS适合哪些地区的医院&#xff1f; 云HIS&#xff08;云医院信息系统&#xff09;适合多种地区的医院&#xff0c;特别是那些希望实现医疗服务的标准化、信息化和规范化&#xff0c;同时降低IT运营成…

二叉排序树的创建

二叉排序树就是节点经过排序构建起的二叉树&#xff0c;其有以下性质&#xff1a; 1. 若它的左子树不为空&#xff0c;则左子树上所有节点的值均小于它的根节点的值。 2. 若它的右子树不为空&#xff0c;则右子树上所有节点的值均大于它的根节点的值。 3. 它的左、右子树也分…

【评价类模型】层次分析法(AHP)

1.评价类思想综述&#xff1a; 明确评价主体–>评价指标确定–>计算指标权重–>方案评价 1.1指标确定&#xff1a; 可以通过一个思维导图的方式来画出一个指标系统&#xff0c;先确定方向&#xff0c;再向下细化 注意&#xff1a;指标需要具有贴合性和独立性。 贴合…