算法基础入门——图结构、广度优先遍历、深度优先遍历、拓扑排序、克鲁斯卡尔 (Kruskal)、prim算法(普里姆算法)、Dijkstra算法

package com.zuoshen.jichurumen.class06;

import java.util.*;

/**
 * @author ShiZhe
 * @create 2022-03-04 18:10
 */
public class code01 {

    /**
     * 图节点
     */
    public class Node {
        // 节点的值
        public int value;
        // 入度
        public int in;
        // 出度
        public int out;
        // 该节点连接的点
        public ArrayList<Node> nexts;
        // 属于该节点的边
        public ArrayList<Edge> edges;

        // 构造函数初始化
        public Node(int value) {
            this.value = value;
            in = 0;
            out = 0;
            nexts = new ArrayList<>();
            edges = new ArrayList<>();
        }
    }

    /**
     * 图的边
     */
    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 Graph {
        // 节点集,key为节点的编号,value为实际的点
        public HashMap<Integer, Node> nodes;
        // 边集
        public HashSet<Edge> edges;

        // 构造函数初始化
        public Graph() {
            nodes = new HashMap<>();
            edges = new HashSet<>();
        }
    }

    /**
     * 广度优先遍历
     * 利用队列实现
     * @param node
     */
    public static void Bfs(Node node) {
        if (node == null) {
            return;
        }
        // 利用队列
        LinkedList<Node> queue = new LinkedList<>();
        // hashset存储访问过了节点
        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);
                }
            }
        }
    }

    /**
     * 深度优先遍历
     * 利用栈实现
     * @param node
     */
    public static void Dfs(Node node) {
        if (node == null) {
            return;
        }
        Stack<Node> stack = new Stack<>();
        HashSet<Node> set = new HashSet<>();
        stack.push(node);
        set.add(node);
        // 入栈打印
        System.out.println(node.value);
        while (!stack.isEmpty()) {
            Node cur = stack.pop();
            for (Node next : cur.nexts) {
                if (!set.contains(next)) {
                    stack.push(cur);
                    stack.push(next);
                    set.add(node);
                    // 入栈打印
                    System.out.println(next.value);
                    // 很重要
                    break;
                }
            }
        }
    }

    /**
     * 拓扑排序算法
     * 适用范围:要求有向图,且有入度为0的节点,且没有环
     * @param graph
     * @return
     */
    public static List<Node> sortedTopology(Graph graph) {
        //返回的节点List
        List<Node> result = new ArrayList<>();
        // 生成节点与入度对应的hashmap
        HashMap<Node, Integer> hashMap = new HashMap<>();
        // 遍历图,得到入度为0的点
        LinkedList<Node> queue = new LinkedList<>();
        for (Node node : graph.nodes.values()) {
            hashMap.put(node, node.in);
            if (node.in == 0) {
                queue.push(node);
            }
        }
        while (!queue.isEmpty()) {
            Node cur = queue.poll();
            result.add(cur);
            for (Node node : cur.nexts) {
                // 访问后修改入度
                hashMap.put(node, hashMap.get(node) - 1);
                if (hashMap.get(node) == 0) {
                    queue.push(node);
                }
            }
        }
        return result;
    }

    /**
     * 无向图
     * 克鲁斯卡尔 (Kruskal)算法:全局选择不成环的最短的边
     * 是用来求加权连通图的最小生成树的算法。利用并查集。
     * 基本思想:按照权值从小到大的顺序选择n-1条边,并保证这n-1条边不构成回路。
     * 具体做法:首先构造一个只含n个顶点的森林,然后依权值从小到大从连通网中选择边加入到森林中,并使森林中不产生回路,直至森林变成一棵树为止。
     * @param graph 整个图
     * @return 最短生成树的边集
     */
    public static HashSet<Edge> kruskalMST(Graph graph) {
        List<Node> nodes = new ArrayList<Node>();
        for (Node cur : graph.nodes.values()) {
            nodes.add(cur);
        }
        // 并查集初始化
        MySets mySets = new MySets(nodes);
        // 新建一个堆
        PriorityQueue<Edge> priorityQueue = new PriorityQueue<>(new EdgeComparator());
        // 边排序
        for (Edge edge : graph.edges) {
            priorityQueue.add(edge);
        }
        HashSet<Edge> result = new HashSet<>();
        while (!priorityQueue.isEmpty()) {
            Edge edge = priorityQueue.poll();
            // 不成环,加入
            if (!mySets.isSameSet(edge.from, edge.to)) {
                result.add(edge);
                mySets.union(edge.from, edge.to);
            }
        }
        return result;
    }

    /**
     * 简化版并查集
     */
    public static class MySets {
        // 每一个节点所在的集合
        public HashMap<Node, List<Node>> setMap;
        // 构造函数,初始化
        public MySets(List<Node> nodes) {
            for (Node cur : nodes) {
                List<Node> set = new ArrayList<Node>();
                set.add(cur);
                setMap.put(cur, set);
            }
        }
        // 判断from和to是否在同一个集合中
        public boolean isSameSet(Node from, Node to) {
            List<Node> fromSet = setMap.get(from);
            List<Node> toSet = setMap.get(to);
            // 比较2个的内存地址,是否指向同一个集合
            return fromSet == toSet;
        }
        // 合并
        public void union(Node from, Node to) {
            List<Node> fromSet = setMap.get(from);
            List<Node> toSet = setMap.get(to);
            for (Node toNode : toSet) {
                fromSet.add(toNode);
                setMap.put(toNode, fromSet);
            }
        }
    }

    /**
     * 比较器,按照边的从小到大排序
     */
    public static class EdgeComparator implements Comparator<Edge> {

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

    /**
     * 无向图
     * prim算法(普里姆算法)实现思路:随机选个点,再选点相连的最短的边。
     * 1. 将连通网中的所有顶点分为两类(假设为 A 类和 B 类)。初始状态下,所有顶点位于 B 类;
     * 2. 选择任意一个顶点,将其从 B 类移动到 A 类;
     * 3. 从 B 类的所有顶点出发,找出一条连接着 A 类中的某个顶点且权值最小的边,将此边连接着的 B 类中的顶点移动到 A 类;
     * 4. 重复执行第 3  步,直至 B 类中的所有顶点全部移动到 A 类,恰好可以找到 N-1 条边。
     * @param graph 整个图
     * @return 最短生成树的边集
     */
    public static HashSet<Edge> primMST(Graph graph) {
        // 以边排序的小根堆
        PriorityQueue<Edge> priorityQueue = new PriorityQueue<>(new EdgeComparator());
        // 节点集
        HashSet<Node> set = new HashSet<>();
        // 返回结果的边集
        HashSet<Edge> result = new HashSet<>();
        // 随机选择一个节点开始
        for (Node node : graph.nodes.values()) {
            // 判断是否访问过
            if (!set.contains(node)) {
                // 加入节点
                set.add(node);
                // 加入边
                for (Edge edge : node.edges) {
                    priorityQueue.add(edge);
                }
                // 不为空
                while (!priorityQueue.isEmpty()) {
                    Edge edge = priorityQueue.poll();
                    Node toNode = edge.to;
                    // 判断是否成环
                    if (!set.contains(toNode)) {
                        set.add(toNode);
                        result.add(edge);
                        for (Edge nextEdge : toNode.edges) {
                            priorityQueue.add((nextEdge));
                        }
                    }
                }
            }
        }
        return result;
    }

    /**
     * 没有权值为负数的边
     * Dijkstra算法详解:最短路径问题
     * Dijkstra算法采用的是一种贪心的策略,声明一个数组dis来保存源点到各个顶点的最短距离和一个保存已经找到了最短路径的顶点的集合:
     * T,初始时,原点 s 的路径权重被赋为 0 (dis[s] = 0)。若对于顶点 s 存在能直接到达的边(s,m),则把dis[m]设为w(s, m),
     * 同时把所有其他(s不能直接到达的)顶点的路径长度设为无穷大。初始时,集合T只有顶点s。
     * 然后,从dis数组选择最小值,则该值就是源点s到该值对应的顶点的最短路径,并且把该点加入到T中,OK,此时完成一个顶点,
     * 然后,我们需要看看新加入的顶点是否可以到达其他顶点并且看看通过该顶点到达其他点的路径长度是否比源点直接到达短,
     * 如果是,那么就替换这些顶点在dis中的值。然后,又从dis中找出最小值,重复上述动作,直到T中包含了图的所有顶点。
     * @param head 从该点出发
     * @return 从某点出发,到其他店的最短路径
     */
    public static HashMap<Node, Integer> dijkstra(Node head) {
        // 返回的最短路径的hashMap
        // key:从head出发到大key
        // value:从head出发到大key的最短路径
        HashMap<Node, Integer> distanceMap = new HashMap<>();
        // 初始化,将head放入hashMap中
        distanceMap.put(head, 0);
        // 以求过距离的点
        HashSet<Node> selectNodes = new HashSet<>();
        // 未访问过的最小的节点
        Node minNode = getMinDistanceAndUnselectedNode(distanceMap, selectNodes);
        while (minNode != null) {
            // 获取最小节点的当前路径长度
            int distance = distanceMap.get(minNode);
            // 遍历边
            for (Edge edge : minNode.edges) {
                Node toNode = edge.to;
                // 某节点未在hashmap中出现过
                if (!distanceMap.containsKey(toNode)) {
                    distanceMap.put(toNode, distance + edge.weight);
                }
                // 出现过,比较大小
                distanceMap.put(toNode, Math.min(distanceMap.get(toNode), distance + edge.weight));
            }
            selectNodes.add(minNode);
            minNode = getMinDistanceAndUnselectedNode(distanceMap, selectNodes);
        }
        return distanceMap;
    }

    /**
     * 返回在distanceMap中最小的距离且并未选择过的节点
     * @param distanceMap
     * @param selectNodes
     * @return
     */
    public static Node getMinDistanceAndUnselectedNode(HashMap<Node, Integer> distanceMap, HashSet<Node> selectNodes) {
        Node minNode = null;
        int minDistance = Integer.MAX_VALUE;
        for (Node node : distanceMap.keySet()) {
            int distance = distanceMap.get(node);
            if (!selectNodes.contains(node) && distance < minDistance) {
                minNode = node;
                minDistance = distance;
            }
        }
        return minNode;
    }
}

 

posted @ 2022-03-12 13:40  北漂的尘埃  阅读(52)  评论(0编辑  收藏  举报