使用MapReduce实现定图的最小生成树

如何实现计算最小生成树

什么是最小生成树

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

简单来说,就是在一个具有N个顶点的带权连通图G中,如果存在某个子图G’,其包含了图G中的所有顶点和一部分边,且不形成回路,并且子图G’的各边权值之和最小,则称G’为图G的最小生成树。

最小生成树其实是最小权重生成树的简称

最小生成树的性质

  • 最小生成树不能有回路。
  • 最小生成树可能是一个,也可能是多个。
  • 最小生成树边的个数等于顶点的个数减一。

使用Kruskal算法实现最小生成树

克鲁斯卡尔算法的核心思想是:在带权连通图中,不断地在边集合中找到最小的边,如果该边满足得到最小生成树的条件,就将其构造,直到最后得到一颗最小生成树。

Kruskal算法简述

假设 WN=(V,{E}) 是一个含有 n 个顶点的连通网,则按照克鲁斯卡尔算法构造最小生成树的过程为:先构造一个只含 n 个顶点,而边集为空的子图,若将该子图中各个顶点看成是各棵树上的根结点,则它是一个含有 n 棵树的一个森林。之后,从网的边集 E 中选取一条权值最小的边,若该条边的两个顶点分属不同的树,则将其加入子图,也就是说,将这两个顶点分别所在的两棵树合成一棵树;反之,若该条边的两个顶点已落在同一棵树上,则不可取,而应该取下一条权值最小的边再试之。依次类推,直至森林中只有一棵树,也即子图中含有 n-1条边为止。

实现逻辑

在这里插入图片描述

  1. 将所有的边根据距离由小到大排序
  2. 先选择最小的一条边作为初始树,并且将这条边的两个点记录至Set集合
  3. 继续选择第二条边最小边(如果有两条相等的边,则随机选一条)从第二条边开始,选择时需做判断,判断规则:
    1. 将该边加入后,是否会形成闭环。如果形成闭环则直接跳过。
    2. 如果没形成闭环,就判断两个点是否在Set集合中,·出现过的则不记录,没有出现过的就新增进去,并将权值(距离)记录。
  4. 重复第3步,直到遍历完所有的边。

MapReduce实现Kruskal算法

数据格式

点1 点2 权值(距离)

在这里插入图片描述

完整代码

  • MyMapReduce.java
public class MyMapReduce {
	static double sum = 0D;
	public static void main(String[] args) {
		Configuration conf = new Configuration();
		Job job = null;
		try {
			job = Job.getInstance(conf, "MyFirstJob");
		} catch (IOException e) {
			e.printStackTrace();
		}
		//作业以jar包形式  运行
		job.setJarByClass(MyMapReduce.class);

		// InputFormat
//		Path path = new Path("/data/maillog.txt");
		Path path = new Path("F:\\IdeaProject\\kruskal\\src\\main\\resources\\10000EWG.txt");
		try {
			TextInputFormat.addInputPath(job, path);
		} catch (IOException e) {
			e.printStackTrace();
		}

		//Map
		job.setMapperClass(MyMapper.class);
		job.setMapOutputKeyClass(DoubleWritable.class);
		job.setMapOutputValueClass(Text.class);

		//shuffle 默认的方式处理 无需设置

		//reduce
		job.setReducerClass(MyReducer.class);
		job.setOutputKeyClass(Text.class);
		job.setOutputValueClass(Text.class);

		//输出目录一定不能存在,由MR动态创建
		Path out = new Path("F:\\IdeaProject\\kruskal\\src\\main\\resources\\out");
		FileSystem fileSystem = null;
		try {
			fileSystem = FileSystem.get(conf);
			fileSystem.delete(out,true);
			TextOutputFormat.setOutputPath(job,out);

			//运行job作业
			job.waitForCompletion(true);
		} catch (IOException e) {
			e.printStackTrace();
		} catch (InterruptedException e) {
			e.printStackTrace();
		} catch (ClassNotFoundException e) {
			e.printStackTrace();
		}

		System.out.println("最小树距离为:" + sum);
	}
}

  • MyMapper.java
public class MyMapper extends Mapper<LongWritable, Text, DoubleWritable, Text> {
	@Override
	public void map(LongWritable key, Text value, Context context)
			throws IOException, InterruptedException {
		Text srcDestPair = new Text();

		String[] inputTokens = value.toString().split("\\s+");
		if (inputTokens.length == 3) {
			String weight = inputTokens[2];
			// 获取两点距离
			Double wt = Double.parseDouble(weight);

			DoubleWritable iwWeight = new DoubleWritable(wt);
			// 将两个点拼接
			srcDestPair.set(inputTokens[0] + ":" + inputTokens[1]);

			// 将每行数据以(距离, 点1:点2)形式向下传输
			context.write(iwWeight, srcDestPair);
		}
	}
}
  • MyReducer.java
