图--05---贪心算法Prim算法kruskal算法

Posted 高高for 循环

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了图--05---贪心算法Prim算法kruskal算法相关的知识,希望对你有一定的参考价值。

提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档


贪心算法

贪心算法----定义:

使用切分定理找到最小生成树的一条边,不断的重复直到找到最小生成树的所有边

  • 贪心算法是计算图的最小生成树的基础算法,它的基本原理就是切分定理,
  • 使用切分定理找到最小生成树的一条边,不断的重复直到找到最小生成树的所有边。
  • 如果图有V个顶点,那么需要找到V-1条边,就可以表示该图的最小生成树。

贪心算法----原理:



最小生成树的算法

  • 计算图的最小生成树的算法有很多种,但这些算法都可以看做是贪心算法的一种特殊情况
  • 这些算法的不同之处在于保存切分和判定权重最小的横切边的方式

Prim算法

  • 我们学习第一种计算最小生成树的方法叫Prim算法,它的每一步都会为一棵生成中的树添加一条边。一开始这棵树只有一个顶点,然后会向它添加V-1条边,每次总是将下一条连接树中的顶点与不在树中的顶点且权重最小的边加入到树中。

切分规则:

  • 把最小生成树中的顶点看做是一个集合,把不在最小生成树中的顶点看做是另外一个集合。

算法API设计

Prim算法的实现原理

Prim算法始终将图中的顶点切分成两个集合,最小生成树顶点和非最小生成树顶点,通过不断的重复做某些操作,可以逐渐将非最小生成树中的顶点加入到最小生成树中,直到所有的顶点都加入到最小生成树中

  • 我们在设计API的时候,使用最小索引优先队列存放树中顶点与非树中顶点的有效横切边,那么它是如何表示的呢?我们可以让最小索引优先队列的索引值表示图的顶点,让最小索引优先队列中的值表示从其他某个顶点到当前顶点的边权重。

1. 初始化状态

  • 初始化状态,先默认0是最小生成树中的唯一顶点,其他的顶点都不在最小生成树中,此时横切边就是顶点0的邻接表中0-2,0-4,0-6,0-7这四条边,我们只需要将索引优先队列的2、4、6、7索引处分别存储这些边的权重值就可以表示了。

2. 找出权重最小的边,加入树中

现在只需要从这四条横切边中找出权重最小的边,然后把对应的顶点加进来即可。所以找到0-7这条横切边的权重最小,因此把0-7这条边添加进来,此时0和7属于最小生成树的顶点,其他的不属于,现在顶点7的邻接表中的边也成为了横切边,

这时需要做两个操作:

  • 0-7这条边已经不是横切边了,需要让它失效:只需要调用最小索引优先队列的delMin()方法即可完成;
  • 2和4顶点各有两条连接指向最小生成树,需要只保留一条:
    4-7的权重小于0-4的权重,所以保留4-7,调用索引优先队列的change(4,0.37)即可,
    0-2的权重小于2-7的权重,所以保留0-2,不需要做额外操作。

3.重复上面的动作,

  • 我们不断重复上面的动作,就可以把所有的顶点添加到最小生成树中。

辅助类

1.Edge—边

package graph.tu;

public class Edge implements Comparable<Edge> {
    private final int v;//顶点一
    private final int w;//顶点二
    private final double weight;//当前边的权重

    //通过顶点v和w,以及权重weight值构造一个边对象
    public Edge(int v, int w, double weight) {
        this.v = v;
        this.w = w;
        this.weight = weight;
    }

    //获取边的权重值
    public double weight(){
        return weight;
    }

    //获取边上的一个点
    public int either(){
        return v;
    }

    //获取边上除了顶点vertex外的另外一个顶点
    public int other(int vertex){
        if (vertex==v){
            return w;
        }else{
            return v;
        }
    }

    @Override
    public int compareTo(Edge that) {
        //使用一个遍历记录比较的结果
        int cmp;

        if (this.weight()>that.weight()){
            //如果当前边的权重值大,则让cmp=1;
            cmp = 1;
        }else if (this.weight()<that.weight()){
            //如果当前边的权重值小,则让cmp=-1;
            cmp=-1;
        }else{
            //如果当前边的权重值和that边的权重值一样大,则让cmp=0
            cmp = 0;
        }

        return cmp;
    }
}

2.EdgeWeightedGraph—加权无向图

