1135. Connecting Cities With Minimum Cost

这个题目是leetcode的premuim 才能看的,网上看到别人贴出来了,学习一下。

一 题目

 原文链接:https://blog.csdn.net/Csdn_jey/article/details/97611628

There are N cities numbered from 1 to N.

You are given connections, where each connections[i] = [city1, city2, cost] represents the cost to connect city1 and city2 together. (A connection is bidirectional: connecting city1 and city2 is the same as connecting city2 and city1.)

Return the minimum cost so that for every pair of cities, there exists a path of connections (possibly of length 1) that connects those two cities together. The cost is the sum of the connection costs used. If the task is impossible, return -1.
å¨è¿éæå¥å¾çæè¿°

二 分析

真的,英语倒是不难,看完就懵逼了,这是一个图的问题。没有太多思路。大意是让我们找到一棵树,连接每一个节点,边数为n-1,妥妥的最小生成树。咋实现来? 脑子记得沈老师之前提到过,只有一个词“并查集”。其实沈老师每次在将算法的时候,都是加上口头禅,思路比结论重要,有收获就好。可是我思路没记住。

   没办法,先给自己科普下,再看实现吧。

一分钟说清楚并查集

关于最小生成树:MST(minimum spanning tree)

在一给定的无向图G = (V, E) 中,(u, v) 代表连接顶点 u 与顶点 v 的边(即),而 w(u, v) 代表此的权重,若存在 T 为 E 的子集(即)且为无循环图,使得的 w(T) 最小,则此 T 为 G 的最小生成树

百科上有有关的推论。最小生成树保证最小权值是固定的,但是最小生成树可能有多个。

这篇文章有英语版本的介绍:

Minimum Spanning Tree Property

Let G = (V,E) be a connected graph with a cost function on the edges.

Let U be a subset of V.

If (u,v) is an edge of lowest cost such that u is in U and v is in V-U, then there is a minimum spanning tree that includes (u,v).

Proof by contradiction:

Assume the contrary. Consider T, a MCST for G. According to property 2 of free trees, adding (u,v) to T introduces a cycle involving (u,v).

There must be another edge (u2,v2) in T such that u2 in U and v2 in V-U; otherwise there is no way for the cycle to leave vertices in U, enter vertices in V-U, and return without using (u,v) twice.

If we delete (u2,v2), we break the cycle and get a spanning tree T2. The cost of T2 is the cost of T - (u2,v2) + (u,v).

Since (u,v) is the least cost edge between vertices in U and V-U, the cost of T2 is less than or equal to the cost of T. This contradicts our assumption that a minimum cost spanning tree would not include (u,v).

Property 1: A free tree with N >= 1 vertices has exactly N-1 edges.

