算法学习笔记----图

目录

图结构

广度优先搜索

深度优先搜索

拓扑排序

最小生成树

k算法

p算法

最短路径


图结构

存储图的结构有很多,不同的结构写出来的算法模型也不同。所以最好有自己熟悉的图结构,对题目所给的边、点信息通过写一个接口转换为自己熟悉的图结构,就可以用自己熟悉的图结构解题。

package com.wtp.图.图结构;
​
import java.util.HashMap;
import java.util.HashSet;
​
public class Graph {
​
    public HashMap<Integer,Node> nodes;//点集
    public HashSet<Edge> edges;//边集
    
    public Graph() {
        nodes = new HashMap<>();
        edges = new HashSet<>();
    }
}
​
package com.wtp.图.图结构;
​
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;
    }
}
​
package com.wtp.图.图结构;
​
import java.util.ArrayList;
​
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<>();
    }
}
​

广度优先搜索

准备一个队列,和一个容器记录所有点是否出现过。根据队列先进先出的特点,弹出一个点看这个点的下属点,遇到点如果没有加入过队列就加入。

package com.wtp.图;
​
import java.util.HashSet;
import java.util.LinkedList;
import java.util.Queue;
​
import com.wtp.图.图结构.Node;
​
public class 广度优先搜索 {
​
    public static void bfs(Node node) {
        
        if(node == null) {
            return;
        }
        
        Queue<Node> queue = new LinkedList<>();
        HashSet<Node> set = new HashSet<>();//记录点是否出现过
        
        set.add(node);
        queue.add(node);
        
        while(!queue.isEmpty()) {
            Node cur = queue.remove();
            System.out.println(node.value);
            for(Node next : cur.nexts) {
                if(!set.contains(next)) {
                    set.add(next);
                    queue.add(next);
                }
            }
        }
    }
}
​
深度优先搜索

深度优先:set记录node是否出现过,手动压栈,弹出一个node就遍历与该node相连的node,若发现一个没遍历过的就把node和没遍历过的压栈每次只找一个。

package com.wtp.图;
​
import java.util.HashSet;
import java.util.Stack;
​
import com.wtp.图.图结构.Node;
​
public class 深度优先搜索 {
​
    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)) {
                    //记录
                    set.add(next);
                    System.out.println(next.value);
                    //当前节点还没看完 压回去
                    stack.push(cur);
                    //下一次弹出的就是此次新发现的节点
                    stack.push(next);
                    break;
                }
            }
        }
    }
}
​
拓扑排序

拓扑排序:把所有点加入到map中记录节点和剩余入度,队列中存储入度为0的点,移除队列中入度为0的点,消除指向其他节点的影响,若出现入度为0的点继续入队列 队列可以保证发现入度为0的点的顺序,记顺序用队列

package com.wtp.图;
​
import java.util.ArrayList;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Queue;
​
import com.wtp.图.图结构.Graph;
import com.wtp.图.图结构.Node;
​
public class 拓扑排序 {
​
    public static List<Node> process(Graph graph){
        
        //入度为0的点才进队列
        Queue<Node> zeroQueue = new LinkedList<>();
        //记录每个节点的入度
        Map<Node,Integer> inMap = new HashMap<>();
        List<Node> res = new ArrayList<>();
        
        for(Node node : graph.nodes.values()) {
            if(node.in == 0) {
                zeroQueue.add(node);
            }
            inMap.put(node, node.in);
        }
        
        while(!zeroQueue.isEmpty()) {
            Node cur = zeroQueue.remove();
            res.add(cur);
            
            for(Node next : cur.nexts) {    
                inMap.put(next, inMap.get(next)-1);
                if(inMap.get(next) == 0) {
                    zeroQueue.add(next);
                }
            }
        }
        
        return res;
    }
}
​
最小生成树
k算法

排序边,从小的边开始选,选的所有边不能连成环

kruskal:

选边,一直选小的边但保证不连成回路。自定义一个结构map,初始每个点对应只含有自己的集合,再选边时看此边的from和to是否是同一个集合,如果是同一个集合说明这条边将构成回路。选不构成回路的边,再将该边的两个集合合并成一个集合。选边时通过所有节点获取到这个结构,将所有边加入到优先级队列。不断弹出判断最后返回一个边集。选边就可能出现一堆边的图与另一堆边构成的图合并,所以需要集合的合并。

package com.wtp.图.最小生成树;
​
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.PriorityQueue;
import java.util.Set;
​
import com.wtp.图.图结构.Edge;
import com.wtp.图.图结构.Graph;
import com.wtp.图.图结构.Node;
​
public class k算法 {
​
    public static class mySet{//用于判断加入某个点后是否形成环
        
        public HashMap<Node,List<Node>> map;
        
        public mySet(Collection<Node> collection) {
            map = new HashMap<>();
            
            //初始化 初始每个点都各自为一个集合
            for(Node node : collection) {
                List<Node> list = new ArrayList<>();
                list.add(node);
                map.put(node, list);
            }
        }
        
        //判断两个节点是否在同一个集合中
        public boolean isSameSet(Node from,Node to) {//时间复杂度O(1)
            return map.get(from) == map.get(to);
        }
        
