普里姆(Prim)算法和克鲁斯卡尔(Kruskal)算法

这两种算法都是去寻找图的最小生成树的算法

普里姆(Prim)算法

思想:这个算法思路有些像DFS遍历。不停的寻找已经被包括进生成树的点所有到未被包括进生成树的点的最小权值,找到则将这个点包入生成树。因为刚好把所有点包进去没有多余操作,所以是最小生成树。

算法实现;

package Prim;

import com.sun.corba.se.impl.orbutil.graph.Graph;
import com.sun.org.apache.xerces.internal.util.SynchronizedSymbolTable;

public class Prim {
    public static void main(String[] args){
        int[][] graph=new int[][]{{0,6,1,5,65535,65535},
                {6,0,5,65535,3,65535},
                {1,5,0,5,6,4},
                {5,0,5,0,65535,2},
                {65535,3,6,65535,0,6},
                {65535,65535,4,2,6,0}};
        MinTree minTree=new MinTree();
        minTree.createMinTree(graph);
    }

}



class MinTree{
    public void createMinTree(int[][] graph) {

        int i, j,k=0 ,m;
        int[] lowcost = new int[6];//建立一个存储已被包入当前生成树的所有可达路径的权值
        int[] adjvex = new int[6];//用来记录可达路径权值的起始点,用于输出路径
        //接下来初始化第一个点
        lowcost[0] = 0;//我们要将所有访问过的节点包括设为0,以至于不会被访问到
        adjvex[0] = 0;//我们设置第一个顶点为0节点
        //将第一行的数据存入lowcost中
        for (i = 0; i < 6; i++) {
            lowcost[i] = graph[0][i];//初始化到所有点的权值,访问不到的是65535,以后会有新的点来更新这个数据
            adjvex[i] = 0;//先设置所有点都是从vo发出的
        }

        /**
         * 先选出当前可达点的权值最小的点,并加入生成树
         * **/
        for (i = 1; i < 6; i++) {//需要连接几个点就遍历几次
            int min = 65535;//定义在这里防止min拿到了最小值,之后min无法改变
            for (j = 1; j < 6; j++) {//遍历所有lowcost找到现在最小的权值
                if (lowcost[j] != 0 && lowcost[j] < min) {
                    min = lowcost[j];//将最小权值点赋给lowcost,循环结束得到最小的
                    k=j;//将最小权值点的下标值存入k
                }
            }

            System.out.println(adjvex[k]+"-"+k);
            lowcost[k]=0;//将这个点包入生成树
            /**
             * 下面的步骤:
             * 作用:因为加入了新的节点,所以更新到各个点的最小权值
             **/
            for (m=1;m<6;m++){//将新加入生成树的点的连接点的权值全部更新至lowcost
                if(graph[k][m]!=0&&graph[k][m]<lowcost[m]){//查看这个新加入点的权值是否有小于lowcost存在的权值并存入
                    lowcost[m]=graph[k][m];//赋值
                    adjvex[m]=k;
                }
            }
        }
    }
}


我觉得这个算法分为三部分:
第一部分:初始化自己设置的顶点,这个点是任意的。

  int i, j,k=0 ,m;
        int[] lowcost = new int[6];//建立一个存储已被包入当前生成树的所有可达路径的权值
        int[] adjvex = new int[6];//用来记录可达路径权值的起始点,用于输出路径
        //接下来初始化第一个点
        lowcost[0] = 0;//我们要将所有访问过的节点包括设为0,以至于不会被访问到
        adjvex[0] = 0;//我们设置第一个顶点为0节点
        //将第一行的数据存入lowcost中
        for (i = 0; i < 6; i++) {
            lowcost[i] = graph[0][i];//初始化到所有点的权值,访问不到的是65535,以后会有新的点来更新这个数据
            adjvex[i] = 0;//先设置所有点都是从vo发出的
        }

