算法笔记---图

图的的存储方式有两种:1、邻接表,2、邻接矩阵

示例:

1、邻接表法:

A的直接邻居:C、D

B的直接邻居:C

C的直接邻居:A、B、D

D的直接邻居:A、C

用链表表示这种形式:

2、邻接矩阵法:一个二维数组,里面的数值是边的权值

接下来说几个概念

1、入度:就是有多少条边指向自己,比如上面那个简单的图:A的入度是2,B的入度1

2、出度:就是从该点出发,指出去几条边。A的出度是2,B的出度是1

无向图的出度和入度相同

代码实现:

//点集
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 graphic {
    HashMap<Integer,Node> nodes;
    HashSet<Edge> edges;

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

将题目中的图结构转化成自己熟悉的图结构

例如,题中给一个二维数组arr[[5,0,1],[3,1,2],[7,0,2]],arr[0][0]表示边的权值,arr[0][1]和arr[0][2]表示从0节点出发到1节点,然后边的权值是5。

public static Graphic createGraphic(Integer[][] matrix){
        Graphic graphic=new Graphic();
        for(int i=0;i<matrix.length;i++){
            Integer weight=matrix[i][0];
            Integer from=matrix[i][1];
            Integer to=matrix[i][2];
            if(!graphic.nodes.containsKey(from)){ //如果在图中当前from节点不存在,则新建一个节点存在hashmap中
                graphic.nodes.put(from,new Node(from));
            }
            if(!graphic.nodes.containsKey(to)){  //当前图中to节点不存在,则新创建一个
                graphic.nodes.put(to,new Node(to));
            }
            Node fromNode=graphic.nodes.get(from);
            Node toNode=graphic.nodes.get(to);
            Edge newEdge=new Edge(weight,fromNode,toNode);
            fromNode.nexts.add(toNode);
            fromNode.out++;
            toNode.in++;
            fromNode.edges.add(newEdge);
            graphic.edges.add(newEdge);
        }
        return graphic;
    }

图的遍历

1、宽度优先遍历(利用队列实现)

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

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

循环直到队列为空

public static void bfs(Node node){
        if(node==null){
            return ;
        }
        Queue<Node> queue=new LinkedList<>();
        Set<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);
                }
            }
        }
    }

2、深度优先遍历

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

每弹出一个点,检查该点的下一个节点是否在set集合中,若不在,把该点和该点的下一个节点一起再压入栈,若在,不做任何处理。

循环直到栈空

public static void dfs(Node node){
        if(node==null){
            return ;
        }
        Stack<Node> stack=new Stack<>();
        Set<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(next);
                    System.out.println(next.value);
                    break;
                }
            }

        }
    }

拓扑排序算法

拓扑结构:就是只有从前指向后的边,没有从后面指向前面的边,也就是说在一个图中没有环,类似于下面这样。

有向无环图一定是拓扑结构

有向有环图一定不是拓扑结构

无向图没有拓扑结构

拓扑排序算法(要求有向图,有入度为0的节点,无环):可以理解成Java的依赖关系,不能有循环依赖

1、找到入度为0的节点

2、把这个点以及它的影响擦掉,会出现另一个入度为0的节点

3、循环直到最后一个节点

public static List<Node> sortedTopology(Graphic graphic){
        //key:某一个node
        //value:剩余的入度
        HashMap<Node,Integer> inMap=new HashMap<>();
        //入度为0的点才能进这个队列
        Queue<Node> zeroInQueue=new LinkedList<>();
        for(Node node:graphic.nodes.values()){
            inMap.put(node,node.in);
            if(node.in==0){
                zeroInQueue.add(node);
            }
        }
        //拓扑排序的结果,依次加入result
        List<Node> result=new ArrayList<>();
        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;
    }

最小生成树算法(要求无向图)

什么是最小生成树?在一个图中,保证连通且权值最小,例如:

1、k算法(kruskal算法)

从最小的边开始找,如果没有形成环就加上,如果形成环就不加

怎么判断有没有形成环呢,初始化的时候将每个节点都放在各自集合中{A},{B},{C},{D},然后开始选最小的边,最小的边为B和D之间的边,判断B所在的集合和D所在的集合不是同一个集合,然后把B和D放在一个集合中,所以当前集合为{B,D},{A},{C},然后是A和B之间的边,所以集合为{A,B,D},{C},然后是B到C之间的边,此时集合为{A,B,C,D}。然后是A到D的边,但是A所在的集合和D所在的集合是同一个集合,所以就形成了环。以此类推,会出现最小生成树。

代码实现:

public class MySets {
    public HashMap<Node, List<Node>> setMap;

    //给每一个节点创建集合,然后把该点和该点所在的集合以map形式放入setMap中
    public MySets(List<Node> list){
        for(Node cur:list){
            List<Node> set=new ArrayList<Node>();
            set.add(cur);
            setMap.put(cur,set);
        }
    }

    //判断from节点和to节点是否在一个集合中
    public boolean isSame(Node from,Node to){
        List<Node> fromList=setMap.get(from);
        List<Node> toList=setMap.get(to);
        return fromList==toList;
    }

    //把from节点所在集合和to节点所在集合合并
    public void unionList(Node from,Node to){
        List<Node> fromList=setMap.get(from);
        List<Node> toList=setMap.get(to);
        for(Node cur:toList){
            fromList.add(cur);
            setMap.put(cur,fromList);
        }
    }

}
private static class EdgeComparator implements Comparator<Edge> {
        @Override
        public int compare(Edge o1,Edge o2) {
            return o1.weight-o2.weight;
        }

    }

    public static Set<Edge> kruskalMST(Graphic graphic){
        MySets mySets=new MySets((List<Node>) graphic.nodes.values());
        PriorityQueue<Edge> priorityQueue=new PriorityQueue<>(new EdgeComparator());
        for(Edge edge:graphic.edges){
            priorityQueue.add(edge);
        }
        Set<Edge> result=new HashSet<>();
        while(!priorityQueue.isEmpty()){
            Edge edge=priorityQueue.poll();
            if(!mySets.isSame(edge.from,edge.to)){
                result.add(edge);
                mySets.unionList(edge.from,edge.to);
            }
        }
        return result;
    }

p算法(prim算法)

public static Set<Edge> primMst(Graphic graphic){
        //解锁的边进入小根堆
        PriorityQueue<Edge> priorityQueue=new PriorityQueue<>(new EdgeComparator());
        HashSet<Node> set=new HashSet<>();//依次选的节点放进set里
        Set<Edge> result=new HashSet<>();//依次挑选的边在result里

        for(Node node:graphic.nodes.values()){ //随便挑了一个节点
            if(!set.contains(node)){ //如果set里没有这个节点,将该节点放进set中并且将该点的边放进小根堆
                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算法解决特定问题:规定一个出发点,计算这个出发点到所有点的最短距离

以这个图为例:

代码实现:

public static Node getMinDistanceAndUnselectedNode(
            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;
    }

    public static HashMap<Node,Integer> Dijkstra(Node head){
        //从head出发到所有点的最小距离
        //key:从head出发到达key
        //value:从head出发到达key的最小距离
        //如果在表中,没有T的记录,含义是从head出发到T这个点的距离为正无穷
        HashMap<Node,Integer> distanceMap=new HashMap<>();
        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;
    }

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值