图的算法

news2024/11/24 9:19:10
 拓扑排序算法

解析

要求:无环有向图

编译过程使用的是拓扑排序。A依赖BCD,在BCD三个文件编译完成才能引入A;B依赖ECD,在ECD三个文件编译完成才能引入B。拓扑排序排出整体的编译顺序E→CD→B→A

 

算法实现

找到整个图入度为0的点,打印,取消A和它的指向

再找到下一个入度为0的点,打印,取消B和它的指向

........

直到所有的节点遍历完成

package graph;

import java.util.*;

public class Sort {
    public static List<Node> topologicalSorting(Graph graph) {
        if (graph == null) {
            return null;
        }

        //HashMap<Node,Integer>,Node节点, Integer剩余的入度
        HashMap<Node, Integer> hashMap = new HashMap<>();
        Queue<Node> queue = new LinkedList<>();//记录入度为0的节点

        //记录map,找到入度为0的节点
        for (Node value : graph.nodes.values()) {
            hashMap.put(value, value.in);
            //找到入度为的节点,直接入队列
            if (value.in == 0) {
                queue.add(value);
            }
        }

        List<Node> result = new ArrayList<>();
        //将A放入result,并且取消A和它的指向
        while (!queue.isEmpty()) {
            Node node = queue.poll();
            result.add(node);
            for (Node node0 : node.nexts) {//A节点的next指向
                hashMap.put(node0, node0.in - 1);//取消A节点的指向,即下一个节点入度-1
                if (hashMap.get(node0) == 0) {//node0.in - 1 == 0
                    queue.add(node0);
                }
            }
        }

        return result;
    }
}

kruskal算法

 生成最小生成树

生成树:保证连通性

最小生成树:在所有的生成树中,各个边的累加的权值是最小的

算法实现解析

从权值最小的边开始考虑,考虑在加上这条边之后,这个图是否有形成环

没环加上,有环不要

                  

                        

如何判断是否形成环?

不使用HashSet结构,因为一开始所有的点都是存在的

令所有的点一开始各自为一个集合,根据图的边集的权值从小到大查看,查看边的from和to是否在一个集合中,如果不在,则合并两个集合;如果在,那么说明这条边加上就会形成环,跳过

实现集合的合并,查询:并查集结构

并查集结构简单版本
package graph;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;

public class SingleMergeSet {
    public static HashMap<Node, List<Node>> map;

    //初始化集合,让每个节点自己一个集合
    public static void initSet(HashMap<Integer, Node> nodes) {
        for (Node node : nodes.values()) {
            List<Node> listNode = new ArrayList<>();
            listNode.add(node);//将自己添加到自己的集合
            map.put(node, listNode);
        }
    }

    //判断是否在同一个集合
    public static boolean insameSet(Node from, Node to) {
        List<Node> listFrom = map.get(from);
        List<Node> listTo = map.get(to);
        return listFrom == listTo;//比较地址值判断两个节点是否属于同一个集合
    }

    //合并集合
    public static void mergeSet(Node from, Node to) {
        List<Node> listFrom = map.get(from);
        List<Node> listTo = map.get(to);
        for (Node toNode : listTo) {
            listFrom.add(toNode);
            map.put(toNode,listFrom);
            //其实并没有改变listTo集合的地址值,只是把集合中的值全部加到listFrom集合,但是下一次取值的时候是从map集合里面取值,从一个集合里面取值的地址值相同
        }
    }
}

并查集结构可以将上述结构实现为O(n)的算法

在kruskal算法实现的时候,我们可以直接使用并查集的结构

kruskal算法
package graph;

import java.util.*;

public class Kruskal implements Comparator<Edge> {