Property 2: Adding an edge to a free tree introduces a cycle.

        算法导论上有相关的证明。如果上面英语的 能看懂了,下面这段文字就可以略过了。因为很多文章一说MST,接着就说两种算法:(prim算法和kruskal算法。没有把算法的来龙去脉讲清楚。

以下引自:https://www.jianshu.com/p/916ae4663ab7

无向图G=(V,E)的一个切割(S, V-S)是节点集V的一个划分,如图所示:

显然,一个切割将图划分为两部分。如果一条边(u,v),u在S中,v在S-V中,则称边(u,v)横跨切割(S, V-S)。如果集合A中不存在横跨该切割的边,则称该切割尊重A;在横跨切割的所有边中,权重最小的边称为轻量级边(注:轻量级边可能不是唯一的)

  定理1: 设G=(V,E)是一个在边E上定义了实数值权重函数w的连通无向图,设A为E的一个子集,且A包括在图G的mst中,设(S, V-S)是图中尊重A的任意一个切割,(u, v)是横跨切割(S, V-S)的一条轻量级边,那么边(u,v)对于集合A是安全的(即为可以选择的边)。

 推论1: 设G=(V,E)是一个在边E上定义了实数值权重函数w的连通无向图,设A为E的一个子集,且A包括在图G的mst中,设C=(Vc, Ec)为森林GA=(V, A)中的一个连通分量(树)。如果(u,v)是连接C和GA中某个其它连通分量的一条轻量级边,则(u, v)对于集合A是安全的。

  根据定理1和推论1,构造最小生成树可以有两种方式,一种是切割的角度,一种是森林的角度。切割的角度:只存在一个子图,不断选择顶点(是距离子图最近的顶点,即与子图中顶点形成的边是权值最小的边。)加入到该子图中,即通过对子图进行扩张,直到形成最终的最小生成树。

森林的方式是:首先将每个节点都看成一个连通分量(此时有n个连通分量),寻找连通连接连通分量权值最小的边(u, v),(u, v)连接的连通分量为C1和C2,则(u, v)连接后,合成一个更大的连通分量C12(此时有n-1个连通分量);然后对n-1个连通分量继续实施上述类似的动作,直至最后变成一个连通分量。

切割的角度对应的是Prim算法,森林的方式对应的是Kruskal算法。

下面我们以Kruskal算法为例,进行说明:

详细的过程:

看完这个图,主要的关键点就能抽取出来,

权重边排序,(使用了优先级队列排序)

判断两点是否相通,链接 ( 使用了并查集)

其实我一开始,看完这些也没写出来,主要是并查集实在不知道怎么写,网上搜了下,所以Kruskal关键点就是它了。

 

public class MSTTest {

	 public static void main(String[] args) {
	        int numCities = 3;
	        int[][] roads = {{1, 2, 5}, {1, 3, 6}, {2, 3, 1}};
	        System.out.println(getMinCostToConstruct(numCities, roads));
	        numCities = 4;
	        int[][] roads1 = {{1, 2, 3}, {3,4, 4}};
	        System.out.println(getMinCostToConstruct(numCities, roads1));
	        
	    }
	    
	    public static int getMinCostToConstruct(int numCities,  int[][] newRoads) {
	    	//边界判断
	    	if(newRoads == null || newRoads.length<=0|| newRoads.length <(numCities-1)){
	    		return -1;
	    	}
	    	Set<Edge> edges = new HashSet<>();
	    	for (int[] road : newRoads) {
	            edges.add(new Edge(road[0], road[1], road[2]));
	        }
	        return kruskalMST(numCities, edges);
	    }

	    private static int kruskalMST(int numCities, Set<Edge> edges) {
	    	//使用优先级队列接队列实现排序
	        Queue<Edge> pq = new PriorityQueue<>(edges);
	        UF uf = new UF(numCities + 1);
	        int mstSize = 0;
	        int totalCost = 0;
	        while (!pq.isEmpty() && mstSize < numCities - 1) {
	            Edge edge = pq.poll();
	            //判断是否连接:有并查集是否同一根节点
	            if (!uf.InSameSet(edge.u, edge.v)) {
	                uf.union(edge.u, edge.v);
	                totalCost += edge.cost;
	                mstSize++;
	            }

	        }
	        return totalCost;
	    }
	}

    /**
     * 定义边
     */
	class Edge implements Comparable<Edge> {
	    int v;//点
	    int u;//点
	    int cost;//权重

	    public Edge(int v, int u, int cost) {
	        this.v = v;
	        this.u = u;
	        this.cost = cost;
	    }

	    @Override
	    public int compareTo(Edge that) {
	        return Integer.compare(this.cost, that.cost);
	    }
	}

	class UF {
	    private int[] parent;  // parent[i] = parent of i
	    private byte[] rank;   // rank[i] = rank of subtree rooted at i
        
	    //初始化
	    public UF(int n) {
	        if (n < 0) throw new IllegalArgumentException();
	        parent = new int[n];
	        rank = new byte[n];
	        for (int i = 0; i < n; i++) {
	            parent[i] = i;
	        }
	    }

	    //查找根节点
	    public int find(int p) {
	        while (p!=parent[p] ) {
	            p = parent[p];
	        }
	        return p;
	        //这里忽略压缩路径。
	    }
	    //合并
	    public void union(int p, int q) {
	        int pr = find(p);
	        int qr = find(q);
	        if (pr == qr) return;
	        //高度小的合并入高度大的(就是把节点数少的指向节点数多根)
	        if (rank[pr] < rank[qr]) {
	            parent[pr] = qr;
	        } else {
	            parent[qr] = pr;
	            if (rank[pr] == rank[qr]) rank[pr]++;
	        }
	    }
        //判断根节点是否相同,来判断节点是否相同
	    public boolean InSameSet(int p, int q) {
	        return find(p) == find(q);
	    }
	}

代码有点长,刨除并查集外,基本上思路是清晰的,沿用了上面图上的步骤。

我的感触:对于算法,不要一开始就看很难的,基础不好,说了思路也做不出来。本来我尤其害怕这些图啊、树啊之类的,硬着头皮去看。看完本篇之后,最后再来看这段介绍Kruskal's ,就有些明白了。

Kruskal's algorithm is a greedy algorithm in graph theory that finds a minimum spanning tree for a connected weighted graph. 
It finds a subset of the edges that forms a tree that includes every vertex, where the total weight of all the edges in the tree is minimized.
This algorithm is directly based on the MST( minimum spanning tree) property.
 

参考:

http://scanftree.com/Data_Structure/kruskal%27s-algorithm

https://www.jianshu.com/p/916ae4663ab7

https://www.cs.rochester.edu/u/nelson/courses/csc_173/graphs/mcst.html

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值