目录

 

 

 

 

1 生成图

图的表示

图表示有很多中,一般在面试中常见的如下:

 

代码实现

图的节点

//图的节点的定义
public class Node {
    //value的类型不一定是整型可以是泛型
    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 {
    //图就是所有点的集合和边的集合
    public HashMap<Integer,Node> nodes;
    public HashSet<Edge> edges;

    public Graph() {
        nodes = new HashMap<>();
        edges = new HashSet<>();
    }
}

生成图

public class GraphGenerator {
    //生成图
    public static Graph createGraph(Integer[][] matrix) {
        Graph graph = new Graph();
        //如博客中给出的示例,矩阵的行数不定但是列数为3即每一行的长度为3
        for (int i = 0; i < matrix.length; i++) {
            Integer from = matrix[i][0];
            Integer to = matrix[i][1];
            Integer weight = matrix[i][2];
            //如果from这个点的没有在图中定义,则建上该点
            if (!graph.nodes.containsKey(from)) {
                graph.nodes.put(from, new Node(from));
            }
            //如果to这个点的没有在图中定义,则建上该点
            if (!graph.nodes.containsKey(to)) {
                graph.nodes.put(to, new Node(to));
            }
            //拿到from点和to点
            Node fromNode = graph.nodes.get(from);
            Node toNode = graph.nodes.get(to);
            //根据两个点生成一个边
            Edge newEdge = new Edge(weight, fromNode, toNode);
            //from的邻居增加to这个点
            fromNode.nexts.add(toNode);
            //from点出度自增
            fromNode.out++;
            //to点入度自增
            toNode.in++;
            //from点的边集中增加这个边
            fromNode.edges.add(newEdge);
            //整个图的边集中增加这个边
            graph.edges.add(newEdge);
        }
        return graph;
    }
}

2 图的遍历

宽度(广度)优先遍历

 依照距离起始节点的距离确定输出的先后顺序,同等距离则可能会出现不同情况,注意图的遍历顺序是不确定的

//整体与树中的BFS很类似的,不同点在于需要注意重复元素
public static void bfs(Node node) {
    if (node == null) {
        return;
    }
    //用队列实现BFS
    Queue<Node> queue = new LinkedList<>();
    //用来记录哪些节点已经进入了
    HashSet<Node> map = new HashSet<>();
    queue.add(node);
    map.add(node);
    while (!queue.isEmpty()) {
        Node cur = queue.poll();
        System.out.println(cur.value);
        for (Node next : cur.nexts) {
            //只有没进过set集合的节点才添加,避免重复添加的问题
            if (!map.contains(next)) {
                map.add(next);
                queue.add(next);
            }
        }
    }
}

 深度优先

public static 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) {
            //如果当前节点的后代不在set中,那就把当前节点和其后代都放入栈中
            if (!set.contains(next)) {
                stack.push(cur);
                stack.push(next);
                set.add(next);
                System.out.println(next.value);
                break;
            }
        }
    }
}

3 进阶算法

1 拓扑排序算法

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

使用场景:编辑一个工程A其依赖有BCDE配置文件,配置文件内部又相互依赖,决定应该先编译哪一个文件。

算法思想:

 

代码实现:

public static List<Node> sortedTopology(Graph graph) {
    //统计当前所有节点的入度
    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);
        }
    }
    List<Node> result = new ArrayList<>();
    while (!zeroInQueue.isEmpty()) {
        //弹出一个入度为0的点,并在结果中保存
        Node cur = zeroInQueue.poll();
        result.add(cur);
        //把当前节点的所有后代入度自减1,实质就是消除该节点的影响
        for (Node next : cur.nexts) {
            inMap.put(next, inMap.get(next) - 1);
            //如果有入度为0的点进队列
            if (inMap.get(next) == 0) {
                zeroInQueue.add(next);
            }
        }
    }
    return result;
}

最小生成树算法

适用范围:无向图

使用场景:在保证图的连通性前提下,使得图的权值最小

1 Kruskal 算法

算法思想:选择权值最小的边,如下图中先选择1,然后选择2,如果选择的边构成了一个回路则舍弃,当选择的边能够遍历到所有的点时算法结束。

 

public static Set<Edge> kruskalMST(Graph graph) {
    //把所有的点放入并查集中
    UnionFind unionFind = new UnionFind();
    unionFind.makeSets(graph.nodes.values());
    //按照边的权重组成一个小根堆,堆中放的都是边
    PriorityQueue<Edge> priorityQueue = new PriorityQueue<>();
    for (Edge edge : graph.edges) {
        priorityQueue.add(edge);
    }
    //保存最后的结果
    Set<Edge> result = new HashSet<>();
    while (!priorityQueue.isEmpty()) {
        Edge edge = priorityQueue.poll();
        //每次从最小堆弹出一个边,如果边的出入节点已经属于一个集合,则舍弃该边
        if (!unionFind.isSameSet(edge.from, edge.to)) {
            //如果没有则添加该边,并把该边的出入节点合并在一起
            result.add(edge);
            unionFind.union(edge.from, edge.to);
        }
    }
    return result;
}

2 prim 算法

算法思想:从一个点出发(V1)找其发出边中最小的边,到达下一个新的节点,然后从这两个点的所有发出边中找到最小可以到达新的点的边,直至达到所有的点。

 

public static Set<Edge> primMST(Graph graph) {
    //最小堆中放边
    PriorityQueue<Edge> priorityQueue = new PriorityQueue<>();
    //保存已经遍历过的点
    HashSet<Node> set = new HashSet<>();
    //保存结果的边
    Set<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 : node.edges) {
                        priorityQueue.add(nextEdge);
                    }
                }
            }
        }
    }
    return result;
}

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

0

posted @ 2019-12-27 20:08  windy杨树  阅读(186)  评论(0编辑  收藏  举报