首页 > 编程语言 >【算法】006_图

【算法】006_图

时间:2024-02-15 18:11:47浏览次数:21  
标签:Node node 结点 add 算法 006 new public

图的表示

图的表示方法有很多种,如果遇到不同的表示方法,可以转换成自己最常用的那一种

public class Graph {
    public HashMap<Integer, Node> nodes;//点集
    public HashSet<Edge> edges;//边集

    public Graph() {
    }

    public Graph(HashMap<Integer, Node> nodes, HashSet<Edge> edges) {
        this.nodes = nodes;
        this.edges = edges;
    }

}

public class Edge {
    public int weight;//边的距离
    public Node from;
    public Node to;

    public Edge(int weight, Node from, Node to) {
        this.weight = weight;
        this.from = from;
        this.to = to;
    }
}

public class Node {
    public int value;//点的值
    public int in;//点的入度
    public int out;//点的出度
    public ArrayList<Node> nextList;//点指向哪些点
    public ArrayList<Edge> edgeList;//点的出边

    public Node(int value){
        this.value = value;
        in = 0;
        out = 0;
        nextList = new ArrayList<>();
        edgeList = new ArrayList<>();
    }
}

public class GraphGenerator {
    public Graph createGraph(Integer[][] matrix){
        Graph graph = new Graph();
        for (int i = 0; i < matrix.length; i++) {
            Integer from = matrix[i][0];//出点
            Integer to = matrix[i][1];//入点
            Integer weight = matrix[i][2];//距离
            //如果图的点集中没有当前结点
            if (!graph.nodes.containsKey(from)){
                graph.nodes.put(from, new Node(from));//新建结点加入点集中
            }
            if (!graph.nodes.containsKey(to)){
                graph.nodes.put(to, new Node(to));//新建结点加入点集中
            }
            Node fromNode = graph.nodes.get(from);
            Node toNode = graph.nodes.get(to);
            Edge newEdge = new Edge(weight, fromNode, toNode);
            fromNode.nextList.add(toNode);
            fromNode.out++;
            toNode.in++;
            fromNode.edgeList.add(newEdge);
            graph.edges.add(newEdge);
        }
        return graph;
    }
}

宽度优先遍历BFS

  • 利用队列实现
  • 从源节点开始依次按照宽度进队列,然后弹出
  • 每弹出一个结点,就把该结点所有没有进过队列的邻接点放入队列
  • 直到队列为空
public class Code01_BFS {
    public void bfs(Node node){
        if (node == null){
            return;
        }
        Queue<Node> queue = new LinkedList<>();
        HashSet<Node> set = new HashSet<>();//用于标记访问过的结点
        queue.add(node);
        set.add(node);
        while (!queue.isEmpty()){
            Node cur = queue.poll();
            System.out.println(cur.value);
            for (Node next : cur.nexts) {
                if (!set.contains(next)){
                    set.add(next);
                    queue.add(next);
                }
            }
        }
    }
}

深度优先遍历DFS

  • 利用栈实现
  • 从源节点开始把结点按照深度放入栈,然后弹出
  • 每弹出一个点,就把该结点下一个没有进过栈的邻接点放入栈
  • 直到栈为空
public class Code02_DFS {
    public void dfs(Node node){
        if (node == null){
            return;
        }
        Stack<Node> stack = new Stack<>();
        HashSet<Node> set = new HashSet<>();
        stack.add(node);
        set.add(node);
        System.out.println(node.value);
        while (!stack.isEmpty()){
            Node cur = stack.pop();
            for (Node next : cur.nexts) {
                //只要是没有访问过的结点就访问,一直走到底
                //栈中保存的就是当前的DFS的路径
                if (!set.contains(next)){
                    stack.push(cur);
                    stack.push(next);
                    set.add(next);
                    System.out.println(next.value);
                    break;//访问下一个邻接点
                }
            }
        }
    }
}

拓扑排序算法

适用范围:要求有向图,且有入度为0的结点,且没有环

  • 从第一个结点node出发,访问node

  • 去掉图上的node,同时删除与node的邻接点之间的边,这些邻接点的入度就会减一

  • 然后访问下一个入度为0的结点

