图的存储方式--与相关算法

前言

图的难度,并不在于算法难度,而在于图的表现形式多样,不同的题中的图表现形式不一样.
一个好的解决思路就是,用你喜欢的一种图结构,来实现所有的图算法,然后以后遇到不同的图表现形式,写一个接口函数,将这个表现形式转换为你熟悉的图结构,在用这个结构实现该算法

图的存储方式

图的存储方式一共有俩种,邻接表和邻接矩阵俩种形式,本文主要讲述邻接表方式,接下的算法也都由邻接表实现

package Graph;

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

/*这个要补充一点说明,这个图结构是为了兼顾所有的图*/
public class GrapH {


    /*注意:这里面实现的都是有向图*/
    //key表示点的编号,Node表示实际结构
    public HashMap<Integer,Node> nodes;//
    //边集,装着所有的边
    public HashSet<Edge> edges;
    public GrapH(){
        nodes=new HashMap<Integer, Node>();
        edges=new HashSet<Edge>();
    }
}

package Graph;

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<Edge>();
    }
}

package Graph;

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;
    }
}

深度优先遍历

思路:逮着一条路,使劲走,走不通或着走完了在返回

图的深度优先遍历
* 1利用栈实现,从给定节点开始把节点按照深度依次放入栈,然后弹出
* 2每弹出一个点,就把这个节点下一个没有进过栈的临接点放入栈中
* 3直到栈空


    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)){
                     /*注意这里的细节,当邻居不在表set中,就要把原来的节点也要重新押入栈
                     * 在把邻居压入栈中,表中*/
                     stack.push(cur);
                     stack.push(next);
                     set.add(next);
                     System.out.println(next.value);
                      //输出语句同样可以换成别的数据操作语句
                     break;
                     //压入进去后,直接返回,继续查询
                 }
             }
         }
    }

广度优先遍历

图的广度优先遍历:
1利用队列实现,从给定节点依次按照宽度进队列,然后弹出
2每弹出一个点,就把该节点所有没有进过队列的临接点,放入队列
3:重复上述过程

 
    //图的广度优先遍历与二叉树的宽度优先遍历的不同点在于,图是可能有环的
    public static void BFS(Node node){
        if (node == null)return;
        Queue<Node> queue= new LinkedList<>();
        HashSet<Node> hashSet=new HashSet<>();
        //set为队列服务,保证每一个点不重复进入,避免成环
        queue.add(node);
        hashSet.add(node);

        while (!queue.isEmpty()){
            Node cur = queue.poll();

            System.out.println(cur.value);
            //这个输出语句代表你要对这个数据进行的操作

            for(Node next : cur.nexts){
                /*这个循环代表者,把这个节点的所有临接点都进去*/
                if (!hashSet.contains(next)){
                    queue.add(next);
                    hashSet.add(next);
                }
            }
        }
    }

拓扑排序算法

拓扑排序指的就是,完成一向A,需提前找到A的依赖比如说B,也就是说假如A有3个依赖BCD
如果实现A 就得 先实现BCD

实现思想
先找一个入度为0的点,记录下来,然后消除这个点的所有影响, 在重复上述过程


    public static List<Node> sortedTopolgy(GrapH graph){
        //key 某一个node
        //value 剩余的依赖
        HashMap<Node,Integer> inMap=new HashMap<Node, Integer>();
        /*入度为0的进队列*/
        Queue<Node> zeroInQueue =new LinkedList<Node>();
        for (Node node : graph.nodes.values()){
            /*将每个点都作为单独的一个集合,进入map,*/
            inMap.put(node, node.in);
            if (node.in == 0){

                zeroInQueue.add(node);
            }
        }
        List<Node> result=new LinkedList<>();
        //结果集
        while (!zeroInQueue.isEmpty()){
            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;
    }

最小生成树

给你一个图,找出一条路径得到的权值最小,这个路径就叫做最小生成树

无向图-Prim

Prim的思想为:
1:从任意一个点开始,解锁这个点引出的所有边的点
2:在这些新引出的点中,找一个权值最小的点重复上述过程
注意和K 算法区别,Prim可能出现的情况是,图不连通,K算法可能出现的情况是,形成环了

public static Set<Edge> primMST(GrapH grapH){
        //解锁的边放到小跟堆里面去
        PriorityQueue<Edge> priorityQueue=new PriorityQueue<>(new Comparator<Edge>() {
            @Override
            public int compare(Edge o1,Edge o2) {
                return o1.weight-o2.weight;
            }
        });
        //考察过的点都放入set中
        HashSet<Node> set=new HashSet<Node>();
        Set<Edge> result = new HashSet<>();
        for (Node node : grapH.nodes.values()){//防止图不是连通的情况(如果是连通的这个for就没必要写)
            //node是开始点
            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;
    }

无向图-kruskal

k算法的思路是:每次都找最小的边,然后如果这个边不形成环路的话就连接起来,如果会就不加


  public static Set<Edge> kruskalMST(GrapH grapH){
        /*unionFind 代表实现的一种并查集*//*
        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<Edge>();
      *//*形成一个结果集*//*
        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;*/
      return new HashSet<>();
    }

有向图-Dijkstra

在这里插入图片描述

这个是有向图的最小生成树,思想还是很好理解的
Dijkstra要求这个图中没有权值为负数的边,和一定要给定一个出发点
从这个出发点,解锁到所有可达到点的距离(包括通过新的点来去别的点),不可达就设置为正无穷,同时将这个使用过的点锁死不让用了
然后遍历这些点,并更新最小距离,同时将新出现的点加入进去
重复上述过程

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

public class Dijkstra {
    /*用来记录理解,B站左成云老师的算法与数据结构
    * 本类主要完成,迪杰特斯拉算法---用于有向图的最短路径算法
    * 这个可以优化,比如使用堆,但是堆也得优化*/
    public static HashMap<Node,Integer> dijkstral(Node head){
        /*从head出发到所有点的最下举例
        * key : 表示从head出发到达 key
        * value : 从 head出发 到达 key的最小距离
        * 如果在表中,没有T的记录,含义就是从head出发到T这个点的距离为正无穷*/
        HashMap<Node,Integer> distanceMap=new HashMap<Node, Integer>();

        distanceMap.put(head,0);
        //已经求过距离的节点,存在selectedNodes中,以后再也不碰
        HashSet<Node> selectNodes =new HashSet<Node>();

        Node minNode = getMinDistanceAndUnselectdeNode(distanceMap,selectNodes);
        //一开始会把头结点选出来
        //getMinDistanceAndUnselectdeNode指从distanceMap找到一个最下的距离,但这个距离节点不能是选过的
        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));
                //判断 之前的距离和现在的距离那个小入那个
            }
            selectNodes.add(minNode);//将这个点锁死不再用了
            minNode =getMinDistanceAndUnselectdeNode(distanceMap,selectNodes);
        }
        return distanceMap;
    }
    public static Node getMinDistanceAndUnselectdeNode(
            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){
                minNode =node;
                minDistance=distance;
            }
        }
        return minNode;
    }


}

