经典算法题每日演练——第十六题 Kruskal算法

     这篇我们看看第二种生成树的Kruskal算法,这个算法的魅力在于我们可以打一下算法和数据结构的组合拳,很有意思的。

一:思想

    若存在M={0,1,2,3,4,5}这样6个节点,我们知道Prim算法构建生成树是从”顶点”这个角度来思考的,然后采用“贪心思想”

来一步步扩大化,最后形成整体最优解,而Kruskal算法有点意思,它是站在”边“这个角度在思考的,首先我有两个集合。

1. 顶点集合(vertexs):

     比如M集合中的每个元素都可以认为是一个独根树(是不是想到了并查集?)。

2.边集合(edges):

    对图中的每条边按照权值大小进行排序。(是不是想到了优先队列?)

好了,下面该如何操作呢?

首先:我们从edges中选出权值最小的一条边来作为生成树的一条边,然后将该边的两个顶点合并为一个新的树。

然后:我们继续从edges中选出次小的边作为生成树的第二条边,但是前提就是边的两个顶点一定是属于两个集合中,如果不是

        则剔除该边继续选下一条次小边。

最后:经过反复操作,当我们发现n个顶点的图中生成树已经有n-1边的时候,此时生成树构建完毕。

经典算法题每日演练——第十六题 Kruskal算法

经典算法题每日演练——第十六题 Kruskal算法

从图中我们还是很清楚的看到Kruskal算法构建生成树的详细过程,同时我们也看到了”并查集“和“优先队列“这两个神器

来加速我们的生成树构建。

 

二:构建

1.Build方法

这里我灌的是一些测试数据,同时在矩阵构建完毕后,将顶点信息放入并查集,同时将边的信息放入优先队列,方便我们在

做生成树的时候秒杀。

 1 #region 矩阵的构建

 2         /// <summary>

 3         /// 矩阵的构建

 4         /// </summary>

 5         public void Build()

 6         {

 7             //顶点数

 8             graph.vertexsNum = 6;

 9 

10             //边数

11             graph.edgesNum = 8;

12 

13             graph.vertexs = new int[graph.vertexsNum];

14 

15             graph.edges = new int[graph.vertexsNum, graph.vertexsNum];

16 

17             //构建二维数组

18             for (int i = 0; i < graph.vertexsNum; i++)

19             {

20                 //顶点

21                 graph.vertexs[i] = i;

22 

23                 for (int j = 0; j < graph.vertexsNum; j++)

24                 {

25                     graph.edges[i, j] = int.MaxValue;

26                 }

27             }

28 

29             graph.edges[0, 1] = graph.edges[1, 0] = 80;

30             graph.edges[0, 3] = graph.edges[3, 0] = 100;

31             graph.edges[0, 5] = graph.edges[5, 0] = 20;

32             graph.edges[1, 2] = graph.edges[2, 1] = 90;

33             graph.edges[2, 5] = graph.edges[5, 2] = 70;

34             graph.edges[4, 5] = graph.edges[5, 4] = 40;

35             graph.edges[3, 4] = graph.edges[4, 3] = 60;

36             graph.edges[2, 3] = graph.edges[3, 2] = 10;

37 

38             //优先队列,存放树中的边

39             queue = new PriorityQueue<Edge>();

40 

41             //并查集

42             set = new DisjointSet<int>(graph.vertexs);

43 

44             //将对角线读入到优先队列

45             for (int i = 0; i < graph.vertexsNum; i++)

46             {

47                 for (int j = i; j < graph.vertexsNum; j++)

48                 {

49                     //说明该边有权重

50                     if (graph.edges[i, j] != int.MaxValue)

51                     {

52                         queue.Eequeue(new Edge()

53                         {

54                             startEdge = i,

55                             endEdge = j,

56                             weight = graph.edges[i, j]

57                         }, graph.edges[i, j]);

58                     }

59                 }

60             }

61         }

62         #endregion

 

2:Kruskal算法

并查集,优先队列都有数据了,下面我们只要出队操作就行了,如果边的顶点不在一个集合中,我们将其收集作为最小生成树的一条边,

