算法-图

一、如何表达图,生成图?

package FaceQuestion.;

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 FaceQuestion.;

/**
 * 图的边类
 */
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 FaceQuestion.;

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 FaceQuestion.;


/**
 * 图的生成器
 */
public class GraphGenerator {

    /**
     * 图的生成方法
     * @param matrix 表示一条边的二维数组
     * @return 一张图
     */
    public static Graph createGraph(Integer[][] matrix){
        Graph graph=new Graph();
        for (int i=0;i<matrix.length;i++){
            Integer from=matrix[i][0];//边的入节点的key            Integer to=matrix[i][1];//边的出节点的key            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.nexts.add(toNode);
            fromNode.out++;
            toNode.in++;
            fromNode.edges.add(newEdge);
            graph.edges.add(newEdge);
        }
        return graph;
    }
}

二、宽度优先遍历

1,利用队列实现

2,从源节点开始依次按照宽度进队列,然后弹出

3,每弹出一个点,把该节点所有没有进过队列的邻接点放入队列

4,直到队列变空

package FaceQuestion.;


import java.util.HashSet;
import java.util.LinkedList;
import java.util.Queue;

/**
 * 图的宽度优先搜索算法
 * 其邻居节点尚未被检验过的节点会被放置在一个被称为 open 的容器中(例如队列或是链表),
 * 而被检验过的节点则被放置在被称为 closed 的容器中。(open-closed表)
 * 1,利用队列实现
 * 2,从源节点开始依次按照宽度进队列,然后弹出
 * 3,每弹出一个点,把该节点所有没有进过队列的邻接点放入队列
 * 4,直到队列变空
 */
public class BFS {


    /**
     * 宽度优先搜索
     * @param node 当前节点
     */
    public void bfs(Node node){
        if (node==null){
            return;
        }
        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){
                //循环遍历当前节点的邻居节点
                if (!map.contains(next)){
                    //如果没有被遍历过
                    map.add(next);
                    queue.add(next);
                }
            }
        }
    }
}

三、深度优先搜索算法

1,利用栈实现

2,从源节点开始把节点按照深度放入栈,然后弹出

3,每弹出一个点,把该节点下一个没有进过栈的邻接点放入栈

4,直到栈变空

package FaceQuestion.;


import java.util.HashSet;
import java.util.Stack;

/**
 * 图的深度优先搜索算法
 * 1,利用栈实现
 * 2,从源节点开始把节点按照深度放入栈,然后弹出
 * 3,每弹出一个点,把该节点下一个没有进过栈的邻接点放入栈
 * 4,直到栈变空
 */
public class DFS {

    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){
                //遍历当前节点的邻居节点
                if (!set.contains(next)){
                    //只要存在一个邻居节点没有进过栈,那么就直接结束循环
                    //注意,此时,邻居节点并不会遍历完,这也是深度优先遍历的特点
                    stack.push(cur);
                    stack.push(next);
                    set.add(next);
                    System.out.println(next.value);
                    break;
                }
            }
        }
    }

}

四、拓扑排序算法
适用范围:要求有向图,且有入度为0的节点,且没有环

例子:比如有ABCDE五件事情需要去完成,A事件的完成必须依赖B和C事件的完成,而B事件要完成的话D事件必须先完成,C事件要完成那么E事件必须先完成,所以完成此事件链的排序有EDCBA或者DECBA等方法。这就是拓扑排序算法;

package FaceQuestion.;

import java.util.*;

/**
 * 图的拓扑排序算法
 * 1.遍历所有节点,找到所有入度为0 的节点
 * 2.将所有入度为0的节点的邻居节点的入度减1,那么就会产生新的入度为0的节点
 */
public class TopologySort {

    /**
     * 拓扑排序算法
     * @param graph
     * @return
     */
    public List<Node> sortedTopology(Graph graph){
        HashMap<Node,Integer> inMap=new HashMap<>();//所有节点的入度哈希表
        Queue<Node> zeroInQueue=new LinkedList<>();//所有入度为0 节点的哈希表
        for (Node node:graph.nodes.values()){
            inMap.put(node,node.in);//将所有节点的入度存储
            if (node.in==0){
                zeroInQueue.add(node);//将所有入度为0的节点存储
            }
        }
        
        List<Node> result=new ArrayList<>();//结果集
        while (!zeroInQueue.isEmpty()){
            //遍历所有入度为0 的节点的邻居节点,并将其入度减1
            Node cur=zeroInQueue.poll();
            result.add(cur);
            for (Node next:cur.nexts){
                inMap.put(next,inMap.get(next)-1);
                if (inMap.get(next)==0){
                    zeroInQueue.add(next);
                }
            }
        }
        return result;
    }
}