        //将两个点所在的集合合并 
        public void union(Node from,Node to) {//集合的合并用的是遍历的方法时间复杂度O(N)可用并查集结构优化
            List<Node> fromNodes = map.get(from);
            List<Node> toNodes = map.get(to);
            
            for(Node toNode : toNodes) {
                //将to集合中的节点加入到from集合中
                fromNodes.add(toNode);
                //将to集合中的节点指向from集合
                map.put(toNode, fromNodes);
            }
        }
    }
    
    //选边 加上边后不形成环就选
    public static Set<Edge> kruskal(Graph graph){
        mySet unionFind = new mySet(graph.nodes.values());
        
        PriorityQueue<Edge> priorityQueue = 
                new PriorityQueue<>((o1,o2) -> o1.weight - o2.weight);
        for(Edge edge : graph.edges) {
            priorityQueue.add(edge);
        }
        
        HashSet<Edge> res = new HashSet<>();
        while(!priorityQueue.isEmpty()) {
            Edge edge = priorityQueue.remove();
            if(!unionFind.isSameSet(edge.from, edge.to)) {
                res.add(edge);
                unionFind.union(edge.from, edge.to);
            }
        }
        
        return res;
    }
}
​
p算法

选节点,节点选完后解锁边,在根据节点所有已解锁的边中选一个最小且没有解锁新节点的,加入新节点的边,直到选择完所有点

prim: 选节点,从所有节点中选取一个,如果是联通图一个其中任意一个节点都能获取到最小生成树,但如果是一片森林就需要遍历所有节点。从所有节点中获取一个节点,如果没有被访问过就添加这个新节点所连的边(新解锁的边)到优先级队列。优先级队列不为空时一直弹出一条边,如果当前边所指向的顶点没有被访问过,那将这个顶点所连边加入到优先级队列。(可能会重复加入边,但判断的时候都能判断是否访问过)

package com.wtp.图.最小生成树;
​
import java.util.HashSet;
import java.util.PriorityQueue;
import java.util.Set;
​
import com.wtp.图.图结构.Edge;
import com.wtp.图.图结构.Graph;
import com.wtp.图.图结构.Node;
​
public class p算法 {
​
    public static Set<Edge> prim(Graph graph){
        
        //用来标记解锁的点是否出现过
        HashSet<Node> set = new HashSet<>();
        
        PriorityQueue<Edge> priorityQueue = 
                new PriorityQueue<>((o1,o2) -> o1.weight - o2.weight);
        
        Set<Edge> res = 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.remove();
                    if(!set.contains(edge.to)) {//这个边的点没有遇到过
                        res.add(edge);
                        set.add(edge.to);
                        
                        //加入新点新解锁的边
                        for(Edge nextEdge : edge.to.edges) {
                            priorityQueue.add(nextEdge);
                        }
                    }
                }
            }
        }
        
        return res;
    }
}
​

过程中可能有重复加入的边,但重复的边的节点已经结算,所以不影响结论

最短路径

dijkstra:从一个点出发,到其他顶点,找一个从头节点到当前节点最短的节点,遍历该节点的所有边更新到其他顶点的最短距离

dijkstra(可以有权值为负数的边,不能有累加和为负数的环): 建立一个map(Node-Integer):key-从head出发到次node value-head到key的最小距离(如果表中没有点代表正无穷大) set记录点有没有访问过,在所有没走过的节点中选一个目前路径最小的出发点minNode。 到新出发点的距离为map.get(minNode) 如果出发点不为null就从这个点拿边,根据边的toNode节点更新信息:若toNode不在map中就添加,距离为走到这个点的距离加上边的长度。 若在map中就更新距离为原先到达toNode的距离和通过当前minNode的边加到minNode的距离取一个小的作为新路径 遍历完minNode的所有边后记录该节点已访问继续获取下一个minNode

package com.wtp.图.最短路径;
​
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
​
import com.wtp.图.图结构.Edge;
import com.wtp.图.图结构.Node;
​
public class d {
​
    public static Map<Node,Integer> dijkstra1(Node head){
        
        //从head点到node点的距离为value
        Map<Node,Integer> distanceMap = new HashMap<>();
        distanceMap.put(head, 0);
        
        //标记节点是否已经锁定
        HashSet<Node> set = new HashSet<>();
        Node minNode = getMinDistanceNode(distanceMap, set);
        
        while(minNode != null) {
            int distance = distanceMap.get(minNode);
            for(Edge edge : minNode.edges) {
                Node toNode = edge.to;
                //如果不存在 就新增 存在就将更小的更新
                if(!distanceMap.containsKey(toNode)) {
                    distanceMap.put(toNode, edge.weight + distance);
                }else {
                    distanceMap.put(toNode,
                            Math.min(distanceMap.get(toNode), edge.weight + distance));
                }
            }
            set.add(minNode);
            minNode = getMinDistanceNode(distanceMap, set);
        }
        
        return distanceMap;
        
    }
    
    //从未锁定的节点中找一个最小距离的点
    public static Node getMinDistanceNode(Map<Node,Integer> distanceMap,Set<Node> set) {
        int min = Integer.MAX_VALUE;
        Node minNode = null;
        
        for(Map.Entry<Node,Integer> entry : distanceMap.entrySet()) {
            
            Node node = entry.getKey();
            int distance = entry.getValue();
            
            if(!set.contains(node) && distance < min) {
                min = distance;
                minNode = node;
            }
        }
        
        return minNode;
    }
}
​

  • 16
    点赞
  • 20
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值