经典图论,深度优先和广度优先,拓扑,Prim和Krukal,该来温习啦

图在生活被广泛应用,你平常听到的深度优先和广度优先算法都要建立图的基础之上.
图的相关概念很简单,这里先不给出复述,等到小编讲述到例子,一下子就豁然开朗了.

分析

我们代码中如何存储一个图呢?其实有常用的两种方式
一种是使用领接矩阵,也就是一个二维数组,数组下标是图点的表示,a[i][j]=1就表示i点和j点连通,若是为0表示不连通.
另一种是邻接表,也就是用一个一维数组表示这些图点,然后每个位置又链接出一个队列或者链表,该链存储的是与该点直接连通的点.(是直接连通,而不是间接连通,比如a-b-c,a和b是直接连通)

代码

package com.hyb.ds.;

import sun.misc.Queue;

public class Graph {
    //点
    private final int v;
    //边
    private int e;

    private final Queue<Integer>[] queues;

    public Graph(int v){
        this.v=v;
        this.e=0;
        queues= new Queue[v];
        for (int i = 0; i < v; i++) {
            queues[i]= new Queue<Integer>();
        }
    }

    public int getV(){
        return v;
    }
    public int getE(){
        return e;
    }

    public void addE(int w,int m){
        queues[w].enqueue(m);
        queues[m].enqueue(w);
        e++;
    }

    public Queue<Integer> getE(int i){
        return queues[i];
    }

}

class Test{
    public static void main(String[] args) {
        Graph graph = new Graph(13);
        graph.addE(0,1);
        graph.addE(0,2);
        graph.addE(0,6);
        graph.addE(0,5);
        graph.addE(5,3);
        graph.addE(5,4);
        graph.addE(3,4);
        graph.addE(4,6);
        graph.addE(7,8);
        graph.addE(9,10);
        graph.addE(9,11);
        graph.addE(9,12);
        graph.addE(11,12);
        DFS dfs = new DFS(graph, 0);
        System.out.println(dfs.getCount());
        System.out.println(dfs.marked(7));
        System.out.println(dfs.marked(6));
        System.out.println(dfs.marked(3));
    }
}

深度优先搜索

image.png
请看上面的无向图中,深度优先搜索主要是为了判断与0点相通的点,从0开始出发,先往6走,走过的路标记为已走过,只要6没有被自己走过,就去往6,然后6继续以这样的规则向下搜索,如果发现四周字节相连的点都走过了,就原路返回,即如果搜索到了5这个点,发现0,3,4都搜索过了,就返回原来的点,比如3,来到3发现四周再搜索过了,就继续返回到4,一直返回不断探索四周是否有未被搜索的点,如果存在就搜索.一直到最起点开始都没有可搜索的点了,就结束.

package com.hyb.ds.;

import com.hyb.ds.队列.ArrayQueue;
import sun.misc.Queue;

import java.util.Enumeration;

public class DFS {
    private boolean[] marked;
    //初始化跟顶点相通的数量
    private int count;

    public DFS(Graph graph,int first){
        this.marked=new boolean[graph.getV()];
        this.count=0;
        dfs(graph,first);
    }

    public void dfs(Graph graph,int first){
        marked[first]=true;


        Queue<Integer> e = graph.getE(first);
        Enumeration<Integer> elements = e.elements();

        while (elements.hasMoreElements()){
            Integer integer = elements.nextElement();
            if (!marked(integer)){
                dfs(graph,integer);
            }
        }

        count++;

    }

    //判断某点是否与顶点相通
    public boolean marked(int w){
        return marked[w];
    }

    public int getCount(){
        return count;
    }
}

从上面的代码可以看出,当我们使用邻接表存储图的时候,深度优先搜索的路径是,先在对应点引出的队列里搜索,如果发现一个点未被搜索,里面转为该点的队列开始搜索,而不在原队列开始搜索.
在这个图中,如果需要进行路径查找,也就是输出所有到达的节点的路径,直接再造一个数组即可.