五、最小生成树算法

适用范围:无向图

在连通网的所有生成树中,所有边的代价和最小的生成树,称为最小生成树。

在说这个之前,需要先谈一下并查集这个概念:什么是并查集?

在一些有N个元素的集合应用问题中,我们通常是在开始时让每个元素构成一个单元素的集合,然后按一定顺序将属于同一组的元素所在的集合合并,其间要反复查找一个元素在哪个集合中。这一类问题近几年来反复出现在信息学的国际国内赛题中,其特点是看似并不复杂,但数据量极大,若用正常的数据结构来描述的话,往往在空间上过大,计算机无法承受;即使在空间上勉强通过,运行的时间复杂度也极高,根本就不可能在比赛规定的运行时间(1~3秒)内计算出试题需要的结果,只能用并查集来描述。

并查集是一种树型的数据结构,用于处理一些不相交集合(Disjoint Sets)的合并及查询问题。常常在使用中以森林来表示。

按照我的理解,并查集一开始将所有数据单独作为一个集合,它提供两种操作,第一个是判断两个数是否是同一个集合,第二个是合并两个集合;

并查集算法:

package FaceQuestion.二叉树;


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

/**
 * 并查集
 */
public class UnionFind {

    public static class Node {
        // whatever you like
    }

    public static class DisjointSets {
        //存储的是一个节点和它的父节点的对应关系
        public HashMap<Node, Node> fatherMap;
        //存储的是一个节点如过是一个集合的特征节点,那么代表的就是特征节点和集合大小的对应关系
        public HashMap<Node, Integer> rankMap;
        

        public DisjointSets() {
            fatherMap = new HashMap<Node, Node>();
            rankMap = new HashMap<Node, Integer>();
        }

        /**
         * 初始化所有元素,使得每一个元素各成一个集合
         * @param nodes
         */
        public void makeSets(List<Node> nodes) {
            fatherMap.clear();
            rankMap.clear();
            for (Node node : nodes) {
                fatherMap.put(node, node);
                rankMap.put(node, 1);
            }
        }

        /**
         * 找到一个集合的特征节点
         * @param n
         * @return
         */
        public Node findFather(Node n) {
            Node father = fatherMap.get(n);
            if (father != n) {
                father = findFather(father);
            }
            fatherMap.put(n, father);
            return father;
        }

        /**
         * 合并集合的操作
         * @param a
         * @param b
         */
        public void union(Node a, Node b) {
            if (a == null || b == null) {
                return;
            }
            Node aFather = findFather(a);
            Node bFather = findFather(b);
            if (aFather != bFather) {
                int aFrank = rankMap.get(aFather);
                int bFrank = rankMap.get(bFather);
                if (aFrank <= bFrank) {
                    fatherMap.put(aFather, bFather);
                    rankMap.put(bFather, aFrank + bFrank);
                } else {
                    fatherMap.put(bFather, aFather);
                    rankMap.put(aFather, aFrank + bFrank);
                }
            }
        }

    }

    public static void main(String[] args) {

    }
}

有了并查集,我们就可以很容易的实现Kruskal算法了

package FaceQuestion..最小生成树算法;


import FaceQuestion.二叉树.UnionFind;
import FaceQuestion..Edge;
import FaceQuestion..Graph;
import FaceQuestion..Node;

import java.util.*;

/**
 * 最小生成树Kruskal算法
 * 遍历所有边,依次选择最小权重的边,判断是否存在回路,如果不存在,那么就选择
 * 使用并查集判断是否存在回路问题,边的入节点和出节点是否在一个集合中
 */
public class Kruskal {

    // Union-Find Set
    public static class UnionFind {
        private HashMap<Node, Node> fatherMap;
        private HashMap<Node, Integer> rankMap;

        public UnionFind() {
            fatherMap = new HashMap<Node, Node>();
            rankMap = new HashMap<Node, Integer>();
        }

