最小生成树两个经典算法(Prime算法、Kruskal算法) - biaobiao88

 经典的最小生成树例子,Prime算法,具体的步骤及其注释本人均在代码中附加,请仔细阅读与品味,要求,可以熟练的打出。

 1 //Prime算法基础 
 2 #include<iostream>
 3 using namespace std;
 4 
 5 int main()
 6 {
 7     int n,m,i,j,k,min,t1,t2,t3;
 8     int e[7][7],dis[7],book[7] = {0};
 9     int inf = 99999999;
10     int count = 0,sum = 0;
11     cin >> n >> m;
12     
13     //初始化 用邻接矩阵存储 
14     for(int i = 1;i <= n;i++)
15         for(int j = 1;j <= n;j++)
16             if(i == j)
17                 e[i][j] = 0;
18             else
19                 e[i][j] = inf;
20     
21     //读入边 无向图来回都得设置权值 
22     for(int i = 1;i <= m;i++)
23     {
24         cin >> t1 >> t2 >> t3;
25         e[t1][t2] = t3;
26         e[t2][t1] = t3;
27     }
28     
29     //初始化dis数组,这里是第一个顶点到各个顶点的初始距离,因为当前生成树中只有一个顶点
30     for(int i = 1;i <= n;i++)
31         dis[i] = e[1][i];
32         
33     //Prime核心部分
34     //将第一个顶点(即1号顶点)加入生成树
35     book[1] = 1;//这里用book数组来标记一个顶点是否已经加入生成树 
36     count++;//表示生成树中已加入一个顶点 
37     while(count < n)
38     {
39         min = inf;
40         
41         //扫描找出距离当前根顶点相连接的最小权值的顶点 
42         for(int i = 1;i <= n;i++)
43         {
44             if(book[i] == 0 && min > dis[i])//如果根节点未被标记并且根节点到剩下的n-1个顶点之间有直连线(就是相连着的边) 
45             {
46                 min = dis[i];//就将这条边的权值代替之前初始化时的无穷大 ,更新min的值,min的值更新只在此for循环中有效,此for循环目的是找出连接根节点权值最小的那个顶点j,再把min放入for循环中,一直比较,直至找到最小的权值及连的顶点j 
47                 j = i;//同时将此时的顶点的编号记录在临时变量j中,以便接下来使用 
48             }
49         }
50         book[j] = 1;//将上面for循环中找到的最小权值的顶点j加入到生成树中并标记 
51         count++;//生成树中的顶点加一 
52         sum += dis[j];//将权值最小的边都累加,最终得到最优解 
53         
54         //扫描当前顶点j所有的边,再以j为中心点,更新生成树到每一个非树顶点的距离 
55         for(k = 1;k <= n;k++)
56         {
57             if(book[k] == 0 && dis[k] > e[j][k])//e[k]表示第一个加入的根顶点到顶点k之间的权值(可能直接连接也可能间接连接),e[j][k]表示的是当前根顶点作为j,到与j顶点直接相连的顶点k的权值 
58                 dis[k] = e[j][k];//此循环的目的是找出离j顶点最近的顶点 
59         }
60     }
61     cout << sum;
62     return 0;
63 }
64 /*
65 6 9
66 2 4 11
67 3 5 13
68 4 6 3
69 5 6 4
70 2 3 6
71 4 5 7
72 1 2 1
73 3 4 9
74 1 3 2
75 19
76 */

 运行结果:

 

 Kruskal算法,需要用到并查集,具体请看以下代码

  1 //Kruskal算法 
  2 #include<iostream>
  3 #include<algorithm>
  4 using namespace std;
  5 struct edge
  6 {
  7     int u;
  8     int v;
  9     int w;
 10 }e[10];//为了方便排序,这里创建了一个结构体数组来存储边的关系,数组大小根据实际情况来设置,要比m的最大值大1 
 11 int n,m;
 12 int f[7],sum = 0,countt = 0;//并查集需要用到的一些变量,f数组大小根据实际情况来设置,要比n的最大值大1 
 13 
 14 ////这个也是快速排序,也可以替代sort函数 
 15 //void quicksort(int left,int right)
 16 //{
 17 //    int i,j;
 18 //    struct edge t;
 19 //    if(left > right)
 20 //        return;
 21 //    i = left;
 22 //    j = right;
 23 //    while(i != j)
 24 //    {
 25 //        //顺序很重要,要先从右边开始找 
 26 //        while(e[j].w >= e[left].w && i < j)
 27 //            j--;
 28 //        //再从左边开始找 
 29 //        while(e[i].w <= e[left].w && i < j)
 30 //            i++;
 31 //        //交换 
 32 //        if(i < j)
 33 //        {
 34 //            t = e[i];
 35 //            e[i] = e[j];
 36 //            e[j] = t;
 37 //        }
 38 //    }
 39 //    //最终将基准数归位,将left和i互换 
 40 //    t = e[left];
 41 //    e[left] = e[i];
 42 //    e[i] = t;
 43 //    
 44 //    quicksort(left,i - 1);//继续处理左边的,这里是一个递归的过程 
 45 //    quicksort(i + 1,right);//继续处理右边的,这里是一个递归的过程 
 46 //    return;
 47 //}
 48 
 49 //并查集寻找祖先的函数 
 50 int getf(int v)
 51 {
 52     if(f[v] == v)
 53         return v;
 54     else
 55     {
 56         //这里是路径压缩 
 57         f[v] = getf(f[v]);
 58         return f[v];
 59     }
 60 }
 61 //并查集合并两个子集合的函数 
 62 int merge(int v,int u)
 63 {
 64     int t1,t2;
 65     t1 = getf(v);
 66     t2 = getf(u);
 67     if(t1 != t2)//判断两个点是否在同一个集合中 
 68     {
 69         f[t2] = t1;
 70         return 1;
 71     }
 72     return 0;
 73 }
 74 
 75 bool cmp(const edge &a,const edge &b)
 76 {
 77     return a.w < b.w;
 78 }
 79 
 80 int main()
 81 {
 82     //读入顶点个数n和边的条数m 
 83     cin >> n >> m;
 84     //读入边,这里用结构体数组来存储边的关系 
 85     for(int i = 1;i <= m;i++)
 86         cin >> e[i].u >> e[i].v >> e[i].w;
 87 //    quicksort(1,m);//按照权值从大到小对边进行快速排序
 88     //快速排序权值 
 89     sort(e + 1,e + m + 1,cmp);
 90     //并查集初始化
 91     for(int i = 1;i <= n;i++)
 92         f[i] = i;
 93     
 94     //Kruskal算法核心部分
 95     for(int i = 1;i <= m;i++)
 96     {
 97         //判断一条边的两个顶点是否已经连通,即判断是否已在同一个集合中 
 98         if(merge(e[i].u,e[i].v))//如果目前不连通,则选用这条边 
 99         {
100             countt++;//满足条件将顶点加入生成树 
101             sum += e[i].w;//路径累加和 
102         }
103         if(countt == n - 1)//当顶点均加入到生成树中时,结束循环 
104             break;
105     }
106     cout << sum << endl;
107     return 0;
108 }

