一、拓扑排序
1、拓扑排序目标
对于有向无环图,拓扑排序的目标其实就是找出依赖关系的顺序。
上面那幅图的拓扑排序就是A B C D E F 或 A B D C E F
。
2、算法思路
先找到入度为0的顶点依次入队,每次从队头出队一个顶点(可以看成是从这幅图中删除),由此更新该顶点出度边终点的入度信息,一旦有新的入度为0的顶点就立刻将这个点入队,直到队列为空。由此出队的顺序其实就是拓扑排序的结果。
3、注意点
怎么维护入度信息?用一个哈希表记录除了起始入度就为0的所有结点的入度信息,每次从队列中取出一个顶点就更新这个哈希表即可。
怎么保存结果集?因为结果是严格要求顺序的,可用动态数组或者链表保存。
4、实现
/**
* 有向无环图的拓扑排序
*/
@Override
public List<E> topologicalSort() {
List<E> result = new ArrayList<>();
Map<E, Integer> table = new HashMap<>(); //哈希表维护所有结点的入度
Queue<Vertex<E, V>> queue = new LinkedList<>();
Set<E> keys = vertices.keySet();
for (E e : keys) {
Vertex<E, V> vertex = vertices.get(e);
int size = vertex.inEdges.size();
if (size == 0)
queue.offer(vertex); //队列初始化
else
table.put(e, vertex.inEdges.size()); //哈希表初始化
}
while (!queue.isEmpty()) {
Vertex<E, V> vertex = queue.poll();
result.add(vertex.element);
for (Edge<E, V> edge : vertex.outEdges) {
Integer size = table.get(edge.to.element);
if (--size == 0) {
queue.offer(edge.to); //一旦结点的入度为0立刻入队
table.remove(edge.to.element);
} else {
table.put(edge.to.element, size); //更新由出队结点指向的其他结点的入度
}
}
}
return result;
}
二、最小生成树算法(无向图)
1、最小生成树算法目标
生成树:连通图的极小连通子图。即 n-1 条边连接 n 个顶点。
最小生成数的目标就是找到所有生成树中总权值最小的一棵树。
2、Prim算法
① 算法思路
选取一个点作为起点,将起点加入集合,从集合中所有点的出度边中选出权值最小的一条边加入到结果集中,并将这条边的终点也加到集合中,不断执行上述操作,直到集合中的顶点数量达到图中所有的顶点数,则算法结束。
这个算法中自始自终只有一个集合,所以可用set模拟并查集。
② 注意点
怎么挑选权值最小的边?可以用优先队列来保存所有边,但是出队最小边的时候需要注意,不能让原来结果集里面的顶点成环!
③ 实现
private Set<EdgeInfo<E, V>> prim() {
Set<EdgeInfo<E, V>> result = new HashSet<>();
Set<Vertex<E, V>> addedVertices = new HashSet<>();
int desPointSize = vertices.size();
PriorityQueue<Edge<E, V>> queue = new PriorityQueue<>(new Comparator<Edge<E, V>>() {
@Override
public int compare(Edge<E, V> o1, Edge<E, V> o2) {
return weightAbout.compareWeight(o1.weight, o2.weight);
}
});
Iterator<Vertex<E, V>> it = vertices.values().iterator();
Vertex<E, V> vertex = it.next();
for (Edge<E, V> edge : vertex.outEdges)
queue.offer(edge); //初始化优先队列
addedVertices.add(vertex);
while (!queue.isEmpty() && addedVertices.size() < desPointSize) {
Edge<E, V> minEdge = queue.poll(); //取出权值最小的边
Vertex<E, V> toVertex = minEdge.to;
if (addedVertices.contains(toVertex)) //避免成环
continue;
toVertex.outEdges.forEach((Edge<E, V> edge) -> {
if (!addedVertices.contains(edge.to)) //避免添加重复边
queue.offer(edge); //每次都将取出边终点的出度边入队
});
result.add(minEdge.castToInfo());
addedVertices.add(toVertex);
}
return result;
}
3、Kruskal算法
① 算法思路
按照边的权重顺序(从小到大)将边加入生成树中,直到生成树中含有V – 1 条边为止( V 是顶点数量)。 相当于初始的时候把所有顶点都作为一个单独的集合,选出一条边就将这条边的起点和终点合并为一个集合。当然如果选出边的起点和终点本来就在一个集合中那势必会成环,就只能选下一条边了。
这个算法涉及到多个集合,只能用并查集来实现了。
② 实现
private Set<EdgeInfo<E, V>> kruskal() {
Set<EdgeInfo<E, V>> result = new HashSet<>();
PriorityQueue<Edge<E, V>> queue = new PriorityQueue<>(new Comparator<Edge<E, V>>() {
@Override
public int compare(Edge<E, V> o1, Edge<E, V> o2) {
return weightAbout.compareWeight(o1.weight, o2.weight);
}
});
int desEdgeSize = vertices.size() - 1;
UnionFind<E> uf = new UnionFind<>();
edges.forEach((Edge<E, V> edge) -> { //初始化队列
queue.offer(edge);
});
vertices.values().forEach((Vertex<E, V> vertex) -> { //初始化集合
uf.makeSet(vertex.element);
});
while (!queue.isEmpty() && result.size() < desEdgeSize) {
Edge<E, V> minEdge = queue.poll();
Vertex<E, V> fromVertex = minEdge.from;
Vertex<E, V> toVertex = minEdge.to;
if (uf.isSame(fromVertex.element, toVertex.element)) //避免成环
continue;
result.add(minEdge.castToInfo());
uf.union(fromVertex.element, toVertex.element);
}
return result;
}