        private Node findFather(Node n) {
            Node father = fatherMap.get(n);
            if (father != n) {
                father = findFather(father);
            }
            fatherMap.put(n, father);
            return father;
        }

        public void makeSets(Collection<Node> nodes) {
            fatherMap.clear();
            rankMap.clear();
            for (Node node : nodes) {
                fatherMap.put(node, node);
                rankMap.put(node, 1);
            }
        }

        public boolean isSameSet(Node a, Node b) {
            return findFather(a) == findFather(b);
        }

        public void union(Node a, Node b) {
            if (a == null || b == null) {
                return;
            }
            Node aFather = findFather(a);
            Node bFather = findFather(b);
            if (aFather != bFather) {
                int aFrank = rankMap.get(aFather);
                int bFrank = rankMap.get(bFather);
                if (aFrank <= bFrank) {
                    fatherMap.put(aFather, bFather);
                    rankMap.put(bFather, aFrank + bFrank);
                } else {
                    fatherMap.put(bFather, aFather);
                    rankMap.put(aFather, aFrank + bFrank);
                }
            }
        }
    }
    public static class EdgeComparator implements Comparator<Edge> {

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

    }

    public Set<Edge> kruskalMST(Graph graph){
        UnionFind unionFind=new UnionFind();
        unionFind.makeSets(graph.nodes.values());
        //将边的权重按小根堆存储
        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();
            if (!unionFind.isSameSet(edge.from,edge.to)){
                //如果边的入节点和出节点不是同一个集合的,那么就意味着不是一个环
                //那么就可一选择这条边作为最小生成树的一个边
                result.add(edge);
                unionFind.union(edge.from,edge.to);
            }
        }
        return result;
    }

}

最小生成树还有一种算法,Prim算法,他是以节点作为观察的,从一个节点开始,选择与他连接边中的权重最小的边作为连接边;

package FaceQuestion..最小生成树算法;


import FaceQuestion..Edge;
import FaceQuestion..Graph;
import FaceQuestion..Node;

import java.util.Comparator;
import java.util.HashSet;
import java.util.PriorityQueue;
import java.util.Set;

/**
 * prim算法
 */
public class Prim {

    public static class EdgeComparator implements Comparator<Edge> {

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

    }

    public static Set<Edge> primMST(Graph graph){
        //边的小根堆
        PriorityQueue<Edge> priorityQueue=new PriorityQueue<>(new EdgeComparator());
        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){
                    //node所有边加入小根堆
                    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;
    }
}
六、Dijkstra算法

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

package FaceQuestion.;


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

/**
 * 图的最短路径算法
 * Dijkstra算法
 */
public class Dijkstra {


    public static HashMap<Node,Integer> dijkstra1(Node head){
        //所有节点对应的路径长度和
        HashMap<Node,Integer> distanceMap=new HashMap<>();
        //头结点路径为0
        distanceMap.put(head,0);
        //已经被选择过的节点集合
        HashSet<Node> selectedNodes=new HashSet<>();

        //获取当前节点下一个最短路径的节点
        Node minNode=getMinDistanceAndUnselectedNode(distanceMap,selectedNodes);

        while (minNode!=null){
            int distance=distanceMap.get(minNode);//获取当前最短路径长度
            //比较当前节点的下一个节点的权重值,找出下一个最短路径节点
            for (Edge edge:minNode.edges){
                Node toNode=edge.to;
                if (!distanceMap.containsKey(toNode)){
                    distanceMap.put(toNode,distance+edge.weight);
                }
                distanceMap.put(edge.to,Math.min(distanceMap.get(toNode),distance+edge.weight));
            }
            selectedNodes.add(minNode);
            minNode=getMinDistanceAndUnselectedNode(distanceMap,selectedNodes);
        }
        return distanceMap;
    }

    /**
     * 在未被选择的节点中选择拥有最短路径的节点
     * @param distanceMap 所有节点的集合
     * @param selectedNodes 已经选择过节点集合
     * @return
     */
    private static Node getMinDistanceAndUnselectedNode(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();
            int distance=entry.getValue();
            if (!selectedNodes.contains(node)&&distance<minDistance){
                minNode=node;
                minDistance=distance;
            }
        }

        return minNode;
    }
    
    
}

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值