按着这样的方式,最终生成树构建完毕,怎么样,组合拳打的爽不爽?

 1 #region Kruskal算法

 2         /// <summary>

 3         /// Kruskal算法

 4         /// </summary>

 5         public List<Edge> Kruskal()

 6         {

 7             //最后收集到的最小生成树的边

 8             List<Edge> list = new List<Edge>();

 9 

10             //循环队列

11             while (queue.Count() > 0)

12             {

13                 var edge = queue.Dequeue();

14 

15                 //如果该两点是同一个集合,则剔除该集合

16                 if (set.IsSameSet(edge.t.startEdge, edge.t.endEdge))

17                     continue;

18 

19                 list.Add(edge.t);

20 

21                 //然后将startEdge 和 endEdge Union起来,表示一个集合

22                 set.Union(edge.t.startEdge, edge.t.endEdge);

23 

24                 //如果n个节点有n-1边的时候,此时生成树已经构建完毕,提前退出

25                 if (list.Count == graph.vertexsNum - 1)

26                     break;

27             }

28 

29             return list;

30         }

31         #endregion

最后是总的代码:

View Code
  1 using System;

  2 using System.Collections.Generic;

  3 using System.Linq;

  4 using System.Text;

  5 using System.Diagnostics;

  6 using System.Threading;

  7 using System.IO;

  8 using System.Threading.Tasks;

  9 

 10 namespace ConsoleApplication2

 11 {

 12     public class Program

 13     {

 14         public static void Main()

 15         {

 16             MatrixGraph graph = new MatrixGraph();

 17 

 18             graph.Build();

 19 

 20             var edges = graph.Kruskal();

 21 

 22             foreach (var edge in edges)

 23             {

 24                 Console.WriteLine("({0},{1})({2})", edge.startEdge, edge.endEdge, edge.weight);

 25             }

 26 

 27             Console.Read();

 28         }

 29     }

 30 

 31     #region 定义矩阵节点

 32     /// <summary>

 33     /// 定义矩阵节点

 34     /// </summary>

 35     public class MatrixGraph

 36     {

 37         Graph graph = new Graph();

 38 

 39         PriorityQueue<Edge> queue;

 40 

 41         DisjointSet<int> set;

 42 

 43         public class Graph

 44         {

 45             /// <summary>

 46             /// 顶点信息

 47             /// </summary>

 48             public int[] vertexs;

 49 

 50             /// <summary>

 51             /// 边的条数

 52             /// </summary>

 53             public int[,] edges;

 54 

 55             /// <summary>

 56             /// 顶点个数

 57             /// </summary>

 58             public int vertexsNum;

 59 

 60             /// <summary>

 61             /// 边的个数

 62             /// </summary>

 63             public int edgesNum;

 64         }

 65 

 66         #region 矩阵的构建

 67         /// <summary>

 68         /// 矩阵的构建

 69         /// </summary>

 70         public void Build()

 71         {

 72             //顶点数

 73             graph.vertexsNum = 6;

 74 

 75             //边数

 76             graph.edgesNum = 8;

 77 

 78             graph.vertexs = new int[graph.vertexsNum];

 79 

 80             graph.edges = new int[graph.vertexsNum, graph.vertexsNum];

 81 

 82             //构建二维数组

 83             for (int i = 0; i < graph.vertexsNum; i++)

 84             {

 85                 //顶点

 86                 graph.vertexs[i] = i;

 87 

 88                 for (int j = 0; j < graph.vertexsNum; j++)

 89                 {

 90                     graph.edges[i, j] = int.MaxValue;

 91                 }

 92             }

 93 

 94             graph.edges[0, 1] = graph.edges[1, 0] = 80;

 95             graph.edges[0, 3] = graph.edges[3, 0] = 100;

 96             graph.edges[0, 5] = graph.edges[5, 0] = 20;

 97             graph.edges[1, 2] = graph.edges[2, 1] = 90;

 98             graph.edges[2, 5] = graph.edges[5, 2] = 70;

 99             graph.edges[4, 5] = graph.edges[5, 4] = 40;

100             graph.edges[3, 4] = graph.edges[4, 3] = 60;

101             graph.edges[2, 3] = graph.edges[3, 2] = 10;

102 

103             //优先队列,存放树中的边

104             queue = new PriorityQueue<Edge>();

105 

106             //并查集

107             set = new DisjointSet<int>(graph.vertexs);

108 

109             //将对角线读入到优先队列

110             for (int i = 0; i < graph.vertexsNum; i++)

111             {

112                 for (int j = i; j < graph.vertexsNum; j++)

113                 {

114                     //说明该边有权重

115                     if (graph.edges[i, j] != int.MaxValue)

116                     {

117                         queue.Eequeue(new Edge()

118                         {

119                             startEdge = i,

120                             endEdge = j,

121                             weight = graph.edges[i, j]

122                         }, graph.edges[i, j]);

123                     }

124                 }

125             }

126         }

127         #endregion

128 

129         #region 边的信息

130         /// <summary>

131         /// 边的信息

132         /// </summary>

133         public class Edge

134         {

135             //开始边

136             public int startEdge;

137 

138             //结束边

139             public int endEdge;

140 

141             //权重

142             public int weight;

143         }

144         #endregion

145 

146         #region Kruskal算法

147         /// <summary>

148         /// Kruskal算法

149         /// </summary>

150         public List<Edge> Kruskal()

151         {

152             //最后收集到的最小生成树的边

153             List<Edge> list = new List<Edge>();

154 

155             //循环队列

156             while (queue.Count() > 0)

157             {

158                 var edge = queue.Dequeue();

159 

160                 //如果该两点是同一个集合,则剔除该集合

161                 if (set.IsSameSet(edge.t.startEdge, edge.t.endEdge))

162                     continue;

163 

164                 list.Add(edge.t);

165 

166                 //然后将startEdge 和 endEdge Union起来,表示一个集合

167                 set.Union(edge.t.startEdge, edge.t.endEdge);

168 

169                 //如果n个节点有n-1边的时候,此时生成树已经构建完毕,提前退出

170                 if (list.Count == graph.vertexsNum - 1)

171                     break;

172             }

173 

174             return list;

175         }

176         #endregion

177     }

178     #endregion

179 }