第二部分:将已经加入生成树的点在lowcost中进行最小权值遍历找出到下个最小全职点。
这里说下lowcost这个数组:lowcost的下标是各个点,而lowcost值是当前生成树里所有点到这个下标的点的最小权值,当lowcost[i]=0时,说明i点已经被访问过了。
那么如何找到是生成树中的哪个点出发的?那么就通过adjvex数组来寻找。第三部分会慢慢讲

        /**
         * 先选出当前可达点的权值最小的点,并加入生成树
         * **/
        for (i = 1; i < 6; i++) {//需要连接几个点就遍历几次
            int min = 65535;//定义在这里防止min拿到了最小值,之后min无法改变
            for (j = 1; j < 6; j++) {//遍历所有lowcost找到现在最小的权值
                if (lowcost[j] != 0 && lowcost[j] < min) {
                    min = lowcost[j];//将最小权值点赋给lowcost,循环结束得到最小的
                    k=j;//将最小权值点的下标值存入k
                }
            }

            System.out.println(adjvex[k]+"-"+k);//输出边
            lowcost[k]=0;//将这个点包入生成树

第三部分:第二部分每次执行完都会寻找权值最小边的尾点加入,那么此时最小生成树又多了一个点,我们需要将这个点所能到达且未被加入生成树的点的权值(这个权值小于之前到某点的权值,大于就不保存进去)更新进入lowcost数组(),方便下一次查找最小权值边。

            * 下面的步骤:
             * 作用:因为加入了新的节点,所以更新到各个点的最小权值
             **/
            for (m=1;m<6;m++){//将新加入生成树的点的连接点的权值全部更新至lowcost
                if(graph[k][m]!=0&&graph[k][m]<lowcost[m]){//查看这个新加入点的权值是否有小于lowcost存在的权值并存入
                    lowcost[m]=graph[k][m];//赋值
                    adjvex[m]=k;
                }

当更新后,到某个点最小权值就是由这个当前操作点发出的。那么我们把这个点的下标赋值给k,m是当前操作点能到达且是当前生成树中最近到达的点的下标。这样存储当我们需要时就能直接通过adjvex[某点]得出他是哪个点发出的。如:
adjvex={0,5}  那么下标v1这个点就是由v5发出的。



克鲁斯卡尔(Kruskal算法)

算法思想:通过按顺序从小到大寻找最短边,每次要进行判断是否会形成环,直接最后结束,变成生成最小生成树。

算法实现:

 
 
package kruskal;
/*

* 算法思路:通过边为目标构建最小生成树 * 算法过程:建立一个边集数组由小到大的存储边的两个顶点和权值,通过顺序遍历能按大小找到所有边,并进行输出 * 算法问题:当生成多个边时可能会造成环 * 解决思路:我们通过一个数组,在起点下标处存他的终点下标,这样就相当于有了顺序,当某个起点到达它相连的最后一个点时,这 * 个点和终点所到达的点是同一个点,那么这条边会构成环相同。 * 其实把这个生成树连起来的过程像是连一个有向图的过程,因为每次都要看这个点是否有下一个连接点 *

*/ public class KruskalMain { public static void main(String[] args){ //建立一个边集数组,按权值顺序存储 Edge[] edge=new Edge[15]; for(int i=0;i<15;i++) edge[i] =new Edge(); edge[0].begin=4; edge[0].end=7; edge[0].weight=7; edge[1].begin=2; edge[1].end=8; edge[1].weight=8; edge[2].begin=0; edge[2].end=1; edge[2].weight=10; edge[3].begin=0; edge[3].end=5; edge[3].weight=11; edge[4].begin=1; edge[4].end=8; edge[4].weight=12; edge[5].begin=3; edge[5].end=7; edge[5].weight=16; edge[6].begin=1; edge[6].end=6; edge[6].weight=16; edge[7].begin=5; edge[7].end=6; edge[7].weight=17; edge[8].begin=1; edge[8].end=2; edge[8].weight=18; edge[9].begin=6; edge[9].end=7; edge[9].weight=19; edge[10].begin=3; edge[10].end=4; edge[10].weight=20; edge[11].begin=3; edge[11].end=8; edge[11].weight=21; edge[12].begin=2; edge[12].end=3; edge[12].weight=22; edge[13].begin=3; edge[13].end=6; edge[13].weight=24; edge[14].begin=4; edge[14].end=5; edge[14].weight=26; Kruskal kruskal=new Kruskal(); kruskal.kruskalMethod(edge); } } //边集数组类 class Edge { int begin; int end; int weight; } class Kruskal{ public void kruskalMethod(Edge[] edge){ int i,j; int m,n;//m存放起点开始下标最后一个点,n存终点开始下标最后一个点。 int[] parent =new int[15]; //建立一个数组,专门存储下一个点的下标,用来判断是否形成回路 for(i=0;i<15;i++) //将parent中所有下标赋值为0,这样表示他们一开始都还没有相连的点 parent[i]=0; for(i=0;i<15;i++) {//遍历每条边,将他们放入生成树 /* * 判断是否形成环 */ //1.得到这个点的最后一个无相连的点 m=find(parent,edge[i].begin); n=find(parent,edge[i].end); //2.判断起点的最后一个点和终点的最后一个点是否相同,相同则为环,不相同把n放入m的下标,打印这条边 if(m!=n){ parent[m]=n; System.out.println(edge[i].begin+"-"+edge[i].end+"="+edge[i].weight);//输出起点和终点的边 } } } public int find(int[] parent,int f){
//判断下一个点是否有连接,有则换到那个点,没有返回这个点
while (parent[f]>0){ f=parent[f]; } return f;
 } 
 
}
这个算法在我的理解中分为三步
第一步:初始化

  int i,j;
      int m,n;//m存放起点开始下标最后一个点,n存终点开始下标最后一个点。
       int[] parent =new int[15];  //建立一个数组,专门存储下一个点的下标,用来判断是否形成回路
      for(i=0;i<15;i++)   //将parent中所有下标赋值为0,这样表示他们一开始都还没有相连的点
        parent[i]=0;

parent这个数组的作用是判断某个点是否还有相连的下一个点,没有的话则为0,有的话存储那个点的下标,这样做是为了判断是否生成环。

第二步:判断是否成环


      for(i=0;i<15;i++) {//遍历每条边,将他们放入生成树
         /*
         * 判断是否形成环
         */
         //1.得到这个点的最后一个无相连的点
             m=find(parent,edge[i].begin);
             n=find(parent,edge[i].end);

find函数

  public int find(int[] parent,int f){//判断下一个点是否有连接,有则换到那个点,没有返回这个点
      while (parent[f]>0){
        f=parent[f];
      }
      return f;

成环的条件:

我们通过一个数组,在起点下标处存他的终点下标,这样就相当于有了顺序,当某个起点到达它相连的最后一个点时,这
* 个点和终点所到达的点是同一个点,那么这条边会构成环相同。


这一部分就是围绕这个条件设计的

第三部分:判断是否成环,不成环输出:

 //2.判断起点的最后一个点和终点的最后一个点是否相同,相同则为环,不相同把n放入m的下标,打印这条边
          if(m!=n){
            parent[m]=n;
            System.out.println(edge[i].begin+"-"+edge[i].end+"="+edge[i].weight);//输出起点和终点的边
          }
       }

克鲁斯尔卡算法是针对边来展开的。边数少会效率很高,所以对于稀疏图更好,相反的普里姆算法在处理稠密图的能力高于克鲁斯尔卡算法。

猜你喜欢

转载自blog.csdn.net/Sunmeok/article/details/80774810