public class Code03_TopologySort {
    public List<Node> topologySort(Graph graph){
        //key:某一个node;value:剩余的入度
        HashMap<Node, Integer> inMap = new HashMap<>();
        //保存入度为0的结点
        Queue<Node> zeroInQueue = new LinkedList<>();
        //将所有结点放入哈希表中
        for (Node node : graph.nodes.values()) {
            inMap.put(node, node.in);
            if (node.in == 0){
                zeroInQueue.add(node);
            }
        }
        //拓扑排序的结果依次加入result
        List<Node> result = new ArrayList<>();
        while (!zeroInQueue.isEmpty()){
            Node cur = zeroInQueue.poll();
            result.add(cur);
            //去掉这个入度为0的结点后,它的邻接点的入度也要减一
            for (Node next : cur.nexts) {
                inMap.put(next, inMap.get(next) - 1);//更新
                if (inMap.get(next) == 0){
                    zeroInQueue.add(next);
                }
            }
        }
        return result;
    }
}

kruskal算法

适用范围:要求无向图

作用:生成最小生成树

  • 每次都选择最小的边
  • 选择过程中不能形成环
  • 直到形成了最小生成树

image-20240215133543305

  • 初始时,每个结点单独一个集合:{A},{B},{C},

  • 找到最小的边为2,2连接了A和C,A和C不在同一个集合,于是把他们加入一个集合:{A,C},{B},

  • 考察权值为4的边,发现D和C不在一个集合,合并:{A,C,D},

  • 考察权值为7的边,发现A和B不在一个集合,合并:

  • 考察权值为100的边,形成了环,过

    如何判断是否有环:

    • 边的两个结点在一个集合,加上这条边之后就会形成环
  • 考察权值为1000的边,形成了环,过

  • 最后选择了权值为2、4、7的三条边

public class Code04_Kruskal {

    public class MySet{
        
        //key:结点;value:结点对应的集合
        public HashMap<Node, List<Node>> setMap;

        public MySet(List<Node> nodes){
            //每个结点对应的集合
            //最开始的时候一个结点一个集合
            for (Node cur : nodes){
                List<Node> set = new ArrayList<>();
                set.add(cur);
                setMap.put(cur, set);
            }
        }

        //判断两个结点是否属于一个集合
        public boolean isSameSet(Node from, Node to){
            List<Node> fromSet = setMap.get(from);
            List<Node> toSet = setMap.get(to);
            return fromSet == toSet;//一个集合
        }

        //一条边的两个结点,如果不在一个集合就合并
        public void union(Node from, Node to){
            List<Node> fromSet = setMap.get(from);//from所在的结点
            List<Node> toSet = setMap.get(to);//to所在的结点
            for (Node toNode : toSet) {
                fromSet.add(toNode);
                setMap.put(toNode, fromSet);
            }
        }
    }

    public class EdgeComparator implements Comparator<Edge> {

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

    }

    public Set<Edge> kruskalMST(Graph graph){
        //初始化,每个结点对应一个集合
        MySet mySet = new MySet(graph.nodes.values().stream().toList());
        //边按照从小到大排序
        PriorityQueue<Edge> priorityQueue = new PriorityQueue<>(new EdgeComparator());
        for (Edge edge : graph.edges) {
            priorityQueue.add(edge);
        }
        Set<Edge> result = new HashSet<>();
        while (!priorityQueue.isEmpty()){
            Edge edge = priorityQueue.poll();//获取最短边
            //判断edge的两个结点是否属于一个集合
            //如果属于一个集合,加上这条边,这个图就会有环
            //如果不属于一个集合,就合并
            if (!mySet.isSameSet(edge.from, edge.to)){
                result.add(edge);
                mySet.union(edge.from, edge.to);//合并
            }
        }
        return result;
    }
}

prim算法

适用范围:要求无向图

作用:生成最小生成树

  • 集合用于保存访问过的结点
  • 每次都选择的是集合中的结点的所有邻接边中,最短的那条边,所以可以使用小根堆
  • 选中的边就会有一个新的结点,这个结点就加入集合中

image-20240215155016961

  • 任意选择一个出发点,假设从A出发
  • A的邻接边中AC权值最小:
  • {A,C}两个结点的所有邻接边中CF权值最小:
  • {A,C,F}的邻接边中FD权值最小:
  • {A,C,F,D}的邻接边中CB最短:
  • {A,C,F,D,B}的邻接边中BE权值最小:
public class Code05_Prim {
    public class EdgeComparator implements Comparator<Edge> {

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

    }