并查集:

View Code
  1 using System;

  2 using System.Collections.Generic;

  3 using System.Linq;

  4 using System.Text;

  5 

  6 namespace ConsoleApplication2

  7 {

  8     /// <summary>

  9     /// 并查集

 10     /// </summary>

 11     public class DisjointSet<T> where T : IComparable

 12     {

 13         #region 树节点

 14         /// <summary>

 15         /// 树节点

 16         /// </summary>

 17         public class Node

 18         {

 19             /// <summary>

 20             /// 父节点

 21             /// </summary>

 22             public T parent;

 23 

 24             /// <summary>

 25             /// 节点的秩

 26             /// </summary>

 27             public int rank;

 28         }

 29         #endregion

 30 

 31         Dictionary<T, Node> dic = new Dictionary<T, Node>();

 32 

 33         public DisjointSet(T[] c)

 34         {

 35             Init(c);

 36         }

 37 

 38         #region 做单一集合的初始化操作

 39         /// <summary>

 40         /// 做单一集合的初始化操作

 41         /// </summary>

 42         public void Init(T[] c)

 43         {

 44             //默认的不想交集合的父节点指向自己

 45             for (int i = 0; i < c.Length; i++)

 46             {

 47                 dic.Add(c[i], new Node()

 48                 {

 49                     parent = c[i],

 50                     rank = 0

 51                 });

 52             }

 53         }

 54         #endregion

 55 

 56         #region 判断两元素是否属于同一个集合

 57         /// <summary>

 58         /// 判断两元素是否属于同一个集合

 59         /// </summary>

 60         /// <param name="root1"></param>

 61         /// <param name="root2"></param>

 62         /// <returns></returns>

 63         public bool IsSameSet(T root1, T root2)

 64         {

 65             return Find(root1).CompareTo(Find(root2)) == 0;

 66         }

 67         #endregion

 68 

 69         #region  查找x所属的集合

 70         /// <summary>

 71         /// 查找x所属的集合

 72         /// </summary>

 73         /// <param name="x"></param>

 74         /// <returns></returns>

 75         public T Find(T x)

 76         {

 77             //如果相等,则说明已经到根节点了,返回根节点元素

 78             if (dic[x].parent.CompareTo(x) == 0)

 79                 return x;

 80 

 81             //路径压缩(回溯的时候赋值,最终的值就是上面返回的"x",也就是一条路径上全部被修改了)

 82             return dic[x].parent = Find(dic[x].parent);

 83         }

 84         #endregion

 85 

 86         #region 合并两个不相交集合

 87         /// <summary>

 88         /// 合并两个不相交集合

 89         /// </summary>

 90         /// <param name="root1"></param>

 91         /// <param name="root2"></param>

 92         /// <returns></returns>

 93         public void Union(T root1, T root2)

 94         {

 95             T x1 = Find(root1);

 96             T y1 = Find(root2);

 97 

 98             //如果根节点相同则说明是同一个集合

 99             if (x1.CompareTo(y1) == 0)

100                 return;

101 

102             //说明左集合的深度 < 右集合

103             if (dic[x1].rank < dic[y1].rank)

104             {

105                 //将左集合指向右集合

106                 dic[x1].parent = y1;

107             }

108             else

109             {

110                 //如果 秩 相等,则将 y1 并入到 x1 中,并将x1++

111                 if (dic[x1].rank == dic[y1].rank)

112                     dic[x1].rank++;

113 

114                 dic[y1].parent = x1;

115             }

116         }

117         #endregion

118     }

119 }

