图的基础概念我就不去介绍了,不懂的可以去看看相关技术类书籍,讲的云里雾里的,很难懂。到最后发现,全是一堆屁话,核心还是要去了解图的算法。
我自己概括一下:
1、图就是一堆顶点和边的集合,也可以把图抽象成一个具体的类,而这个类里面有一堆顶点和边的集合。
2. 而这些边,是有方向的,开始节点,结束节点。
3. 顶点,就是边中开始节点或者结束节点,而这些节点需要持有边,只有这样,我们才能知道它有哪些边。
图的表示,有很多的表示方法。像什么邻接矩阵、邻接表、十字链表等等,各种各样奇葩的表示都有。算法并不一定难,但是这么多表示图的结构中,每一种结构都要掌握对应的算法,这个就有点烧脑了。
综上所述,我自己定义了一个适合自己版本的图的表示方法。不管你给我什么结构表示的图,我都用我自己熟悉的图的渲染方式,通过一个适配类转换成自己的图。在自己熟悉的结构中,根据实际的业务去修改自己熟悉的代码,相对而言会简单许多。
下面来看看我自己设计的图的结构.
首先定义图Graph结构:
package code03.图_05.结构;
import java.util.HashMap;
import java.util.HashSet;
public class Graph {
public HashMap<Integer, Node> nodes;
public HashSet<Edge> edges;
public Graph() {
nodes = new HashMap<>();
edges = new HashSet<>();
}
}
再定义边结构:
package code03.图_05.结构;
public class Edge {
public int weight; //权重
public Node from; //开始顶点
public Node to; //结束顶点
public Edge(int w, Node f, Node t) {
weight = w;
from = f;
to = t;
}
}
点结构:
package code03.图_05.结构;
import java.util.ArrayList;
public class Node {
public int val; //当前点对应的值
public int in; //有多少条边指向这个顶点,称为入度。根据业务定
public int out; //这个点作为开始顶点,一个出去多少边,称为出度。根据业务定
//当前节点出去找到的节点
public ArrayList<Node> neighborNodes;
//当前节点出去的边
public ArrayList<Edge> neighborEdges;
Node (int value) {
val = value;
in = 0;
out = 0;
neighborNodes = new ArrayList<>();
neighborEdges = new ArrayList<>();
}
}
有了以上3个实现类,现在还差一个适配类。适配类是需要根据自己面对的具体业务逻辑去写的。
下面以数组形式为例。
假设,现在给你一堆数组,[[3,0,3],[5,3,6].........]. 这代表什么意思呢?解释一下,二维数组中,每一个子数组有3个值,第一个代表边的权重,第二个代表边的开始节点,第三个代表边的结束节点。【5,3,6】 就代表从顶点 3 到顶点6的边,而边的权重为5。
代码如下:
package code03.图_05.结构;
public class GenerateGrap {
// matrix 所有的边
// N*3 的矩阵
// [weight, from节点上面的值,to节点上面的值]
//
// [ 5 , 0 , 7]
// [ 3 , 0, 1]
public static Graph createGraph(int[][] matrix)
{
Graph graph = new Graph();
for (int i = 0; i < matrix.length; i++)
{
int weight = matrix[i][0];
int from = matrix[i][1];
int to = matrix[i][2];
//点搜集
if (!graph.nodes.containsKey(from)) {
graph.nodes.put(from, new Node(from));
}
if (!graph.nodes.containsKey(to)) {
graph.nodes.put(to, new Node(to));
}
Node fromNode = graph.nodes.get(from);
Node toNode = graph.nodes.get(to);
Edge edge = new Edge(weight, fromNode, toNode);
//边搜集
if (!graph.edges.contains(edge)) {
graph.edges.add(edge);
}
fromNode.out++;
fromNode.neighborEdges.add(edge);
fromNode.neighborNodes.add(toNode);
toNode.in++;
}
return graph;
}
/** 5
* 2 ----------> 8
* 3/ 4 2/ \4
* 1 ------> 3 7
* \ 7 /
* 2\ /
* 4
*/
public static void main(String[] args) {
int[][] matrix = {{3,1,2},{4,1,3},{2,1,4}, {5,2,8},{2,3,8},{7,4,7},{4,8,7}};
Graph graph = createGraph(matrix);
System.out.println("111");
}
}
其实,此时,一个图就构造好了。如果遇到的是二维数组,就可以写出这样的适配类来进行转化。当然,还有很多的表示方法,需要根据不同的表示方法,设计出不同的适配类。反正,图的结构就是这样。也可以根据实际的业务,去设计整个结构,使图有不同的结构。
图的广度优先遍历(Breadth First Search)
我们接触二叉树的时候,讲过树的层序遍历。其实,图的广度优先遍历和二叉树的层序优先遍历逻辑一样。就是找到一个顶点,然后把它的子节点给全部遍历出来,然后根据子节点再找孙子节点,再把每个子节点的孙子节点给遍历出来,依次类推。
package code03.图_05;
import code03.图_05.结构.GenerateGrap;
import code03.图_05.结构.Graph;
import code03.图_05.结构.Node;
import java.util.*;
public class BreadthFirstSearch_01 {
//广度优先遍历
public static void bfs (Graph graph)
{
//边界值判断
if (graph == null) {
return;
}
Queue queue = new LinkedList();
Set set = new HashSet();
HashMap<Integer, Node> nodes = graph.nodes;
//锁定顶点,就是入度为0的节点
for(Iterator iterator = nodes.keySet().iterator(); iterator.hasNext();) {
int key = (int) iterator.next();
Node val = nodes.get(key);
if (val.in == 0) {
queue.add(val);
set.add(val);
break;
}
}
//队列,先进先出,这是层序遍历的核心
while (!queue.isEmpty())
{
Node cur = (Node) queue.poll();
System.out.println("node : " + cur.val);
//遍历所有子节点
for(int i = 0; i < cur.neighborNodes.size(); i++) {
Node next = cur.neighborNodes.get(i);
//如果set中有值,说明这个子节点已经被遍历过了,无需再次遍历
if (!set.contains(next)) {
set.add(next);
queue.add(next);
}
}
}
}
public static void main(String[] args) {
int[][] matrix = {{3,1,2},{4,1,3},{2,1,4}, {5,2,8},{2,3,8},{7,4,7},{4,8,7}};
//使用我们自己设计的图结构
Graph graph = GenerateGrap.createGraph(matrix);
bfs(graph);
}
}
图的深度优先遍历(Depth First Search)
深度优先遍历,就是根据顶点锁定一个子节点,一直遍历到最后。 然后返回,锁定另一个子节点,一直遍历到最后,依次类推。也就是说,一旦锁定一个子节点,会把这个子节点下方所有的节点都给遍历完为止。
package code03.图_05;
import code03.图_05.结构.GenerateGrap;
import code03.图_05.结构.Graph;
import code03.图_05.结构.Node;
import java.util.*;
public class DepthFirstSearch_02 {
//深度优先遍历
public static void dfs (Graph graph)
{
if (graph == null) {
return;
}
Stack stack = new Stack();
Set set = new HashSet();
HashMap<Integer, Node> nodes = graph.nodes;
//锁定一个顶点
for(Iterator iterator = nodes.keySet().iterator(); iterator.hasNext();) {
int key = (int) iterator.next();
Node val = nodes.get(key);
if (val.in == 0) {
stack.push(val);
set.add(val);
break;
}
}
//栈、后进先出,这是深入优先遍历的核心
while (!stack.isEmpty())
{
Node cur = (Node) stack.pop();
System.out.println("node : " + cur.val);
for(int i = 0; i < cur.neighborNodes.size(); i++) {
Node next = cur.neighborNodes.get(i);
if (!set.contains(next)) {
set.add(next);
stack.add(next);
}
}
}
}
public static void main(String[] args) {
int[][] matrix = {{3,1,2},{4,1,3},{2,1,4}, {5,2,8},{2,3,8},{7,4,7},{4,8,7}, {6,6,4},{7,7,6}};
Graph graph = GenerateGrap.createGraph(matrix);
dfs(graph);
}
}
拓扑序:
1)在图中找到所有入度为0的点输出
2)把所有入度为0的点在图中删掉,继续找入度为0的点输出,周而复始
3)图的所有点都被删除后,依次输出的顺序就是拓扑排序
要求:有向图且其中没有环
package code03.图_05;
import code03.图_05.结构.GenerateGrap;
import code03.图_05.结构.Graph;
import code03.图_05.结构.Node;
import java.util.*;
public class TopoSort_03 {
public static List sort(Graph graph)
{
if (graph == null || graph.nodes.size() == 0) {
return null;
}
Queue<Node> queue = new LinkedList<>();
//使用map可以构造出节点和入度的key-value结构。就算是图的
//结构中没有入度,我们也可以通过map给强行构造出来
Map<Node, Integer> map = new HashMap<>();
//搜集所有入度为0的顶点
for(Node node : graph.nodes.values()) {
map.put(node, node.in);
if (node.in == 0) {
queue.add(node);
}
}
//拓扑结果必须是有向、无环的图
if (queue.isEmpty()) {
return null;
}
List<Node> result = new ArrayList<>();
while (!queue.isEmpty()) {
Node cur = queue.poll();
result.add(cur); //搜集到入度为0的节点
//当前节点指向的节点,入度都要减少1
for (Node next : cur.neighborNodes) {
map.put(next, map.get(next)-1);
if (map.get(next) == 0) {
queue.add(next);
}
}
}
return result;
}
//与上一个方法,唯一的不同就是没有使用map构造节点和入度的key-value
public static List sort2(Graph graph)
{
if (graph == null || graph.nodes.size() == 0) {
return null;
}
Queue<Node> queue = new LinkedList<>();
//搜集所有入度为0的顶点
for(Node node : graph.nodes.values()) {
if (node.in == 0) {
queue.add(node);
}
}
//拓扑结果必须是有向、无环的图。如果找不到入度为0
//的顶点,那就说明是有环
if (queue.isEmpty()) {
return null;
}
List<Node> result = new ArrayList<>();
while (!queue.isEmpty()) {
Node cur = queue.poll();
result.add(cur); //搜集到入度为0的节点
//当前节点指向的节点,入度都要减少1
for (Node next : cur.neighborNodes) {
next.in--;
if (next.in == 0) {
queue.add(next);
}
}
}
return result;
}
public static void main(String[] args) {
int[][] matrix = {{3,1,2},{4,1,3},{2,1,4}, {5,2,8},{2,3,8},{8,3,7},{5,4,6},{4,8,7}, {7,7,6}};
Graph graph = GenerateGrap.createGraph(matrix);
//List<Node> list = sort(graph);
List<Node> list = sort2(graph);
for (Node node : list) {
System.out.println(node.val);
}
}
}
LintCode算法题
下面刷一道算法题,体现一下使用Map构造Node和入度的key-value结构是多么的重要。题目的详细说明看连接 https://www.lintcode.com/problem/127/
这题的最大障碍是他自己定义了点结构,并且这个结构中没有入度、出度这个概念。上一题拓扑序我们是直接使用点结构中的入度,就可以直接锁定入度为0的节点,并且可以根据这个入度参数,直接进行节点删除后,它的指向节点的入度减少操作。
本题,我们定义一个Map来记录它的节点和入度的 key-value结构就可以解决。
package code03.图_05;
import unit2.class33.Hash;
import java.util.*;
/**
* https://www.lintcode.com/problem/127/
*
* 最大的问题是没有入度
*/
public class TopoSortLintCode {
//图中的点结构
static class DirectedGraphNode {
int label;
List<DirectedGraphNode> neighbors;
DirectedGraphNode(int x) {
label = x;
neighbors = new ArrayList<DirectedGraphNode>();
}
}
public ArrayList<DirectedGraphNode> topSort(ArrayList<DirectedGraphNode> graph)
{
if (graph == null || graph.size() == 0) {
return null;
}
Map<DirectedGraphNode, Integer> inMap = new HashMap<>();
//构建每一个节点的入度
for(DirectedGraphNode node : graph) {
//默认都为0的入度
inMap.put(node, 0);
}
//按照节点的直接指向,给每一个指向节点设置入度值
for(DirectedGraphNode node : graph) {
for (DirectedGraphNode next : node.neighbors) {
inMap.put(next, inMap.get(next) + 1);
}
}
//找到默认入度为0的顶点
Queue<DirectedGraphNode> queue = new LinkedList<>();
for(DirectedGraphNode node : graph) {
if (inMap.get(node) == 0) {
queue.add(node);
}
}
//拓扑排序结果搜集
ArrayList<DirectedGraphNode> result = new ArrayList<>();
while (!queue.isEmpty()) {
DirectedGraphNode node = queue.poll();
result.add(node);
for (DirectedGraphNode next : node.neighbors) {
inMap.put(next, inMap.get(next) -1);
if (inMap.get(next) == 0) {
queue.add(next);
}
}
}
return result;
}
public static void main(String[] args) {
//输入 graph = {0,1,2,3#1,4#2,4,5#3,4,5#4#5}
//输出 [0, 1, 2, 3, 4, 5]
ArrayList<DirectedGraphNode> graph = new ArrayList<>();
DirectedGraphNode node0 = new DirectedGraphNode(0);
DirectedGraphNode node1 = new DirectedGraphNode(1);
DirectedGraphNode node2 = new DirectedGraphNode(2);
DirectedGraphNode node3 = new DirectedGraphNode(3);
DirectedGraphNode node4 = new DirectedGraphNode(4);
DirectedGraphNode node5 = new DirectedGraphNode(5);
node0.neighbors.add(node1);
node0.neighbors.add(node2);
node0.neighbors.add(node3);
node1.neighbors.add(node4);
node2.neighbors.add(node4);
node2.neighbors.add(node5);
node3.neighbors.add(node4);
node3.neighbors.add(node5);
graph.add(node0);
graph.add(node1);
graph.add(node2);
graph.add(node3);
graph.add(node4);
graph.add(node5);
TopoSortLintCode code = new TopoSortLintCode();
ArrayList<DirectedGraphNode> nodes = code.topSort(graph);
for (DirectedGraphNode node : nodes) {
System.out.println(node.label);
}
}
}
我测试了一下,在LintCode中打败了84%的选手,也就是还有优化的空间,下面看优化版本:
package code03.图_05;
import java.util.*;
/**
* https://www.lintcode.com/problem/127/
*
* 优化版本
*/
public class TopoSortLintCode_opt {
//图中的点结构
static class DirectedGraphNode {
int label;
List<DirectedGraphNode> neighbors;
DirectedGraphNode(int x) {
label = x;
neighbors = new ArrayList<DirectedGraphNode>();
}
}
public ArrayList<DirectedGraphNode> topSort(ArrayList<DirectedGraphNode> graph)
{
if (graph == null || graph.size() == 0) {
return null;
}
Map<DirectedGraphNode, Integer> inMap = new HashMap<>();
//按照节点的直接指向,给每一个指向节点设置入度值。 此处是最大的优先点,
//是直接把有入度的节点给设置入度值,而不是之前给所有节点设置入度值为0的操作。少了
//一个for循环,提高了性能
for(DirectedGraphNode node : graph) {
//这个for循环都是被指向的节点,因此入度至少为1.
for (DirectedGraphNode next : node.neighbors) {
if (inMap.containsKey(next)) {
inMap.put(next, inMap.get(next) + 1); //多次出现,就是累加
}
else {
inMap.put(next, 1); //第一次就是1
}
}
}
//如果map中没有当前node,说明当前node不是任何node的neighbors元素
//也就变相说明了它没有入度,即入度为0
Queue<DirectedGraphNode> queue = new LinkedList<>();
for(DirectedGraphNode node : graph) {
if (!inMap.containsKey(node)) {
queue.add(node);
}
}
//拓扑排序结果搜集
ArrayList<DirectedGraphNode> result = new ArrayList<>();
while (!queue.isEmpty()) {
DirectedGraphNode node = queue.poll();
result.add(node);
for (DirectedGraphNode next : node.neighbors) {
inMap.put(next, inMap.get(next) -1);
if (inMap.get(next) == 0) {
queue.add(next);
}
}
}
return result;
}
public static void main(String[] args) {
//输入 graph = {0,1,2,3#1,4#2,4,5#3,4,5#4#5}
//输出 [0, 1, 2, 3, 4, 5]
ArrayList<DirectedGraphNode> graph = new ArrayList<>();
DirectedGraphNode node0 = new DirectedGraphNode(0);
DirectedGraphNode node1 = new DirectedGraphNode(1);
DirectedGraphNode node2 = new DirectedGraphNode(2);
DirectedGraphNode node3 = new DirectedGraphNode(3);
DirectedGraphNode node4 = new DirectedGraphNode(4);
DirectedGraphNode node5 = new DirectedGraphNode(5);
node0.neighbors.add(node1);
node0.neighbors.add(node2);
node0.neighbors.add(node3);
node1.neighbors.add(node4);
node2.neighbors.add(node4);
node2.neighbors.add(node5);
node3.neighbors.add(node4);
node3.neighbors.add(node5);
graph.add(node0);
graph.add(node1);
graph.add(node2);
graph.add(node3);
graph.add(node4);
graph.add(node5);
TopoSortLintCode_opt code = new TopoSortLintCode_opt();
ArrayList<DirectedGraphNode> nodes = code.topSort(graph);
for (DirectedGraphNode node : nodes) {
System.out.println(node.label);
}
}
}
通过优化,同样的数据,测试结果打败100%。
最小生成树
概念我就不去说了,简单介绍就是找到连接图中连接所有顶点的边,并且这些边的权重累加和最小。
Kruskal算法:
1)总是从权值最小的边开始考虑,依次考察权值依次变大的边
2)当前的边要么进入最小生成树的集合,要么丢弃
3)如果当前的边进入最小生成树的集合中不会形成环,就要当前边
4)如果当前的边进入最小生成树的集合中会形成环,就不要当前边
5)考察完所有边之后,最小生成树的集合也得到了
K算法的描述已经很清楚了,就是搜集最小边,无环就留下,有环就pass掉。而边是有开始和结束两个顶点组成的,根据from-to这两个顶点,就可以确认一条边了。以三角形3个顶点 A B C举例。 AB BC被搜集了,那么AC或者CA再出现, 就会形成环。典型的并查集思想。
package code03.图_05;
import code03.图_05.结构.Edge;
import code03.图_05.结构.GenerateGrap;
import code03.图_05.结构.Graph;
import code03.图_05.结构.Node;
import java.util.*;
/**
* 最小生成树算法之Kruskal
*
* 1)总是从权值最小的边开始考虑,依次考察权值依次变大的边
* 2)当前的边要么进入最小生成树的集合,要么丢弃
* 3)如果当前的边进入最小生成树的集合中不会形成环,就要当前边
* 4)如果当前的边进入最小生成树的集合中会形成环,就不要当前边
* 5)考察完所有边之后,最小生成树的集合也得到了
*
* 使用并查集的相关算法解决
*/
public class KruskalMinTree_04 {
static class UnionFind {
Map<Node, Node> parent;
Map<Node, Integer> size;
public UnionFind (Collection<Node> nodes) {
parent = new HashMap<>();
size = new HashMap<>();
for (Node node : nodes) {
parent.put(node, node);
size.put(node, 1);
}
}
public Node findParent (Node cur) {
Node node = cur;
/**
* 默认cur == parent.get(cur) 如果不相等
* 说明并查集合并过
*/
while (cur != parent.get(cur)) {
cur = parent.get(cur);
}
return cur;
}
//判断2个顶点是否被合并在同一个集合中
public boolean isSameCollection (Node node1, Node node2) {
return findParent(node1) == findParent(node2);
}
public void union (Node from, Node to) {
Node fParent = findParent(from);
Node tParent = findParent(to);
//没有共同的祖先,说明不在同一个集合中
if (fParent != tParent) {
/*if (size.get(fParent) >= size.get(tParent)) {
//顶点数小节点,挂在顶点数大的节点下方
parent.put(tParent, fParent);
//更新大集合下方顶点数量
size.put(fParent, size.get(fParent) + size.get(tParent));
//被合并后的集合,不再保有后代信息
size.put(tParent, 1); //0 或 1 都不影响
}
else {
parent.put(fParent, tParent);
//更新大集合下方顶点数量
size.put(tParent, size.get(fParent) + size.get(tParent));
size.put(fParent, 1);
}*/
//上方if...else的优化版本
Node maxParent = size.get(fParent) >= size.get(tParent) ? fParent : tParent;
Node minParent = maxParent == fParent ? tParent : fParent;
//顶点数小节点,挂在顶点数大的节点下方. 也就是说它的父为顶点大的节点
parent.put(minParent, maxParent);
//更新大集合下方顶点数量
size.put(maxParent, size.get(maxParent) + size.get(minParent));
//被合并后的集合,不再保有后代信息
size.put(minParent, 1); //0 或 1 都不影响
}
}
}
//升序比较器
class MyComparator implements Comparator<Edge> {
@Override
public int compare(Edge o1, Edge o2) {
return o1.weight - o2.weight;
}
}
public Set<Edge> kruskalMST(Graph graph)
{
if (graph == null || graph.nodes.values().isEmpty()) {
return null;
}
Set<Edge> set = graph.edges; //边
HashMap<Integer, Node> map = graph.nodes; //顶点
UnionFind uf = new UnionFind(map.values());
//小根堆,是为了搜集权重较小的边
Queue<Edge> queue = new PriorityQueue<>(new MyComparator());
//所有的边,按照由小到大排序
for(Iterator iterator = set.iterator(); iterator.hasNext();)
{
Edge edge = (Edge) iterator.next();
queue.add(edge);
}
Set<Edge> minTreeEdge = new HashSet();
/**
* 由小到大遍历所有的边。
* 如果边的开头-结尾顶点不在同一个集合,说明边是我们需要的最小生成树的边
* 如果边的开头-顶点已经在同一个集合,那就不能添加,否则形成环
*/
while (!queue.isEmpty()) {
Edge edge = queue.poll();
//当前边是最小边,如果边的开头-结尾顶点不在同一个集合,
//说明这条边还没有被搜集,我们需要搜集这条表。
//以三角形3个顶点 A B C举例。 AB BC被搜集了,那么AC或者CA再出现,
//就会形成环。
if (!uf.isSameCollection(edge.from, edge.to)) {
//搜集到的边
minTreeEdge.add(edge);
uf.union(edge.from, edge.to);
}
}
return minTreeEdge;
}
public static void main(String[] args) {
int[][] matrix = {{3,1,2},{4,1,3},{2,1,4}, {5,2,8},{2,3,8},{7,4,7},{11,8,7}};
Graph graph = GenerateGrap.createGraph(matrix);
KruskalMinTree_04 k = new KruskalMinTree_04();
Set<Edge> set = k.kruskalMST(graph);
for(Iterator iterator = set.iterator(); iterator.hasNext();) {
Edge edge = (Edge) iterator.next();
System.out.println("当前边的长度为 :" + edge.weight + ", from节点:" + edge.from.val + ", to节点为:" + edge.to.val);
}
}
}
Prim算法
1)可以从任意节点出发来寻找最小生成树
2)某个点加入到被选取的点中后,解锁这个点出发的所有新的边
3)在所有解锁的边中选最小的边,然后看看这个边会不会形成环
4)如果会,不要当前边,继续考察剩下解锁的边中最小的边,重复3)
5)如果不会,要当前边,将该边的指向点加入到被选取的点中,重复2)
6)当所有点都被选取,最小生成树就得到了
简单概括就是,搜集到一个顶点,就解锁这个点的所有边,把这些边和之前已经解锁的边放在一起比较,找到最小的边。无环,就搜集。有环,就pass掉。
package code03.图_05;
import code03.图_05.结构.Edge;
import code03.图_05.结构.GenerateGrap;
import code03.图_05.结构.Graph;
import code03.图_05.结构.Node;
import java.util.*;
/**
* 最小生成树算法之Prim
*
* 1)可以从任意节点出发来寻找最小生成树
* 2)某个点加入到被选取的点中后,解锁这个点出发的所有新的边
* 3)在所有解锁的边中选最小的边,然后看看这个边会不会形成环
* 4)如果会,不要当前边,继续考察剩下解锁的边中最小的边,重复3)
* 5)如果不会,要当前边,将该边的指向点加入到被选取的点中,重复2)
* 6)当所有点都被选取,最小生成树就得到了
*/
public class PrimMinTree_05 {
static class EdgeComparator implements Comparator<Edge> {
@Override
public int compare(Edge o1, Edge o2) {
return o1.weight - o2.weight;
}
}
public Set<Edge> primMST(Graph graph)
{
if (graph == null || graph.nodes.values().isEmpty()) {
return null;
}
// 解锁的边进入小根堆
Queue<Edge> priorityQueue = new PriorityQueue<>(new EdgeComparator());
//哪些点被解锁出来了
HashSet<Node> nodeSet = new HashSet<>();
// 依次挑选的的边在result里. 这里的set是核心,因为它不会存在重复元素,是会被替换的。
Set<Edge> result = new HashSet<>();
//1)可以从任意节点出发来寻找最小生成树
for (Node node : graph.nodes.values())
{
if (!nodeSet.contains(node)) {
nodeSet.add(node);
//2)某个点加入到被选取的点中后,解锁这个点出发的所有新的边
for (Edge edge : node.neighborEdges) {
priorityQueue.add(edge);
}
while (!priorityQueue.isEmpty()) {
//3)在所有解锁的边中选最小的边,然后看看这个边会不会形成环
Edge edge = priorityQueue.poll();
Node toNode = edge.to;
/**
* 4)如果会,不要当前边,继续考察剩下解锁的边中最小的边,重复3)
* 5)如果不会,要当前边,将该边的指向点加入到被选取的点中,重复2)
*
* 这里需要强调的是,为什么nodeSet中不含有这个toNode节点,那这条边就是无环的呢?
* 首先,这是一条最小的边,待定选择;
* 然后,需要判断这条边是否会形成环,以三角形为例。 A B C 3个点
*
* 1. 首先解锁A节点,得到AB AC 两条边。 假设AB边的权重比较小。 选择AB这条边,
* B节点没有呗解锁过。 那么久要AB这条边。
* 2. 然后解锁B这个点对应的所有边。此时发现, AB被解锁过了,BC没有呗解锁。
* AC和BC这2条边选取小的。 假设BC比较小,选取BC
* 3. C这个点没有呗解锁过, BC被搜集
* 4 最后剩下的就是CA这条表了,它此时也是最小的边。C点刚被解锁,发现A点已经被解锁了,
* 如果旋转CA这条边,就形成环。因此放弃这条。最终就是AB BC这2条边被选中
*
* 其实,第4步,AC或者CA都行,如果AC,那就是A节点刚被解锁过,去看C节点,此时C节点也被
* 解锁过,会形成环,直接放弃。道理是一样的
*/
if (!nodeSet.contains(toNode)) {
nodeSet.add(toNode);
//当前边是最小边,并且当前边的from-to都是第一次解锁
//说明这就是要搜集的边。
result.add(edge);
//5)将该边的指向点加入到被选取的点中,重复2)
for (Edge nextEdge : toNode.neighborEdges) {
priorityQueue.add(nextEdge);
}
}
}
}
}
return result;
}
public static void main(String[] args) {
int[][] matrix = {{3,1,2},{4,1,3},{2,1,4}, {5,2,8},{2,3,8},{7,4,7},{11,8,7}};
Graph graph = GenerateGrap.createGraph(matrix);
PrimMinTree_05 p = new PrimMinTree_05();
Set<Edge> set = p.primMST(graph);
for(Iterator iterator = set.iterator(); iterator.hasNext();) {
Edge edge = (Edge) iterator.next();
System.out.println("当前边的长度为 :" + edge.weight + ", from节点:" + edge.from.val + ", to节点为:" + edge.to.val);
}
}
}