    //kruskal算法
    public static Set<Edge> kruskalMST(Graph graph) {
        SingleMergeSet singleMergeSet = new SingleMergeSet();//可以替换并查集
        singleMergeSet.initSet(graph.nodes);//初始化集合、创建集合
        PriorityQueue priorityQueue = new PriorityQueue();//堆结构

        for (Edge edge : graph.edges) {
            priorityQueue.add(edge);//把边放入堆里面
        }
        
        Set<Edge> result = new HashSet<>();//result返回保留哪些边
        HashMap<Node, List<Node>> map = SingleMergeSet.map;

        while (!priorityQueue.isEmpty()) {
            Edge edge = (Edge) priorityQueue.poll();//堆结构按照边的权值从小到大排序,从小到大的顺序由比较器决定
            Node from = edge.from;
            Node to = edge.to;

            if (!SingleMergeSet.insameSet(from, to)) {//from和to不在同一个集合之中
                result.add(edge);//保留这条边
                SingleMergeSet.mergeSet(from, to);//合并from节点所在的集合&to节点所在的集合
            }
        }
        
        return result;
    }

    @Override
    public int compare(Edge o1, Edge o2) {//比较器
        return o1.weight - o2.weight;
    }
}

prim算法

prim算法:同样是实现最小生成树 

从点的角度出发,最终的实现和k算法是一样的 

算法实现解析

由于prim算法是把一个一个点加到集合之中,只需要使用Set结构;而kraskal算法可能需要随时检查两个“相距很远”的节点是否在一个集合之中,需要使用较为复杂的结构

示例一:

1、可以任取一个点出发,比如从A出发,就有6,1,5三条边可以走,选择权值最小的一条边1

2、此时解锁C点,又解锁了5,5,6,4四条边,在第二轮被解锁的边中,选择权值最小的一条边4

3、F,2、6,选择2

4、D,此时它周围的边没有没被解锁的,在所有已经解锁的且没被使用的边中,挑权值最小的边

也就是6,1,5,5,5,6,42,6中黑色的数字,权值最小的为5。

        三个5之中,红色和绿色的5边的两个端点都已经被解锁,所以不选择;选择蓝色的5的边,解锁点B

7、B,3、6,所有已经解锁的且没被使用的边中,挑权值最小的边,选择3

8、E

结束,所有被选择的边组成最小生成树

因为每个点都需要被连上,所以无论选择哪个点开始,选择权值最小的边就可以了

          

示例二:

如何判断是否是新的边?

to点是新的点,边就是新的边;to点不是新的点,边也不是新的边

如何判断是否是新的点?

解锁的点都放在set集合中

为什么使用for循环?

无向图的两边的点都既是from也是to,只要放入了,就是重复的,会被直接跳过

for循环实质上是处理森林的问题

        如果整个图是连通的,可以不使用for循环

        如果整个图非连通,那么就会在一组一组的子图内生成最小生成树

一大片的连通区域一定是一次性计算完成

for循环处理分开的集合如何生成各自的最小生成树

prim算法
package graph;

import java.util.*;

public class Prim implements Comparator<Edge> {
    public static Set<Edge> primMST(Graph graph) {
        HashSet<Node> hashSet = new HashSet<>();//用来存储解锁的点
        Set<Edge> reslut = new HashSet<>();//result返回保留哪些边

        PriorityQueue<Edge> priorityQueue = new PriorityQueue<>();//小根堆,从小到大,其中放入已经解锁的边


        //随便挑一个点
        for (Node node : graph.nodes.values()) {

            if (!priorityQueue.isEmpty()) {
                hashSet.add(node);//弹出的新的点加到hashSet中
                for (Edge edge : node.edges) {//将由这条边发散出去的边都解锁
                    priorityQueue.add(edge);
                }

                while (!priorityQueue.isEmpty()) {//如果堆中非空
                    Edge edge0 = priorityQueue.poll();//弹出权值最小的边
                    Node node0 = edge0.to;//可能是下一个点

                    if (!hashSet.contains(node0)) {
                        hashSet.add(node0);//如果hashSet集合之中不含有set点,说明set点是新的点,加入点
                        reslut.add(edge0);//加入边

                        for (Edge nextEdge : node0.edges) {
                            priorityQueue.add(nextEdge);//解锁新的点指向的新的边
                        }
                    }
                }
            }
        }
        return reslut;
    }

    @Override
    public int compare(Edge o1, Edge o2) {
        return o1.weight - o2.weight;
    }
}

Dijkstra算法