public class MyReducer extends Reducer<DoubleWritable, Text, Text, Text> {
	/**
	 * 保存有关同一组节点的信息树作为给定的节点,
	 * 每个节点都映射到一组节点,
	 * 其中set表示存在于同一树中的节点
	 **/
	Map<String, Set<String>> node_AssociatedSet = new HashMap<>();

	public void reduce(DoubleWritable inputKey, Iterable<Text> values,
					   Context context) throws IOException, InterruptedException {

		// 将距离转为String类型作为权重
		String strKey = new String();
		strKey += inputKey;
		Text outputKey = new Text(strKey);

		for (Text val : values) {

			// 用布尔值来循环检查两个节点是否属于同一个树,
			boolean ignoreEdgeSameSet1 = false;
			boolean ignoreEdgeSameSet2 = false;
			boolean ignoreEdgeSameSet3 = false;

			// 将所有被记录过的节点存放至Set用于后续重复判断
			Set<String> nodesSet = new HashSet<>();

			// 拆分每组节点并添加到集合中,分隔符为":"
			String[] srcDest = val.toString().split(":");

			// 获取两个节点
			String src = srcDest[0];
			String dest = srcDest[1];

			// 检查两个节点是否属于同一个树/集,如果是,则忽略
			ignoreEdgeSameSet1 = isSameSet(src, dest);

			nodesSet.add(src);
			nodesSet.add(dest);

			ignoreEdgeSameSet2 = unionSet(nodesSet, src, dest);
			ignoreEdgeSameSet3 = unionSet(nodesSet, dest, src);

			// 如果两个节点没有全部重复并且没有形成闭,则本条记录生效
			if (!ignoreEdgeSameSet1 && !ignoreEdgeSameSet2 && !ignoreEdgeSameSet3) {
				Double weight = Double.parseDouble(outputKey.toString());

				//按权重值递增计数器,计数器将保持
				//最小生成树的总权重
//                context.getCounter(MST.MSTCounters.totalWeight).increment(weight.longValue());
				MyMapReduce.sum += weight;
				// write the weight and srcDestination pair to the output
				context.write(outputKey, val);
			}
		}

	}

	/**
	 * 判断新加入的边是否和历史边形成闭环
	 * @param nodesSet
	 * @param node1
	 * @param node2
	 * @return
	 */
	private boolean unionSet(Set<String> nodesSet, String node1, String node2) {
		boolean ignoreEdge = false;

		// 判断是否包含第一个节点 如果没有就添加
		if (!node_AssociatedSet.containsKey(node1)) {
			node_AssociatedSet.put(node1, nodesSet);
		} else {

			Set<String> associatedSet = node_AssociatedSet.get(node1);
			Set<String> nodeSet = new HashSet<>();
			nodeSet.addAll(associatedSet);
			Iterator<String> nodeItr = nodeSet.iterator();
			Iterator<String> duplicateCheckItr = nodeSet.iterator();

			// 判断是否包含第二个节点
			while (duplicateCheckItr.hasNext()) {

				String node = duplicateCheckItr.next();
				if (node_AssociatedSet.get(node).contains(node2)) {
					ignoreEdge = true;
				}
			}

			// 如果associatedSet包含元素{node1,node2,…,nodeN}
			// 从node1到nodeN获取与每个元素相关联的集合
			while (nodeItr.hasNext()) {

				String nextNode = nodeItr.next();

				if (!node_AssociatedSet.containsKey(nextNode)) {

					node_AssociatedSet.put(nextNode, nodesSet);
				}

				node_AssociatedSet.get(nextNode).addAll(nodesSet);

			}
		}
		return ignoreEdge;

	}

	/**
	 * 迭代检查两个节点是否重复
	 * @param src
	 * @param dest
	 * @return
	 */
	private boolean isSameSet(String src, String dest) {
		boolean ignoreEdge = false;

		// 遍历Map
		for (Map.Entry<String, Set<String>> node_AssociatedSetValue : node_AssociatedSet
				.entrySet()) {

			Set<String> nodesInSameSet = node_AssociatedSetValue.getValue();

			// 如果两个节点都重复,则忽忽略这组节点
			if (nodesInSameSet.contains(src) && nodesInSameSet.contains(dest)) {
				ignoreEdge = true;
			}

		}

		return ignoreEdge;
	}
}
©️2020 CSDN 皮肤主题: 黑客帝国 设计师:上身试试 返回首页