改进后的dijkstra算法

改进堆

public static class NodeHeap{
        private Node[] nodes;
        private HashMap<Node,Integer> heapIndexMap;
        //heapIndexMap查node在堆上的那个位置
        private HashMap<Node,Integer> distanceMap;
        //distanceMap  node到head的最短距离的值
        private int size;//堆上有几个节点

        public NodeHeap(int size){
            nodes = new Node[size];
            heapIndexMap = new HashMap<>();
            distanceMap = new HashMap<>();
            this.size =0;
        }
        public boolean isEmpty(){
            return size == 0;
        }
        public void addOrUpadateOrIgnore(Node node,int distance){
            if (inHeap(node)){
                //如果heap 在堆上
                distanceMap.put(node,Math.min(distanceMap.get(node),distance));
                //把node 更新最小举例
                insertHeapify(node,heapIndexMap.get(node));
                //向上调整

            }
            if (!isEntered(node)){
                //如果一个节点没进过堆
                //创建节点,放入进去数组与哈希表中
                nodes[size] = node;
                heapIndexMap.put(node,size);
                distanceMap.put(node,distance);
                insertHeapify(node,size++);
                //向上调整
            }
        }
        public NodeRecord pop(){

            NodeRecord nodeRecord = new NodeRecord(nodes[0],distanceMap.get(nodes[0]));
            swap(0,size-1);//干掉堆顶
            //调整哈希表
            heapIndexMap.put(nodes[size-1],-1);
            distanceMap.remove(nodes[size-1]);
            //在堆上释放掉这个节点
            nodes[size-1] = null;
            //向下调整
            heapify(0,--size);
            return nodeRecord;
        }
        private void insertHeapify(Node node,int index){
            while (distanceMap.get(nodes[index]) < distanceMap.get(nodes[(index-1)/2])){
                swap(index,(index-1)/2);
                index = (index-1)/2;
            }
        }
        private void heapify(int index,int size){
            int left = index*2 +1;
            while (left < size){
                //选择左右孩子的俩个其中的较小值
                int smallest =left + 1 < size && distanceMap.get(nodes[left+1]) < distanceMap.get(nodes[left])
                        ? left+1 : left;
                smallest = distanceMap.get(nodes[smallest]) < distanceMap.get(index)?index:smallest;
                if (smallest == index){
                    break;
                }
                swap(smallest,index);
                index = smallest;
                left = index *2 +1;
            }
        }
        private boolean isEntered(Node node){
            //指node 进没进来过这个堆中
            return heapIndexMap.containsKey(node);
        }
        private boolean inHeap(Node node){
            //标记这个节点进来过,但不在堆上所以标记为-1
            return isEntered(node) && heapIndexMap.get(node) != -1;

        }
        private void swap(int index1 , int index2){
            /*在这个堆上俩个节点要换位置,数组要换位置,哈希表也要改*/
            heapIndexMap.put(nodes[index1],index2);
            heapIndexMap.put(nodes[index2],index1);
            Node tmp = nodes[index1];
            nodes[index1] = nodes[index2];
            nodes[index2] = tmp;
        }
    }

    public static class NodeRecord{

        public Node node;
        public int distance;//到唯一出发点head的最短距离

        public NodeRecord(Node node, int distance) {
            this.node = node;
            this.distance = distance;
        }
    }
    public static HashMap<Node,Integer> dijkstra2(Node head,int size){
        NodeHeap  nodeHeap= new NodeHeap(size);
        //堆得大小不要超过size
        nodeHeap.addOrUpadateOrIgnore(head,0);
        //到本身的距离 0
        //如果有一个点的记录是第一次出现 add,如果值更小 update 如果不小,就Ignore

        HashMap<Node,Integer> result=new HashMap<>();
        while (!nodeHeap.isEmpty()){
            NodeRecord record =nodeHeap.pop();
            /*NodeRecord 代表弹出的最小值节点*/
            Node cur = record.node;
            int distance = record.distance;
            for (Edge edge : cur.edges){
                //遍历边集来更新(除之前用过节点的)距离,
                nodeHeap.addOrUpadateOrIgnore(edge.to,edge.weight+distance);
            }
            result.put(cur,distance);
        }
        return result;
    }
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值