可以出现负数的边,但是不能出现整体累加和为负数的环 

Dijkstra算法:单元最短路径算法

算法实现解析

一定要规定起始点,从这个点到其后的每个点的最短路径是多少。对于不可达的点,最短距离是无穷大或是系统最大值

A - B 的最短路径为3;A - C 的最短路径为5;A - D 的最短路径为9;A - E 的最短路径为19;

      ​​​​​​​    ​​​​​​​

在每行记录中挑选最小的,由最小的这个点发散,找到到大每个点的路径长度,留较小值 

往回跳的路径一定比原来的大;比如A到B到A的路径大于A到A

ABCDE
指定起始点A0
0+30+150+9
3+293+200
95+14
19

为什么权值不能为负数?

出现负数的时候,很可能发生已经锁死的边发现一个更小的路径

如图出现了累加和为负数的环,那么有一种路径为A - B - E - C - B - E - C - B ......一直在CBE这个圈转下去累加和会越来越小,那么A到B的最短路径为无穷小

每一个点到其他的点的最短路径为无穷小

 

Dijkstra算法
package graph;

import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;

public class Dijkstra {
    public static HashMap<Node, Integer> dijkstra(Node head) {
        //存储从head节点到其他节点的最小路径
        //key:Node节点,value:最短路径
        HashMap<Node, Integer> distanceMap = new HashMap<>();
        distanceMap.put(head, 0);

        //存储锁住的节点
        HashSet<Node> selectedNodes = new HashSet<>();


        Node minNode = getMinDistanceAndUnselectedNode(distanceMap, selectedNodes);//未被锁住的节点

        while (minNode != null) {
            int distance = distanceMap.get(minNode);//当前查找出的未被锁住的最小的节点到head节点的距离

            for (Edge edge : minNode.edges) {//从节点发散的边遍历
                if (!distanceMap.containsKey(edge.to)) {
                    //第一次加的时候没有值,需要判断
                    distanceMap.put(edge.to, distance + edge.weight);
                } else {
                    //修改,比较新的distance + edge.weight路径和原来的路径,取值更小的
                    distanceMap.put(edge.to, Math.min(distance + edge.weight, distanceMap.get(edge.to)));
                }
            }
            //锁住节点
            selectedNodes.add(minNode);
            //获取下一个路径最小的节点
            minNode = getMinDistanceAndUnselectedNode(distanceMap, selectedNodes)
        }

        return distanceMap;
    }


    //得到没有锁住的节点中当前的路径最小的节点
    public static Node getMinDistanceAndUnselectedNode(HashMap<Node, Integer> distanceMap, HashSet<Node> selectedNodes) {
        Integer min = Integer.MAX_VALUE;//寻找当前路径最小的节点

        for (Node node0 : distanceMap.keySet()) {
            if (!selectedNodes.contains(node0)) {//节点没有被锁住
                min = Math.min(min, distanceMap.get(node0));//寻找min
            }
        }
        for (Node node0 : distanceMap.keySet()) {
            if (!selectedNodes.contains(node0) && distanceMap.get(node0) == min) {
                return node0;//找到最小的路径,返回节点
            }
        }
        return null;
    }

    public static Node getMinDistanceAndUnselectedNode1(HashMap<Node, Integer> distanceMap, HashSet<Node> selectedNodes) {
        Node minNode = null;
        int minDistance = Integer.MAX_VALUE;

        for (Map.Entry<Node, Integer> entry : distanceMap.entrySet()) {
            Node node = entry.getKey();//拿到key
            int distance = entry.getValue();//拿到value
            //节点没有被锁住 && 当前节点到head节点的路径小于找到的最小路径 -- 也就是说找到了一个更小的路径
            if (!selectedNodes.contains(node) && distance < minDistance) {
                //赋值
                minNode = node;
                minDistance = distance;
            }
        }
        return minNode;
    }

}

关于使用堆结构

遍历的方式选择最小值

用堆选择最小值:但实际不能用原始的堆结构,因为在往下走的过程中,起始点A到其他点的距离是会被突然改写的,某个较大的值可能突然变成某个较小的值,堆结构中无法变化值。如果修改每一次入堆的值,需要使用全局扫描的方式,实现算法的代价很高