package graph.tu;


import java.util.Queue;
import java.util.concurrent.ConcurrentLinkedQueue;

public class EdgeWeightedGraph {
    //顶点总数
    private final int V;
    //边的总数
    private int E;
    //邻接表
    private Queue<Edge>[] adj;

    //创建一个含有V个顶点的空加权无向图
    public EdgeWeightedGraph(int v) {
        //初始化顶点数量
        this.V = v;
        //初始化边的数量
        this.E = 0;
        //初始化邻接表
        this.adj = new ConcurrentLinkedQueue[v];
        for (int i = 0; i < adj.length; i++) {
            adj[i] = new ConcurrentLinkedQueue<Edge>();
        }

    }

    //获取图中顶点的数量
    public int V() {
        return V;
    }

    //获取图中边的数量
    public int E() {
        return E;
    }


    //向加权无向图中添加一条边e
    public void addEdge(Edge e) {
        //需要让边e同时出现在e这个边的两个顶点的邻接表中
        int v = e.either();
        int w = e.other(v);

        adj[v].offer(e);
        adj[w].offer(e);

        //边的数量+1
        E++;
    }

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

    //获取加权无向图的所有边
    public Queue<Edge> edges() {

        //创建一个队列对象,存储所有的边
        Queue<Edge> allEdges = new ConcurrentLinkedQueue<>();

        //遍历图中的每一个顶点,找到该顶点的邻接表,邻接表中存储了该顶点关联的每一条边

        //因为这是无向图,所以同一条边同时出现在了它关联的两个顶点的邻接表中,需要让一条边只记录一次;
        for(int v =0;v<V;v++){
            //遍历v顶点的邻接表,找到每一条和v关联的边
            for (Edge e : adj(v)) {

                if (e.other(v)<v){
                    allEdges.offer(e);
                }

            }
        }


        return allEdges;
    }
}


3.IndexMinPriorityQueue----最小优先队列

package graph.tu;

public class IndexMinPriorityQueue<T extends Comparable<T>> {
    //存储堆中的元素
    private T[] items;
    //保存每个元素在items数组中的索引,pq数组需要堆有序
    private int[] pq;
    //保存qp的逆序,pq的值作为索引,pq的索引作为值
    private int[] qp;
    //记录堆中元素的个数
    private int N;


    public IndexMinPriorityQueue(int capacity) {
        this.items = (T[]) new Comparable[capacity+1];
        this.pq = new int[capacity+1];
        this.qp= new int[capacity+1];
        this.N = 0;

        //默认情况下,队列中没有存储任何数据,让qp中的元素都为-1;
        for (int i = 0; i < qp.length; i++) {
            qp[i]=-1;
        }

    }

    //获取队列中元素的个数
    public int size() {
        return N;
    }

    //判断队列是否为空
    public boolean isEmpty() {
        return N==0;
    }

    //判断堆中索引i处的元素是否小于索引j处的元素
    private boolean less(int i, int j) {

        return items[pq[i]].compareTo(items[pq[j]])<0;
    }

    //交换堆中i索引和j索引处的值
    private void exch(int i, int j) {
        //交换pq中的数据
        int tmp = pq[i];
        pq[i] = pq[j];
        pq[j] = tmp;


        //更新qp中的数据
        qp[pq[i]]=i;
        qp[pq[j]] =j;

    }

    //判断k对应的元素是否存在
    public boolean contains(int k) {

        return qp[k] !=-1;
    }

    //最小元素关联的索引
    public int minIndex() {

        return pq[1];
    }


    //往队列中插入一个元素,并关联索引i
    public void insert(int i, T t) {
        //判断i是否已经被关联,如果已经被关联,则不让插入

        if (contains(i)){
            return;
        }
        //元素个数+1
        N++;
        //把数据存储到items对应的i位置处
        items[i] = t;
        //把i存储到pq中
        pq[N] = i;
        //通过qp来记录pq中的i
        qp[i]=N;

        //通过堆上浮完成堆的调整

        swim(N);

    }

    //删除队列中最小的元素,并返回该元素关联的索引
    public int delMin() {
        //获取最小元素关联的索引
        int minIndex = pq[1];

        //交换pq中索引1处和最大索引处的元素
        exch(1,N);
        //删除qp中对应的内容
        qp[pq[N]] = -1;
        //删除pq最大索引处的内容
        pq[N]=-1;
        //删除items中对应的内容
        items[minIndex] = null;
        //元素个数-1
        N--;
        //下沉调整
        sink(1);

        return minIndex;
    }