    public Set<Edge> primMST(Graph graph){
        //每加入一个新的点到集合中,就会解锁新的邻接边
        //解锁的边进入小根堆
        PriorityQueue<Edge> priorityQueue = new PriorityQueue<>(new EdgeComparator());

        HashSet<Node> set = new HashSet<>();

        Set<Edge> result = new HashSet<>();

        // Node node = graph.nodes.values().stream().toList().get(0);//选择一个结点作为开始
        
        for (Node node : graph.nodes.values()) {//可能有多个不连通的图,需要每一个图生成一个最小生成树,这时就需要循环
            //选中的结点不在集合中
            //如果选中的结点在集合中,会形成环
            if (!set.contains(node)){//多个不连通的图的时候需要加上这行
                set.add(node);
                //遍历node的所有邻接边,将边加入到小根堆中
                for (Edge edge : node.edges) {
                    priorityQueue.add(edge);
                }
                while (!priorityQueue.isEmpty()){
                    Edge edge = priorityQueue.poll();//小根堆中的最短边
                    Node toNode = edge.to;//解锁的新节点  edge中fromNode已经在集合中了
                    //判断是否在集合中,如果在的话这个点就不能加入,也就是edge这条边不能要,否则会形成环
                    if (!set.contains(toNode)){
                        set.add(toNode);
                        result.add(edge);
                        for (Edge nextEdge : toNode.edges) {
                            priorityQueue.add(nextEdge);
                        }
                    }
                }
            }
        }
        
        return result;
    }
}

Dijkstra算法

适用范围:没有权值为负数的边

作用:从某一个点出发,到其他点的最短距离。其他点要经过已经已知的点。

从一个结点A开始,依次加入结点,更新A到其他结点的最短距离。

image-20240215164627657

初始

image-20240215164737331

  • A有AB、AC、AD三条边
    • 由于这三条边使得A到B、C、D的距离变得更短了

image-20240215164917456

  • A到A的最短距离已经求到了,剩余结点中A到B的距离最近,于是选择B。B有BA、BC、BE三条边

    有了新的边,判断A到其他点的距离是否更短了

    • 从A到B再到C,AC之间的距离变短

      也就是AB的最短距离已经知道,如果B可以到达其他结点,那么距离就会更短

    • 从A到B再到E,AE之间的距离变短

image-20240215165244334

  • 剩余结点A到C的距离最近,选择C。

    判断A经过B、C到其他结点是否距离更短

    也就是AC最短距离是5,那么AE=AC+CE=19

    如果C可以到达其他结点,那么A到其他结点的距离就可能会更新

    image-20240215165832920

public class Code06_Dijkstra {

    public HashMap<Node, Integer> dijkstra1(Node head){
        //从head出发到所有点的最小距离
        //key:从head出发到达key
        //value:从head出发到达key的最小距离
        //如果在表中,没有T的记录,含义是从head出发到T点的距离是正无穷
        HashMap<Node, Integer> distanceMap = new HashMap<>();
        distanceMap.put(head, 0);
        //已经求过距离的结点,存在selectedNodes中,以后再也不碰
        HashSet<Node> selectedNodes = new HashSet<>();
        //从distanceMap中选择最短的路径,并且是没有被使用过的,没有被当作中间结点,更新head到其他结点的距离
        Node minNode = getMinDistanceAndUnselectedNode(distanceMap, selectedNodes);
        while (minNode != null){
            int distance = distanceMap.get(minNode);//head到minNode的最短距离
            //已经知道了head到minNode的最短距离,那么其它结点从head经过minNode到达之后距离是否更短
            for (Edge edge : minNode.edges) {
                Node toNode = edge.to;
                if (!distanceMap.containsKey(toNode)){//说明head到toNode是正无穷
                    distanceMap.put(toNode, distance + edge.weight);
                }
                //原来的距离 与 加入新节点之后的距离进行比较
                distanceMap.put(edge.to, Math.min(distanceMap.get(toNode), distance + edge.weight));
            }
            selectedNodes.add(minNode);//minNode被使用过了
        }
        return distanceMap;
    }