如果要使用堆结构,需要手动优化堆结构,自己实现需要的堆结构

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

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

相关文章

《第三期(先导课)》之《Python 开发环境搭建》

文章目录 《第 1 节 初始Python》《第 6 节 pip包管理工具》 《第 1 节 初始Python》 。。。 《第 6 节 pip包管理工具》 pip是Python的包管理工具,用于安装、升级和管理Python包。 pip是Python标准库之外的一个第三方工具,可以从Python Package Index(PyPI)下载和安装各种P…

频次最高的38道selenium面试题及答案

1、selenium的原理是什么&#xff1f; selenium的原理涉及到3个部分&#xff0c;分别是&#xff1a; 浏览器driver&#xff1a;一般我们都会下载driverclient&#xff1a;也就是我们写的代码 client其实并不知道浏览器是怎么工作的&#xff0c;但是driver知道&#xff0c;在…

机器学习中的决策阈值

什么是决策阈值&#xff1f; sklearn不允许我们直接设置决策阈值&#xff0c;但它允许我们访问用于进行预测的决策分数&#xff08;决策函数o/p&#xff09;。我们可以从决策函数输出中选择最佳得分并将其设置为决策阈值&#xff0c;并且将小于该决策阈值的所有那些决策得分值…

点信息标注_BillboardTextActor3D

开发环境&#xff1a; Windows 11 家庭中文版Microsoft Visual Studio Community 2019VTK-9.3.0.rc0vtk-example参考代码 demo解决问题&#xff1a;点附近创建左边或其他信息&#xff0c;且信息面板显示状态不受相机缩放、旋转影响 prj name: BillboardTextActor3D #include…

【Qt之QMetaType】使用

介绍 QMetaType类管理元对象系统中的命名类型。 该类用作QVariant和排队的信号与槽连接中类型的编组辅助器。它将类型名称与类型关联起来&#xff0c;以便可以在运行时动态创建和销毁它。使用Q_DECLARE_METATYPE()声明新类型&#xff0c;以使它们可供QVariant和其他基于模板的…

民生画派创始人张龙(天驰)作品

简介 张龙&#xff08;天驰&#xff09; 中国民生画派创始人 首届“陆俨少奖”金奖得主 人民大学巨幅主题创作高级研修班导师 中央美院客座教授 神舟十二号载人飞船遨游太空搭载作品创作者 被评为2021、2022年年度最具收藏价值艺术家 中国美术家协会会员 中国美术家协…

【CesiumJS入门】(11)加载LAS点云数据

前言 最近有两次投递简历以及面试都被问到了是否有三维点云数据处理相关的经验。然而我的岗位都没有和点云相关的工作任务&#xff0c;所以还是得自己加把劲呀。 本篇将从数据获取到加载来简易地介绍一个LAS点云数据的加载。 加载数据 首先&#xff0c;你得有一份LAS格式的…

Python实验项目6 :文件操作与模块化

1、使用random库&#xff0c;产生10个100到200之间的随机数&#xff0c;并求其最大值、平均值、标准差和中位数。 # 1、使用random库&#xff0c;产生10个100到200之间的随机数&#xff0c;并求其最大值、平均值、标准差和中位数。 import random # 定义一个列表 list[] for i …

MySQL–第4关:查询用户日活数及支付金额

MySQL–第4关&#xff1a;查询用户日活数及支付金额 – WhiteNights Site 标签&#xff1a;MySQL 非常好的题&#xff0c;爱来自中国。 题目 没啥用 任务描述 现有3张业务表&#xff0c;详见如下: 需要输出结果如下&#xff0c;没有支付的日期不需要显示&#xff0c;请写出对…

Leetcode---370周赛

题目列表 2923. 找到冠军 I 2924. 找到冠军 II 2925. 在树上执行操作以后得到的最大分数 2926. 平衡子序列的最大和 一、找到冠军I 第一题模拟题&#xff0c;简单来说是看每一行(列)是否全是1&#xff0c;当然不包括自己比自己强的情况&#xff0c;需要特判 代码如下 …