广度优先搜索

广度优秀搜索与深度优先相反,深度优先追求,往深的地方找,也就是该点能连通,就往该点的子节点里去找.不断深入子节点的子节点去找,直到发现有一个子节点附近所有的节点都搜索过了,就原路折返继续这样的规则.
而广度优先搜索的话,是先找附近所有能连通的节点,再去往某个连通的节点里去找附近能连通的它的子节点.
也就是从上面的图里我们可以看到,如果采用了广度优先搜索:
0->5->1->2->6 5->3->4
代码实现思路:
我们可以使用一个辅助队列去实现,将当前点入队,然后当队列不为空,就弹出队列元素,第一次弹出的是当前点,所以根据这个当前点找到延伸的邻接表,不断搜索,不断入队,之后循环队列的时候就可以将刚才搜索过点,按照先进先出的原则,去寻找对应的邻接表里搜索.
这和深度优先是不一样的,你可以想象一下,深度优先是在邻接表里找到一个点后,立马去递归该点的邻接表继续寻找.而这个广度优先是先搜索某个点的邻接表里所有的点入队,然后按照先进先出的原则去搜索各个点的邻接表.

import sun.misc.Queue;

import java.util.Enumeration;

public class BFS {
    private boolean[] marked;
    private int count;
    //辅助队列
    private Queue<Integer> queues;

    public BFS(Graph graph,int first) throws InterruptedException {
        marked=new boolean[graph.getV()];
        this.count=0;
        queues=new Queue<Integer>();
        bfs(graph,first);
    }

    public void bfs(Graph graph,int first) throws InterruptedException {
        marked[first]=true;
        queues.enqueue(first);
        while (!queues.isEmpty()){
            Integer dequeue = queues.dequeue();
            Queue<Integer> e = graph.getE(dequeue);
            Enumeration<Integer> elements = e.elements();
            while (elements.hasMoreElements()){
                Integer element = elements.nextElement();
                if (!marked[element]){
                    queues.enqueue(element);
                    marked[element]=true;
                }
            }
            System.out.println("搜索点->"+dequeue);
            count++;
        }
    }

    //判断某点是否与顶点相通
    public boolean marked(int w){
        return marked[w];
    }

    public int getCount(){
        return count;
    }
}
class Test1{
    public static void main(String[] args) throws InterruptedException {
        Graph graph = new Graph(13);
        graph.addE(0,1);
        graph.addE(0,2);
        graph.addE(0,6);
        graph.addE(0,5);
        graph.addE(5,3);
        graph.addE(5,4);
        graph.addE(3,4);
        graph.addE(4,6);
        graph.addE(7,8);
        graph.addE(9,10);
        graph.addE(9,11);
        graph.addE(9,12);
        graph.addE(11,12);
        BFS dfs = new BFS(graph, 0);
        System.out.println(dfs.getCount());
        System.out.println(dfs.marked(7));
        System.out.println(dfs.marked(6));
        System.out.println(dfs.marked(3));
    }
}

畅通工程

和前面的题目一样,不过这个时候需求是判断两个城市是否连通

        Graph graph = new Graph(20);
        graph.addE(0,1);
        graph.addE(6,9);
        graph.addE(3,8);
        graph.addE(5,11);
        graph.addE(2,12);
        graph.addE(6,10);
        graph.addE(4,8);
        BFS dfs = new BFS(graph, 9);
        System.out.println(dfs.getCount());
        System.out.println(dfs.marked(8));
        System.out.println(dfs.marked(10));

有向图

上面讲解的都是无向图,也就是任意两点无论从哪一点触发都能连通,而有向图的话只能从起点连通到终点.
我们可以用上面的代码做一个简单的修改,增加一个布尔字段,如果有传入布尔值为true,则说明要创建有向图,当插入一条边的时候,就连接一条边就可以了.
image.png
image.png
image.png
同时我们可以增加一个有趣的函数:获取该图的反转图:

private Graph reverseGraph(){
        Graph graph = new Graph(v);
        for (int i = 0; i < v; i++) {
            Queue<Integer> e = getE(i);
            Enumeration<Integer> elements = e.elements();
            while (elements.hasMoreElements()){
                Integer integer = elements.nextElement();
                addE(integer,i);
            }
        }
        return graph;
    }

拓扑排序

对一个有向图,设G=(V,E),V表示的是顶点集合,E表示的是顶点间的边关系,若 Vi -> Vj存在路径,则Vi一定排在Vj之前,则我们称这样的顶点序列为拓扑序列,使之构成拓扑序列的过程叫做拓扑排序。
image.png
从上面的定义可以看出,拓扑排序能进行的前提有两点:一是有向图,二是不能弧线循环图
如上图,排序后是下图所示
image.png
构成的拓扑序列有1->3->4->5 0->3->4->5 0->2->4->5

检查环路

无论你用那种方式进行拓扑,首先都得检查是否有环路,必须要在没有环路的前提下进行拓扑

  1. 我们可以利用深度优先算法,直接让所有的节点都走一遍.
  2. 这里需要设计两个标记位,一个是大循环的标记位,何为大循环?也就是以所有节点为起点的深度优先搜索,而对应还有一个小循环标记位,该小循环标记位记录的是从起点到终点走过的路.这个小循环在回溯的时候是要撤回的.
import sun.misc.Queue;

import java.util.Enumeration;

public class DirectedCycle {
    //标记路是否走过(大循环)
    private boolean[] marked;
    //标记是否有环路
    private boolean hasCycle;
    //辅助数组,下标对应节点,值对应是否被访问过(监测环的小循环)
    private boolean[] directed;

    public DirectedCycle(Graph graph){
        int v = graph.getV();
        this.marked=new boolean[v];
        this.directed=new boolean[v];
        for (int i = 0; i < v; i++) {
            if (!marked[i]){
                dfs(graph,i);
            }
        }

    }

    private void dfs(Graph graph,int first){
        marked[first]=true;

        directed[first]=true;

        Queue<Integer> e = graph.getE(first);

        Enumeration<Integer> elements = e.elements();
        while (elements.hasMoreElements()){
            Integer integer = elements.nextElement();
            if (!marked[integer]){
                dfs(graph,integer);
            }
            //如果在小循环里发现,该点已经被访问过了,说明存在一个环
            if (directed[integer]){
                hasCycle=true;
                return;
            }
        }

        directed[first]=false;
    }


    public boolean isHasCycle(){
        return hasCycle;
    }

}

点入栈

如果该图没有环路,说明可拓扑,就是寻找可达序列,那么在这里最核心的实现在于点入栈.
也是用深度优先搜索的方式,搜索到最深点,然后在回溯的时候让点入栈,这样就得到一个连通的链路

package com.hyb.ds.;

import com.hyb.ds.二叉树.PageTree;
import com.hyb.ds.设计模式.代理模式.动态代理.TargetObj;
import sun.misc.Queue;

import java.util.Enumeration;
import java.util.List;
import java.util.Stack;

public class TopSort {
    //是否被访问过
    private boolean[] marked;
    //递归完毕后将节点反向存入栈中
    private Stack<Integer> stack;


    public TopSort(Graph graph){
        int v = graph.getV();
        marked=new boolean[v];
        stack=new Stack<>();
        //因为是有向图,所有要循环所有顶点
        for (int i = 0; i < 2; i++) {
            if (!marked[i]){
                dfs(graph,i);
            }
        }
    }