运行结果:

 

 并查集例题

下面是对并查集使用的代码,并查集不太理解的可以看看下面的代码

 1 //并查集的使用 
 2 #include<iostream>
 3 using namespace std;
 4 int f[1000],n,m,k,sum;
 5 
 6 //这是找爹的递归函数,不停的去找爹,直到找到祖宗为止,即找到根节点,这个函数的目的是判断两个顶点是否属于同一个集合 
 7 int getf(int v)
 8 {
 9     if(f[v] == v)
10         return v;
11     else
12     {
13         /*这里是路径压缩,每次在函数返回的时候,顺带把同一个集合里面的元素都改为根节点的祖先编号,这样可以提高今后找到树的祖先的速度*/
14         f[v] = getf(f[v]);
15         return f[v];
16     }
17 }
18 //这里是合并两子集合的函数,在合并函数中调用getf函数,看两个顶点是否是属于同一个集合,然后再决定是否执行合并操作 
19 void merge(int v,int u)
20 {
21     int t1,t2;
22     t1 = getf(v);
23     t2 = getf(u);
24     if(t1 != t2)//根据调用getf的函数,判断两个顶点是否在同一个集合中,即是否为同一个祖先 
25     {
26         f[t2] = t1;
27     }
28 }
29 
30 int main()
31 {
32     int x,y;
33     cin >> n >> m;
34     //这里是初始化,非常的重要,数组里面存的是自己数组下标的编号就好了
35     for(int i = 1;i <= n;i++)
36         f[i] = i;
37     for(int i = 1;i <= m;i++)
38     {
39         cin >> x >> y;
40         merge(x,y);//将两个顶点传入合并集合函数中进行操作 
41     }
42     for(int i = 1;i <= n;i++)
43     {
44         if(f[i] == i)//根节点的值依旧是原先的值,即判断有几个顶点的值不变即有几个不同的集合 
45             sum++;
46     }
47     cout << sum << endl;
48     return 0;
49 }
50 /*
51 10 9
52 1 2
53 3 4
54 5 2
55 4 6
56 2 6
57 8 7
58 9 7
59 1 6
60 2 4
61 3
62 */

运行结果:

 

posted @ 2019-11-09 16:13  恶魔岛  阅读(588)  评论(0编辑  收藏  举报