python回文日期 并输出下一个ABABBABA型回文日期

题目&#xff1a; 输入&#xff1a; 输入包含一个八位整数N&#xff0c;表示日期 对于所有的测评用例&#xff0c;10000101 ≤N≤89991231&#xff0c;保证N是一个合法日期的8位数表示 输出&#xff1a; 输出两行&#xff0c;每行一个八位数。第一行表示下一个回文日期第二…

任务管理器的正确使用教程

快捷键 Ctrlshiftesc&#xff1a;进入任务管理器 我以Win11举例 如何给XX排序 给XX排序&#xff0c;点击空白处可以选择某项降序排列&#xff08;可以找到最占用某项资料的程序&#xff09;&#xff0c;再点击空白处可以选择某项升序排列 文件正在使用&#xff0c;如何解决 …

windows系统下查看安卓apk的sha1

1.在apk所在文件夹打开cmd或者powershell 2.输入 certutil -hashfile xxx.apk SHA1 这样就可以了 3.指令格式 certutil -hashfile FileName [HashAlgorithm] certutil -hashfile&#xff1a;原样输入 FileName&#xff1a;文件名 HashAlgorithm&#xff1a;可选项包括&…

简述扫码登录原理及测试要点

扫码登录本质是解决将APP端的用户登录信息&#xff08;通常是Token&#xff09;通过扫码的形式安全稳定地同步给Web端。 操作流程&#xff1a; 打开登录页面&#xff0c;展示一个二维码(web)&#xff1b;打开APP扫描该二维码后&#xff0c;APP显示确认、取消按钮(app)&#xf…

A Survey on Neural Network Interpretability

A Survey on Neural Network Interpretability----《神经网络可解释性调查》 摘要 随着深度神经网络的巨大成功&#xff0c;人们也越来越担心它们的黑盒性质。可解释性问题影响了人们对深度学习系统的信任。它还与许多伦理问题有关&#xff0c;例如算法歧视。此外&#xff0c;…

野火霸天虎 STM32F407 学习笔记_5 按键输入;位带操作介绍

输入——按键点灯 开发板按键电路如下&#xff1a; 按键未按下接地&#xff0c;按下后为高电平。电容起到消抖作用&#xff0c;软件处理就不需要手动延时消抖了。 编程没啥难度&#xff0c;就是改了一下输入模式。使用 ReadInputDataBits 读取。 //bsp_button.c #include &q…

golang 2018,go 1.19安装Gin

GOPROXYhttps://mirrors.aliyun.com/goproxy/ 一致提示URL不能有点&#xff0c;给我整郁闷了&#xff0c;换了这个地址好了 但是一致提示zip的包问题&#xff0c;最后还是不行又换回七牛 NEWBEE&#xff01; [GIN-debug] Environment variable PORT is undefined. Using por…

SAE 2.0,让容器化应用开发更简单

云原生容器化应用托管模式的演变 云原生这个概念从提出&#xff0c;到壮大&#xff0c;再到今天的极大普及&#xff0c;始终处于一个不断演进和革新的过程中。云原生体系下应用的托管形态是随着企业应用架构在不断演进的。最早的应用大多是集中式、单体式的&#xff0c;应用通…

多元高斯分布

下面我们来看一下多元高斯分布&#xff0c;叫做 multivariative 高斯分布&#xff0c;也就是目前的情况是向量的形式&#xff0c;也就是说我的 x 它是一个向量&#xff0c;那这个情况下我们的高斯分布应该怎么去表示&#xff1f;我们这里面重点还是来看一下它的一个表示的方法&…

【Linux系统编程十五】:(基础IO2)--重定向实现原理 “Linux下一切皆文件“

【Linux系统编程十五】&#xff1a;重定向原理 与 "Linux下一切皆文件" 一.重定向1.实现原理2.输出重定向3.输入重定向4.补充&#xff1a;简易shell中实现重定向 二."Linux下一切皆文件"1.虚拟文件系统(VFS) 一.重定向 我们首先关闭2号文件描述符&#xff…