优先队列:

View Code
  1 using System;

  2 using System.Collections.Generic;

  3 using System.Linq;

  4 using System.Text;

  5 using System.Diagnostics;

  6 using System.Threading;

  7 using System.IO;

  8 

  9 namespace ConsoleApplication2

 10 {

 11     public class PriorityQueue<T> where T : class

 12     {

 13         /// <summary>

 14         /// 定义一个数组来存放节点

 15         /// </summary>

 16         private List<HeapNode> nodeList = new List<HeapNode>();

 17 

 18         #region 堆节点定义

 19         /// <summary>

 20         /// 堆节点定义

 21         /// </summary>

 22         public class HeapNode

 23         {

 24             /// <summary>

 25             /// 实体数据

 26             /// </summary>

 27             public T t { get; set; }

 28 

 29             /// <summary>

 30             /// 优先级别 1-10个级别 (优先级别递增)

 31             /// </summary>

 32             public int level { get; set; }

 33 

 34             public HeapNode(T t, int level)

 35             {

 36                 this.t = t;

 37                 this.level = level;

 38             }

 39 

 40             public HeapNode() { }

 41         }

 42         #endregion

 43 

 44         #region  添加操作

 45         /// <summary>

 46         /// 添加操作

 47         /// </summary>

 48         public void Eequeue(T t, int level = 1)

 49         {

 50             //将当前节点追加到堆尾

 51             nodeList.Add(new HeapNode(t, level));

 52 

 53             //如果只有一个节点,则不需要进行筛操作

 54             if (nodeList.Count == 1)

 55                 return;

 56 

 57             //获取最后一个非叶子节点

 58             int parent = nodeList.Count / 2 - 1;

 59 

 60             //堆调整

 61             UpHeapAdjust(nodeList, parent);

 62         }

 63         #endregion

 64 

 65         #region 对堆进行上滤操作,使得满足堆性质

 66         /// <summary>

 67         /// 对堆进行上滤操作,使得满足堆性质

 68         /// </summary>

 69         /// <param name="nodeList"></param>

 70         /// <param name="index">非叶子节点的之后指针(这里要注意:我们

 71         /// 的筛操作时针对非叶节点的)

 72         /// </param>

 73         public void UpHeapAdjust(List<HeapNode> nodeList, int parent)

 74         {

 75             while (parent >= 0)

 76             {

 77                 //当前index节点的左孩子

 78                 var left = 2 * parent + 1;

 79 

 80                 //当前index节点的右孩子

 81                 var right = left + 1;

 82 

 83                 //parent子节点中最大的孩子节点,方便于parent进行比较

 84                 //默认为left节点

 85                 var min = left;

 86 

 87                 //判断当前节点是否有右孩子

 88                 if (right < nodeList.Count)

 89                 {

 90                     //判断parent要比较的最大子节点

 91                     min = nodeList[left].level < nodeList[right].level ? left : right;

 92                 }

 93 

 94                 //如果parent节点大于它的某个子节点的话,此时筛操作

 95                 if (nodeList[parent].level > nodeList[min].level)

 96                 {

 97                     //子节点和父节点进行交换操作

 98                     var temp = nodeList[parent];

 99                     nodeList[parent] = nodeList[min];

100                     nodeList[min] = temp;

101 

102                     //继续进行更上一层的过滤

103                     parent = (int)Math.Ceiling(parent / 2d) - 1;

104                 }

105                 else

106                 {

107                     break;

108                 }

109             }

110         }

111         #endregion

112 

113         #region 优先队列的出队操作

114         /// <summary>

115         /// 优先队列的出队操作

116         /// </summary>

117         /// <returns></returns>

118         public HeapNode Dequeue()

119         {

120             if (nodeList.Count == 0)

121                 return null;

122 

123             //出队列操作,弹出数据头元素

124             var pop = nodeList[0];

125 

126             //用尾元素填充头元素

127             nodeList[0] = nodeList[nodeList.Count - 1];

128 

129             //删除尾节点

130             nodeList.RemoveAt(nodeList.Count - 1);

131 

132             //然后从根节点下滤堆

133             DownHeapAdjust(nodeList, 0);

134 

135             return pop;

136         }

137         #endregion

138 

139         #region  对堆进行下滤操作,使得满足堆性质

140         /// <summary>

141         /// 对堆进行下滤操作,使得满足堆性质

142         /// </summary>

143         /// <param name="nodeList"></param>

144         /// <param name="index">非叶子节点的之后指针(这里要注意:我们

145         /// 的筛操作时针对非叶节点的)

146         /// </param>

147         public void DownHeapAdjust(List<HeapNode> nodeList, int parent)

148         {

149             while (2 * parent + 1 < nodeList.Count)

150             {

151                 //当前index节点的左孩子

152                 var left = 2 * parent + 1;

153 

154                 //当前index节点的右孩子

155                 var right = left + 1;

156 

157                 //parent子节点中最大的孩子节点,方便于parent进行比较

158                 //默认为left节点

159                 var min = left;

160 

161                 //判断当前节点是否有右孩子

162                 if (right < nodeList.Count)

163                 {

164                     //判断parent要比较的最大子节点

165                     min = nodeList[left].level < nodeList[right].level ? left : right;

166                 }

167 

168                 //如果parent节点小于它的某个子节点的话,此时筛操作

169                 if (nodeList[parent].level > nodeList[min].level)

170                 {

171                     //子节点和父节点进行交换操作

172                     var temp = nodeList[parent];

173                     nodeList[parent] = nodeList[min];

174                     nodeList[min] = temp;

175 

176                     //继续进行更下一层的过滤

177                     parent = min;

178                 }

179                 else

180                 {

181                     break;

182                 }

183             }

184         }

185         #endregion

186 

187         #region 获取元素并下降到指定的level级别

188         /// <summary>

189         /// 获取元素并下降到指定的level级别

190         /// </summary>

191         /// <returns></returns>

192         public HeapNode GetAndDownPriority(int level)

193         {

194             if (nodeList.Count == 0)

195                 return null;

196 

197             //获取头元素

198             var pop = nodeList[0];

199 

200             //设置指定优先级(如果为 MinValue 则为 -- 操作)

201             nodeList[0].level = level == int.MinValue ? --nodeList[0].level : level;

202 

203             //下滤堆

204             DownHeapAdjust(nodeList, 0);

205 

206             return nodeList[0];

207         }

208         #endregion

209 

210         #region 获取元素并下降优先级

211         /// <summary>

212         /// 获取元素并下降优先级

213         /// </summary>

214         /// <returns></returns>

215         public HeapNode GetAndDownPriority()

216         {

217             //下降一个优先级

218             return GetAndDownPriority(int.MinValue);

219         }

220         #endregion

221 

222         #region 返回当前优先队列中的元素个数

223         /// <summary>

224         /// 返回当前优先队列中的元素个数

225         /// </summary>

226         /// <returns></returns>

227         public int Count()

228         {

229             return nodeList.Count;

230         }

231         #endregion

232     }

233 }

 

你可能感兴趣的:(算法)