    private void dfs(Graph graph,int first){
        marked[first]=true;
        //深度优先完毕后让顶点入栈

        System.out.println("入栈点->"+first);
        Queue<Integer> e = graph.getE(first);
        Enumeration<Integer> elements = e.elements();
        while (elements.hasMoreElements()){
            flag=false;
            Integer integer = elements.nextElement();
            if (!marked[integer]){
                dfs(graph,integer);
        stack.push(first);
    }

    public Stack<Integer> getStack(){
        return stack;
    }

    public List<Stack<Integer>> list(){
        return list;
    }
}

实现拓扑

实现拓扑就是上面两步相加起来

package com.hyb.ds.;


import java.util.Stack;

public class TopoSort {
    private Stack<Integer> stack;



    public TopoSort(Graph graph){
        DirectedCycle directedCycle = new DirectedCycle(graph);
        boolean hasCycle = directedCycle.isHasCycle();
        if (!hasCycle){
            TopSort topSort = new TopSort(graph);
            stack=topSort.getStack();
            
        }
    }

    public boolean isCycle(){
        return stack==null;
    }

    public Stack<Integer> getStack(){
        return stack;
    }

}
class Test4{
    public static void main(String[] args) {
        Graph graph = new Graph(6, true);
        graph.addE(0,2);
        graph.addE(0,3);
        graph.addE(1,3);
        graph.addE(2,4);
        graph.addE(3,4);
        graph.addE(4,5);

        TopoSort topoSort = new TopoSort(graph);
        Stack<Integer> stack = topoSort.getStack();
        for (Integer i :
                stack) {
            System.out.println(i);
        }

    }
}

得到的结果是543201,但这样的结果小编觉得其实是不怎么友好的,因为得到这个序列只能说明这里某些点之间的互相连通,其实并不能从中不借助外力得到一个连通的序列的,比如在这里的0和1其实是不连通,大家都是起点,都可以连通到3而已.

升级拓扑

可以对所有能单向连通的序列输出,这些序列我们用一个list结合去存储.
在得到这些序列的关键在于,也是让所有的点进行深度优先搜索,只不过每一个节点实际上都参与了深度优先搜索,即在回溯的时候存放节点的栈要出栈,mark标记位也要返回原来的状态,让新的一次深度优先搜索不被上一次的影响.
我们在求前面的拓扑序列中,会将所有走过的点都记录下来了,最后返回到一个栈中,这其实是有些不明确的,因为我们不知道到底那两个点相通,加入这两个点同时是入度为0的起点怎么办? 所以你真实要得到所有连通的序列,只能借助手段让所有的点都进行深度优先,然后记录每次深度优先的节点路程.
当然,这也是建立在没有循环连接的情况下的.

package com.hyb.ds.;

import com.hyb.ds.二叉树.PageTree;
import com.hyb.ds.设计模式.代理模式.动态代理.TargetObj;
import sun.misc.Queue;

import java.util.ArrayList;
import java.util.Enumeration;
import java.util.List;
import java.util.Stack;

public class TopSort {
    //是否被访问过
    private boolean[] marked;
    //递归完毕后将节点反向存入栈中
    private Stack<Integer> stack;
    //存储可达路径
    private List<Stack<Integer>> list;
    //放置list被多加
    private boolean flag;

    public TopSort(Graph graph){
        int v = graph.getV();
        marked=new boolean[v];
        stack=new Stack<>();
        list=new ArrayList<>();
        //因为是有向图,所有要循环所有顶点
        for (int i = 0; i < v; i++) {
            if (!marked[i]){
                dfs(graph,i);
            }
        }
    }

    private void dfs(Graph graph,int first){
        flag=false;
        marked[first]=true;
        //深度优先完毕后让顶点入栈
        stack.push(first);
        System.out.println("入栈点->"+first);
        Queue<Integer> e = graph.getE(first);
        Enumeration<Integer> elements = e.elements();
        while (elements.hasMoreElements()){

            Integer integer = elements.nextElement();
            if (!marked[integer]){
                dfs(graph,integer);

           }
        }
        if (!flag && !stack.empty()){
            Stack<Integer> s = new Stack<>();
            for (Integer i :
                    stack) {
                s.push(i);
            }
            list.add(s);
            flag=true;
        }
        stack.pop();
        marked[first]=false;
    }

    public Stack<Integer> getStack(){
        return stack;
    }

    public List<Stack<Integer>> list(){
        return list;
    }
}

升级拓扑之后,你可以在Topo函数里也有list结合接一下就可以测试了.

最小生成树

在一个加权的图中,寻找一些路径,这些路径要求连接所有连通点,以达到路径和最小.
image.png

Prim算法

该算法时寻找最小生成树的经典算法,如上图所示,该算法的步骤是: 假如从0开始,寻找与其直接相邻的点集合,如4,7,2,6,然后在这些点集合中寻找一个与其路径最短的点,如7,然后让7纳入0的阵营,将7和0看成一个整体,比如说看成一个点,再次寻找四周直接相连的点集合,比如说除了原来的点集合外,还有5,1,这两个点与原来的4,7,2,6构成一个点集合.
在构建点集合有注意两点:

  1. 首先是从4,7,2,6这个点集合中寻找到与0相连最近的点7后,要将7从点集合里删除,因为7纳入0的阵营后,已经看成一个点了,就不需要看7和0之间了.
  2. 其次是当7纳入0之后,再看四周的直接相连的点,是存在4的,但是这个4已经与0相连了,也就是说在你从0寻找的时候,4已经纳入点集合里了,当你从7-0开始看的时候,就不能直接将4纳入点集合了,而是要判断7到4的路径和4到0的路径哪个小,如果是前者较小,就覆盖原值,如果前者较大,就不用覆盖.

代码思路:

  1. 既然是要遍历到每个连通点,那么就要求有一个循环,这个可以是个队列,因为队列比较方便一点,我们先将0入队,然后出队,用先出队的这个点寻找点集合,寻找到之后,再讲最短路径上的点纳入进来,这个纳入进来的操作就是将7放入队列中,这样子纳入的点就有了个先后顺序去寻找四周的点集合
  2. 点集合的存储在于,要有一个数组,这个数组的存储很有讲究,下标是当前点的对应点,值是这两个点之间的距离,比如0和7这两个点,放在点集合中就是下标为7,值为7和0之间的长度.
  3. 进行完一个的点集合收集后,就进入一个函数比较寻找该数组里的最小的点.

代码步骤

  1. 首先我们先构造一个对象,对象是点与点之间的边对象
public class EdgeW implements Comparable {
    private int w;
    private int v;
    private double weight;

    public EdgeW(int w, int v, double weight){
        this.w=w;
        this.v=v;
        this.weight=weight;
    }

    public double getWeight() {
        return weight;
    }

    public int getW() {
        return w;
    }

    public int getV() {
        return v;
    }

    @Override
    public int compareTo(Object o) {
        if (o instanceof EdgeW){
            EdgeW e=(EdgeW) o;
            return Double.compare(this.getWeight(), e.getWeight());
        }
        return -2;
    }

    @Override
    public boolean equals(Object obj) {
        if (obj instanceof EdgeW){
            EdgeW e=(EdgeW) obj;
            return this.v == e.v && this.w == e.w && this.weight == e.weight;
        }
        return false;
    }

    @Override
    public String toString() {

        return "["+this.w+"<->"+this.v+" weight:"+weight+"]";
    }


    public int other(int i) {
        if (i==v)
            return w;
        else
            return v;
    }
}

  1. 之后构造一个权重图:
package com.hyb.ds.;

import sun.misc.Queue;

import java.util.Enumeration;

public class WeightGraph {

    //顶点
    private int v;

    //边
    private int e;

    private Queue<EdgeW>[] edgeQueues;

    private boolean type;

    public WeightGraph(int v,boolean type) {
        this.v = v;
        this.edgeQueues = new Queue[v];
        this.type = type;
        for (int i = 0; i < v; i++) {
            edgeQueues[i]=new Queue<EdgeW>();
        }
    }

    public int getV(){
        return v;
    }

    public int getW(){
        return e;
    }

    public void addEdge(EdgeW edge){
        int w = edge.getW();
        int v = edge.getV();
        edgeQueues[w].enqueue(edge);
        if (!type){
            edgeQueues[v].enqueue(edge);
        }
        e++;
    }

    //获取和顶点v关联的所有边
    public Queue<EdgeW> getEdges(int v){
        return edgeQueues[v];
    }

    //获取所有边
    public Set<EdgeW> edges(){
        HashSet<EdgeW> edgeWS = new HashSet<>();
        for (int i = 0; i < this.v; i++) {
            Queue<EdgeW> edges = getEdges(i);
            Enumeration<EdgeW> elements =
                    edges.elements();
            while (elements.hasMoreElements()){
                EdgeW edgeW = elements.nextElement();
                edgeWS.add(edgeW);
            }
        }

        return edgeWS;
    }


}

  1. 下面便是核心代码:
package com.hyb.ds.;

import sun.misc.Queue;

import java.util.ArrayList;
import java.util.Enumeration;
import java.util.List;

public class PrimMST {
    //保存现有的最短路径
    private List<EdgeW> edgeTo;
    //索引代表顶点,判断该顶点是否已经在树中
    private boolean[] marked;
    //存放切分的有效横切边,索引是与该点的最近相连的点(不是当前点),值是这个边对象
    private EdgeW[] pq;

    public PrimMST(WeightGraph graph) throws InterruptedException {
        int v = graph.getV();
        edgeTo =new ArrayList<>();
        marked=new boolean[v];
        pq =new EdgeW[v];
        visit(graph,0);
    }

    public void visit(WeightGraph graph,int v) throws InterruptedException {

        //创建分割点的队列
        Queue<Integer> queue=new Queue<>();
        //第一次让第一个点进来
        queue.enqueue(v);
        //当这个队列不为空
        while (!queue.isEmpty()){
            //吐出队列中先进入的点
            Integer dequeue = queue.dequeue();
            //标记该点已经被访问过了
            marked[dequeue]=true;
            //获取该点所有直连的点的队列
            Queue<EdgeW> edges = graph.getEdges(dequeue);
            //遍历该队列,找到与其最小路径的点
            Enumeration<EdgeW> elements = edges.elements();

            while (elements.hasMoreElements()){
                //找到某条边
                EdgeW edgeW = elements.nextElement();
                //获取当前点在这条边上对应的点
                int other = edgeW.other(dequeue);
                //如果这点已经在过去被当成dequue点了,就直接跳过
                if (marked[other])
                    continue;
                //如果这个点延伸出的边本来就在pq数组中
                if (pq[other]==null){
                    //如果other延伸出的边不在数组中,就直接加入进入
                    pq[other]=edgeW;
                }else {
                    //就比较当前other点延伸出的边比原来的是否小
                    if (edgeW.getWeight() < pq[other].getWeight()) {
                        //如果当前边的权重比本来存在的边的权重要小,就代替它
                        pq[other] = edgeW;
                    }
                }
            }
            //根据得到的pq数组中找一条最小的边
            int j = findMinW(pq);
            //回到原点就不用再入队了
            if (j!=0){
                queue.enqueue(j);
            }

        }
    }

    private int findMinW(EdgeW[] pq) {
        EdgeW x=new EdgeW(0,0,Double.MAX_VALUE);
        int j=0;
        for (int i = 1; i < pq.length; i++) {
            if (pq[i]!=null){
                if (pq[i].getWeight()<x.getWeight()){
                    x=pq[i];
                    j=i;
                }
            }
        }

        //防止回到原点的时候将list集合里的东西
        // 代替了
        if (j!=0){
            int first = x.other(j);
            edgeTo.add(new EdgeW(first,j,x.getWeight()));
            //将最小边从数组记录里移除
            pq[j]=null;
        }
        return j;
    }

    public List<EdgeW> getEdges(){
        return edgeTo;
    }
}
class Test5{
    public static void main(String[] args) throws InterruptedException {
        WeightGraph weightGraph = new WeightGraph(8, false);
        EdgeW edgeW = new EdgeW(0, 2, 0.26);
        EdgeW edgeW1 = new EdgeW(0, 7, 0.16);
        EdgeW edgeW2 = new EdgeW(0, 4, 0.38);
        EdgeW edgeW3 = new EdgeW(0, 6, 0.58);
        EdgeW edgeW4 = new EdgeW(6, 2, 0.40);
        EdgeW edgeW7 = new EdgeW(6, 3, 0.52);
        EdgeW edgeW8 = new EdgeW(6, 4, 0.93);
        EdgeW edgeW5 = new EdgeW(2, 7, 0.34);
        EdgeW edgeW6 = new EdgeW(2, 3, 0.17);
        EdgeW edgeW9 = new EdgeW(2, 1, 0.36);
        EdgeW edgeW11 = new EdgeW(7, 4, 0.37);
        EdgeW edgeW12 = new EdgeW(7, 5, 0.28);
        EdgeW edgeW13 = new EdgeW(7, 1, 0.19);
        EdgeW edgeW18 = new EdgeW(7, 2, 0.34);
        EdgeW edgeW14 = new EdgeW(5, 1, 0.32);
        EdgeW edgeW15 = new EdgeW(5, 4, 0.35);
        EdgeW edgeW16 = new EdgeW(1, 3, 0.29);
        weightGraph.addEdge(edgeW);
        weightGraph.addEdge(edgeW7);
        weightGraph.addEdge(edgeW8);
        weightGraph.addEdge(edgeW9);
        weightGraph.addEdge(edgeW11);
        weightGraph.addEdge(edgeW12);
        weightGraph.addEdge(edgeW13);
        weightGraph.addEdge(edgeW18);
        weightGraph.addEdge(edgeW14);
        weightGraph.addEdge(edgeW15);
        weightGraph.addEdge(edgeW16);
        weightGraph.addEdge(edgeW1);
        weightGraph.addEdge(edgeW2);
        weightGraph.addEdge(edgeW3);
        weightGraph.addEdge(edgeW4);
        weightGraph.addEdge(edgeW5);
        weightGraph.addEdge(edgeW6);
        PrimMST primMST = new PrimMST(weightGraph);
        List<EdgeW> edges = primMST.getEdges();
        for (EdgeW e :
                edges) {
            System.out.println(e.toString());
        }

    }
}

说明: 小编的记录数据结构与算法的步骤是参考黑马和尚硅谷的.这个最小生成树是参照前者的,本来是看着视频照做的,但是却发现黑马的视频有很多弊端,这里的最小生成树就是例子,把简单的问题复杂化了,至于怎么复杂,可以看看黑马.

Kruskal算法

这么算法小编觉得比上一个算法更加简单,这个算法是从全局上看的,也就是不断遍历该图边的集合,然后寻找最小的那条边,直到这些边连通成一棵最小生成树.
image.png
如上图所示: 我们先遍历所有的边,寻找到一条最小的边,假如是3-2,那么就将这条边纳入Set集合中.再次遍历剩下的边,假如这次7-0,就将7-0纳入Set集合中.
这里的难点在于,假如我们得到了两条边3-2和2-6纳入了集合后,那遍历的时候如果发现3-6在剩余的边里是最小的怎么办?这种情况肯定是会出现的,但是我们不能将其纳入到Set中,因为这样子就不是最小生成树了.

其实这个问题利用并查集可以很巧妙的解决这个问题,比如我们在得到3-2和2-6这两条边并放入集合后,还可以将这两边放入到并查集中,也就说明3-2-6是连通的,这个时候如果发现3-6是剩余边中最小的,就可以先判断这两点是否连通,如果是非连通,就可以纳入Set中,如果是连通的,再纳入就构不成最小生成树了

package com.hyb.ds.;

import com.hyb.ds.并查集.UF;

import java.util.*;

public class Kruskal {
    private Set<EdgeW> set;

    private UF uf;

    public Kruskal(WeightGraph graph){

        set=new HashSet<>();
        Set<EdgeW> edges = graph.edges();
        int size = edges.size();
        int v = graph.getV();
        uf=new UF(v);
        for (int i = 0; i < size; i++) {
            kruskal(edges);
        }

    }

    private void kruskal(Set<EdgeW> edges) {

        Iterator<EdgeW> iterator = edges.iterator();
        EdgeW edgeW=new EdgeW(-1,-1,Double.MAX_VALUE);
        while (iterator.hasNext()){
            EdgeW w = iterator.next();
            if (w.getWeight()<edgeW.getWeight()){
                edgeW=w;
            }
        }
        int w = edgeW.getW();
        int v = edgeW.other(w);
        if (!uf.connected(w,v)){
            uf.unioned(w,v);
            set.add(edgeW);
        }
        edges.remove(edgeW);
    }

    public Set<EdgeW> getSet(){
        return set;
    }
}
class Test6{
    public static void main(String[] args) throws InterruptedException {
        WeightGraph weightGraph = new WeightGraph(8, false);
        EdgeW edgeW = new EdgeW(0, 2, 0.26);
        EdgeW edgeW1 = new EdgeW(0, 7, 0.16);
        EdgeW edgeW2 = new EdgeW(0, 4, 0.38);
        EdgeW edgeW3 = new EdgeW(0, 6, 0.58);
        EdgeW edgeW4 = new EdgeW(6, 2, 0.40);
        EdgeW edgeW7 = new EdgeW(6, 3, 0.52);
        EdgeW edgeW8 = new EdgeW(6, 4, 0.93);
        EdgeW edgeW5 = new EdgeW(2, 7, 0.34);
        EdgeW edgeW6 = new EdgeW(2, 3, 0.17);
        EdgeW edgeW9 = new EdgeW(2, 1, 0.36);
        EdgeW edgeW11 = new EdgeW(7, 4, 0.37);
        EdgeW edgeW12 = new EdgeW(7, 5, 0.28);
        EdgeW edgeW13 = new EdgeW(7, 1, 0.19);
        EdgeW edgeW18 = new EdgeW(7, 2, 0.34);
        EdgeW edgeW14 = new EdgeW(5, 1, 0.32);
        EdgeW edgeW15 = new EdgeW(5, 4, 0.35);
        EdgeW edgeW16 = new EdgeW(1, 3, 0.29);
        weightGraph.addEdge(edgeW);
        weightGraph.addEdge(edgeW7);
        weightGraph.addEdge(edgeW8);
        weightGraph.addEdge(edgeW9);
        weightGraph.addEdge(edgeW11);
        weightGraph.addEdge(edgeW12);
        weightGraph.addEdge(edgeW13);
        weightGraph.addEdge(edgeW18);
        weightGraph.addEdge(edgeW14);
        weightGraph.addEdge(edgeW15);
        weightGraph.addEdge(edgeW16);
        weightGraph.addEdge(edgeW1);
        weightGraph.addEdge(edgeW2);
        weightGraph.addEdge(edgeW3);
        weightGraph.addEdge(edgeW4);
        weightGraph.addEdge(edgeW5);
        weightGraph.addEdge(edgeW6);
        Kruskal kruskal = new Kruskal(weightGraph);
        Set<EdgeW> set = kruskal.getSet();
        for (EdgeW next : set) {
            System.out.println(next.toString());
        }

    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值