    //删除索引i关联的元素
    public void delete(int i) {
        //找到i在pq中的索引
        int k = qp[i];

        //交换pq中索引k处的值和索引N处的值
        exch(k,N);
        //删除qp中的内容
        qp[pq[N]] = -1;
        //删除pq中的内容
        pq[N]=-1;
        //删除items中的内容
        items[k]=null;
        //元素的数量-1
        N--;
        //堆的调整
        sink(k);
        swim(k);
    }

    //把与索引i关联的元素修改为为t
    public void changeItem(int i, T t) {
        //修改items数组中i位置的元素为t
        items[i] = t;
        //找到i在pq中出现的位置
        int k = qp[i];
        //堆调整
        sink(k);
        swim(k);
    }


    //使用上浮算法,使索引k处的元素能在堆中处于一个正确的位置
    private void swim(int k) {
        while(k>1){
            if (less(k,k/2)){
                exch(k,k/2);
            }

            k = k/2;
        }
    }


    //使用下沉算法,使索引k处的元素能在堆中处于一个正确的位置
    private void sink(int k) {
        while(2*k<=N){
            //找到子结点中的较小值
            int min;
            if (2*k+1<=N){
                if (less(2*k,2*k+1)){
                    min = 2*k;
                }else{
                    min = 2*k+1;
                }
            }else{
                min = 2*k;
            }
            //比较当前结点和较小值
            if (less(k,min)){
                break;
            }

            exch(k,min);
            k = min;
        }
    }

}


Prim算法----PrimMST

package graph.tu;

import java.util.Queue;
import java.util.concurrent.ConcurrentLinkedQueue;

public class PrimMST {
    //索引代表顶点,值表示当前顶点和最小生成树之间的最短边
    private Edge[] edgeTo;
    //索引代表顶点,值表示当前顶点和最小生成树之间的最短边的权重
    private double[] distTo;
    //索引代表顶点,如果当前顶点已经在树中,则值为true,否则为false
    private boolean[] marked;
    //存放树中顶点与非树中顶点之间的有效横切边
    private IndexMinPriorityQueue<Double> pq;

    //根据一副加权无向图,创建最小生成树计算对象
    public PrimMST(EdgeWeightedGraph G) {
        //初始化edgeTo
        this.edgeTo = new Edge[G.V()];
        //初始化distTo
        this.distTo = new double[G.V()];
        for (int i = 0; i < distTo.length; i++) {
            distTo[i] = Double.POSITIVE_INFINITY;
        }
        //初始化marked
        this.marked = new boolean[G.V()];
        //初始化pq
        pq = new IndexMinPriorityQueue<Double>(G.V());

        //默认让顶点0进入到树中,但是树中只有一个顶点0,因此,0顶点默认没有和其他的顶点相连,所以让distTo对应位置处的值存储0.0
        distTo[0] = 0.0;
        pq.insert(0,0.0);

        //遍历索引最小优先队列,拿到最小和N切边对应的顶点,把该顶点加入到最小生成树中
        while (!pq.isEmpty()){
            visit(G,pq.delMin());
        }


    }


    //将顶点v添加到最小生成树中,并且更新数据
    private void visit(EdgeWeightedGraph G, int v) {
        //把顶点v添加到最小生成树中
        marked[v] = true;
        //更新数据
        for (Edge e : G.adj(v)) {
            //获取e边的另外一个顶点(当前顶点是v)
            int w = e.other(v);
            //判断另外一个顶点是不是已经在树中,如果在树中,则不做任何处理,如果不再树中,更新数据
            if (marked[w]){
                continue;
            }


            //判断边e的权重是否小于从w顶点到树中已经存在的最小边的权重;
            if (e.weight()<distTo[w]){
                //更新数据
                edgeTo[w] = e;

                distTo[w] = e.weight();

                if (pq.contains(w)){
                    pq.changeItem(w,e.weight()最小生成树算法Kruskal算法Prim算法切分定理贪心算法

Prim算法

最小生成树算法:Kruskal算法 Prim算法

贪心算法-最小生成树Kruskal算法和Prim算法

[Python]贪心算法-Prim-和-Kruskal实现-最小生成树

prim和kruscal算法得到的最小生成树是不是一样