    public Node getMinDistanceAndUnselectedNode(HashMap<Node, Integer> distanceMap, HashSet<Node> touchedNodes){
        Node minNode = null;
        int minDistance = Integer.MAX_VALUE;
        for (Map.Entry<Node, Integer> entry : distanceMap.entrySet()) {
            Node node = entry.getKey();
            int distance = entry.getValue();
            if (!touchedNodes.contains(node) && distance < minDistance){
                minDistance = distance;
                minNode = node;
            }
        }
        return minNode;
    }
}

标签:Node,node,结点,add,算法,006,new,public
From: https://www.cnblogs.com/jyyyy/p/18016450

相关文章

  • 洛谷【算法1-5】 贪心
    P2240【深基12.例1】部分背包问题-洛谷|计算机科学教育新生态(luogu.com.cn)#include<bits/stdc++.h>usingnamespacestd;typedeflonglongll;constintN=110;intn,t;structnode{intm,v;};boolcmp(nodeaa,nodebb){returnaa.v*bb.m>bb.v*aa.m......
  • svm算法
    支持向量机(supportvectormachines,SVM)是一种二分类模型,它的基本模型是定义在特征空间上的间隔最大的线性分类器,间隔最大使它有别于感知机;SVM还包括核技巧,这使它成为实质上的非线性分类器。SVM的的学习策略就是间隔最大化,可形式化为一个求解凸二次规划的问题,也等价于正则化的合......
  • 【算法】【动态规划】过桥问题
    1 题目在一个夜黑风高的晚上,有n(n<=50)个小朋友在桥的这边,现在他们需要过桥,但是由于桥很窄,每次只允许不大于两人通过,他们只有一个手电筒,所以每次过桥的两个人需要把手电筒带回来,i号小朋友过桥的时间为T[i],两个人过桥的总时间为二者中时间长者。问所有小朋友过桥的总时间最短是......
  • 回溯算法模板
    回溯算法的模板通常包含递归函数和回溯过程。以下是一个通用的回溯算法模板:defbacktrack(start,path,other_parameters):#满足结束条件时,将当前路径加入结果ifsatisfies_end_condition:result.append(path[:])return#从start开始遍历可......
  • 字符串KMP算法详解
    引入字符串kmp算法用于解决字符串匹配的问题:给出两个字符串\(s_1\)和\(s_2\),若\(s_1\)的区间\([l,r]\)子串与\(s_2\)完全相同,则称\(s_2\)在\(s_1\)中出现了,其出现位置为\(l\)。现在请你求出\(s_2\)在\(s_1\)中所有出现的位置。很显然,我们能够想到暴力求......
  • Tarjan 算法
    part1:离线求\(lca\)将走过的点且未回溯的标记为\(1\),已回溯的标记为\(2\),未访问标记为\(0\)把对于一点\(x\)的询问\(y\)存进相应的\(vector\),当回溯时判断如果询问的点标记为\(2\),则\(lca\)为询问的点\(y\)到根的路径上最早标记为\(1\)的点但直接找复杂度不对......
  • 使用lanczos算法进行的预处理共轭梯度算法(Preconditioned Conjugate Gradients Metho
    构造预处理矩阵M(对称正定)下图来自:预处理共轭梯度法(1)......
  • 代码随想录算法训练营第十七天| 110.平衡二叉树 257. 二叉树的所有路径 404.左叶
    110.平衡二叉树 题目链接:110.平衡二叉树-力扣(LeetCode)思路:判断平衡二叉树,就是判断两个子树的高度差,继而问题转化为了如何求子树的高度——后序遍历(主要卡在了这里)。递归函数返回的是树的高度,同时用-1来表示退出递归(一开始想着用bool型作为返回值,发现函数不好设计)。同时要关......
  • URL编码算法:解决特殊字符在URL中的烦恼
    引言:URL编码算法是一种将URL中的特殊字符转换为特定格式的编码方式。它在网络传输中起到了保护数据安全与完整性的重要作用。本文将深入探讨URL编码算法的优点与缺点,并介绍它在Web开发、网络安全等方面的应用。URL编码解码|一个覆盖广泛主题工具的高效在线平台(amd794.com)h......
  • 【算法】【动态规划】钢条切割
    1 题目来自算法导论的一道经典题目:2 解答动态规划原理虽然已经用动态规划方法解决了上面问题,但是大家可能还跟我一样并不知道什么时候要用到动态规划。总结一下上面的斐波拉契数列和钢条切割问题,发现两个问题都涉及到了重叠子问题,和最优子结构。①最优子结构用动态规......