赞
踩
将AOV网中所有活动排成一个序列,使每个活动的前驱活动都排在该活动前面。
将一项大的工程分为若干个小的子工程,子工程中可以存在顺序,部分子工程需要等待其它子工程完成后才可以执行。
AOV网必须是一个有向完全图:明显可以看出边的终点的顶点依赖于起点的顶点。
**前驱活动:**有向边起点的活动称为终点的前驱活动;
**后驱活动:**有向边终点的活动称为起点的前驱活动;
以下图为示例:
新建一个list
集合作为存储顶点的返回值;新建一个queue
将入度为0的顶点加入到队列中等待操作;新建一个map
记录每个顶点的入度。
记录所有入度非0的顶点的入度:
将入度为0的顶点加入到队列中:
出队,加入到list
中,将当前出队元素的所有后驱活动的入度值减一。
循环执行上方3操作。
@Override public List<V> topologicalSort() { List<V> list = new ArrayList<>(); Queue<Vertex<V, E>> queue = new LinkedList<>(); HashMap<Vertex<V, E>, Integer> map = new HashMap<>(); // 将所有的入度为0的顶点加入到队列中 vertices.forEach((V v, Vertex<V,E> vertex) ->{ int size = vertex.inEdges.size(); if (size == 0){ queue.offer(vertex); }else { map.put(vertex,size); } }); while (!queue.isEmpty()){ Vertex<V, E> vertex = queue.poll(); list.add(vertex.v); for (Edge<V, E> edge : vertex.outEdges) { int size = map.get(edge.to) - 1; if (size == 0){ // 入度为 0 加入到队列中 queue.offer(edge.to); }else { // 不为 0 入度减一 map.put(edge.to,size); } } } return list; }
生成树,也称支撑树。
连通图的极小连通子图:包含图中全部n个顶点,恰好有n - 1条边。
上方图的极小连通子图
所有生成树中,总权值最小的那颗。如果每条边的权值都不相同,就只有一个最小生成树,如果相同则由不同的生成树。
最小生成树
protected WeightManager<E> weightManager; public Graph() {} public Graph(WeightManager<E> weightManager) { this.weightManager = weightManager; } /** * 权值内部接口设计 * @param <E> */ public interface WeightManager<E> { /** * 比较值的大小 * @param w1 * @param w2 * @return */ int compare(E w1, E w2); /** * 两个值相加的结果 * @param w1 * @param w2 * @return */ E add(E w1, E w2); E zero(); }
/** * 返回边的节点设计 * @param <V> * @param <E> */ public static class EdgeInfo<V,E>{ // 起点值 private V from; // 终点值 private V to; // 权值 private E weight; public EdgeInfo(V from, V to, E weight) { this.from = from; this.to = to; this.weight = weight; } public V getFrom() { return from; } public void setFrom(V from) { this.from = from; } public V getTo() { return to; } public void setTo(V to) { this.to = to; } public E getWeight() { return weight; } public void setWeight(E weight) { this.weight = weight; } }
-全部代码:
/** * @Description 图接口 * @date 2022/5/10 20:31 */ public abstract class Graph<V,E>{ protected WeightManager<E> weightManager; public Graph() {} public Graph(WeightManager<E> weightManager) { this.weightManager = weightManager; } /** * 获取边的数量 * @return 边数量 */ public abstract int edgeSize(); /** * 获取顶点数量 * @return 顶点数量 */ public abstract int verticesSize(); /** * 添加一个顶点 * @param v 顶点 */ public abstract void addVertex(V v); /** * 两个顶点之间添加一个边 * @param from 起点 * @param to 终点 */ public abstract void addEdge(V from, V to); /** * 两个顶点之间添加一个携带权值的边 * @param from 起点 * @param to 终点 * @param weight 权值 */ public abstract void addEdge(V from, V to, E weight); /** * 删除一个顶点 * @param v 顶点 */ public abstract void removeVertex(V v); /** * 删除一条边 * @param from 起点 * @param to 终点 */ public abstract void removeEdge(V from, V to); /** * 广度优先搜索 * @param v */ public abstract void bfs(V v, VertexVisitor<V> visitor); /** * 深度优先搜索 * @param v */ public abstract void dfs(V v, VertexVisitor<V> visitor); /** * 使用栈实现深度优先搜索 * @param v */ public abstract void difOfStack(V v, VertexVisitor<V> visitor); /** * dfs 和 bfs 遍历是外部获取值 * @param <V> */ public interface VertexVisitor<V> { boolean visit(V v); } /** * 拓扑排序 * @return */ public abstract List<V> topologicalSort(); /** * 最小生成树 * @return */ public abstract Set<EdgeInfo<V,E>> mst(); /** * 权值内部接口设计 * @param <E> */ public interface WeightManager<E> { /** * 比较值的大小 * @param w1 * @param w2 * @return */ int compare(E w1, E w2); /** * 两个值相加的结果 * @param w1 * @param w2 * @return */ E add(E w1, E w2); E zero(); } /** * 返回边的节点设计 * @param <V> * @param <E> */ public static class EdgeInfo<V,E>{ // 起点值 private V from; // 终点值 private V to; // 权值 private E weight; public EdgeInfo(V from, V to, E weight) { this.from = from; this.to = to; this.weight = weight; } public V getFrom() { return from; } public void setFrom(V from) { this.from = from; } public V getTo() { return to; } public void setTo(V to) { this.to = to; } public E getWeight() { return weight; } public void setWeight(E weight) { this.weight = weight; } } }
Edge
类因为外部暴露的是抽象类中封装的EdgeInfo
类,为了方便对象之间类型转换,新建一个方法将Edge
对象变为EdgeInfo
对象:
EdgeInfo<V, E> info(){
return new EdgeInfo<V, E>(from.v, to.v, weight);
}
小顶堆:因为需要获取多个值中最小的一个,非常适合使用小顶堆,但是JDK的优先级队列方法有限,这里实现了一个小顶堆代码。
/** * @Description 小顶堆 * @date 2022/5/10 20:38 */ @SuppressWarnings("unchecked") public class MinHeap<E> { private int size; private Comparator<E> comparator; private int compare(E e1, E e2) { return comparator != null ? comparator.compare(e1, e2) : ((Comparable<E>)e1).compareTo(e2); } private E[] elements; private static final int DEFAULT_CAPACITY = 10; public MinHeap(Collection<E> elements, Comparator<E> comparator) { this.comparator = comparator; size = elements == null ? 0 : elements.size(); if (size == 0) { this.elements = (E[]) new Object[DEFAULT_CAPACITY]; } else { int capacity = Math.max(size, DEFAULT_CAPACITY); this.elements = (E[]) new Object[capacity]; int i = 0; for (E element : elements) { this.elements[i++] = element; } heapify(); } } public MinHeap(E[] elements, Comparator<E> comparator) { this.comparator = comparator; if (elements == null || elements.length == 0) { this.elements = (E[]) new Object[DEFAULT_CAPACITY]; } else { size = elements.length; int capacity = Math.max(elements.length, DEFAULT_CAPACITY); this.elements = (E[]) new Object[capacity]; for (int i = 0; i < elements.length; i++) { this.elements[i] = elements[i]; } heapify(); } } public MinHeap(Collection<E> elements) { this(elements, null); } public MinHeap(E[] elements) { this(elements, null); } public MinHeap(Comparator<E> comparator) { this.comparator = comparator; this.elements = (E[]) new Object[DEFAULT_CAPACITY]; } public MinHeap() { this.elements = (E[]) new Object[DEFAULT_CAPACITY]; } public int size() { return size; } public boolean isEmpty() { return size == 0; } public void addAll(Collection<E> elements) { if (elements == null) return; for (E element : elements) { add(element); } } public void addAll(E[] elements) { if (elements == null) return; for (E element : elements) { add(element); } } public void clear() { for (int i = 0; i < size; i++) { elements[i] = null; } size = 0; } public void add(E element) { elementNotNullCheck(element); ensureCapacity(size + 1); elements[size++] = element; siftUp(size - 1); } public E get() { emptyCheck(); return elements[0]; } public E remove() { emptyCheck(); int lastIndex = --size; E root = elements[0]; elements[0] = elements[lastIndex]; elements[lastIndex] = null; siftDown(0); return root; } public E replace(E element) { elementNotNullCheck(element); E root = null; if (size == 0) { elements[0] = element; size++; } else { root = elements[0]; elements[0] = element; siftDown(0); } return root; } /** * 批量建堆 */ protected void heapify() { // 自下而上的下滤 for (int i = (size >> 1) - 1; i >= 0; i--) { siftDown(i); } } /** * 让index位置的元素下滤 * @param index */ private void siftDown(int index) { E element = elements[index]; int half = size >> 1; // 第一个叶子节点的索引 == 非叶子节点的数量 // index < 第一个叶子节点的索引 // 必须保证index位置是非叶子节点 while (index < half) { // index的节点有2种情况 // 1.只有左子节点 // 2.同时有左右子节点 // 默认为左子节点跟它进行比较 int childIndex = (index << 1) + 1; E child = elements[childIndex]; // 右子节点 int rightIndex = childIndex + 1; // 选出左右子节点最小的那个 if (rightIndex < size && compare(elements[rightIndex], child) < 0) { child = elements[childIndex = rightIndex]; } if (compare(element, child) <= 0) break; // 将子节点存放到index位置 elements[index] = child; // 重新设置index index = childIndex; } elements[index] = element; } /** * 让index位置的元素上滤 * @param index */ private void siftUp(int index) { E element = elements[index]; while (index > 0) { int parentIndex = (index - 1) >> 1; E parent = elements[parentIndex]; if (compare(element, parent) >= 0) break; // 将父元素存储在index位置 elements[index] = parent; // 重新赋值index index = parentIndex; } elements[index] = element; } private void ensureCapacity(int capacity) { int oldCapacity = elements.length; if (oldCapacity >= capacity) return; // 新容量为旧容量的1.5倍 int newCapacity = oldCapacity + (oldCapacity >> 1); E[] newElements = (E[]) new Object[newCapacity]; for (int i = 0; i < size; i++) { newElements[i] = elements[i]; } elements = newElements; } private void emptyCheck() { if (size == 0) { throw new IndexOutOfBoundsException("Heap is empty"); } } private void elementNotNullCheck(E element) { if (element == null) { throw new IllegalArgumentException("element must not be null"); } } }
并查集:判断顶点与顶点之间是否构成环,判断如果两个顶点已经位于同一个集合中,在执行并操作就会成环,因此当前边不能位于最小生成树中。
/** * @Description 小顶堆 * @date 2022/5/13 20:48 */ public class UnionFind<V> { private Map<V, Node<V>> nodes = new HashMap<>(); public void makeSet(V v) { if (nodes.containsKey(v)) return; nodes.put(v, new Node<>(v)); } /** * 找出v的根节点 */ private Node<V> findNode(V v) { Node<V> node = nodes.get(v); if (node == null) return null; while (!Objects.equals(node.value, node.parent.value)) { node.parent = node.parent.parent; node = node.parent; } return node; } public V find(V v) { Node<V> node = findNode(v); return node == null ? null : node.value; } public void union(V v1, V v2) { Node<V> p1 = findNode(v1); Node<V> p2 = findNode(v2); if (p1 == null || p2 == null) return; if (Objects.equals(p1.value, p2.value)) return; if (p1.rank < p2.rank) { p1.parent = p2; } else if (p1.rank > p2.rank) { p2.parent = p1; } else { p1.parent = p2; p2.rank += 1; } } public boolean isSame(V v1, V v2) { return Objects.equals(find(v1), find(v2)); } private static class Node<V> { V value; Node<V> parent = this; int rank = 1; Node(V value) { this.value = value; } } }
给定任意切分,横切边中权值最小的边必然属于最小生成树。
定义集合信息:V
:图中全部顶点集合;E
:图中全部边集合; S={beignVertex}
:存储切分完毕的顶点信息,开始存储起点;A={}
:存储最小生成树中的边信息。
执行:这里以A
作为起点,进行切分,将权值小的边{A,B}
加入到集合A
中;将B
加入到集合S
中。
S
中,剩余的顶点就是集合V - S
;所以这里切分下图中位置:S = V
时完成。 // 比较器
private Comparator<Edge<V, E>> edgeComparator = (Edge<V, E> e1, Edge<V, E> e2) -> {
return weightManager.compare(e1.weight, e2.weight);
};
prim()
@Override public Set<EdgeInfo<V, E>> mst() { return prim(); } /** * prim最小生成树 * @return */ private Set<EdgeInfo<V, E>> prim(){ // 获取顶点迭代器 如果迭代器为空则返回null Iterator<Vertex<V, E>> iterator = vertices.values().iterator(); if (!iterator.hasNext()) return null; // 返回边集 Set<EdgeInfo<V, E>> edgeInfos = new HashSet<>(); // 已经加入到最小生成树中的顶点集 Set<Vertex<V,E>> added = new HashSet<>(); Vertex<V, E> vertex = iterator.next(); added.add(vertex); // 初始化构建小顶堆,传入第一个顶点的出度边集合 和 比较器 MinHeap<Edge<V, E>> heap = new MinHeap<>(vertex.outEdges,edgeComparator); int size = vertices.size(); // 当堆中有元素 并且 已经处理的顶点数量小于总数量时才需要进行处理 while (!heap.isEmpty() && edgeInfos.size() < size){ // 权值最小的边,一定是最小生成树的一部分 Edge<V, E> edge = heap.remove(); // 如果当前点已经被加入到该集合中 表示已经对其处理完毕 不需要再加入到堆中进行处理 if (added.contains(edge.to)) continue; // 把组成最小生成树的边放入到返回的集合中 edgeInfos.add(edge.info()); added.add(edge.to); // 将该边终点的所有出度边加入到堆中 heap.addAll(edge.to.outEdges); } return edgeInfos; }
按照权重从小到大的顺序将边加入到生成树中,但从第三条开始,可能会于生成树形成环,此时就不加入该边。直到生成树种的边数量为顶点数量减一为止。
/** * kruskal 最小生成树 * @return */ private Set<EdgeInfo<V, E>> kruskal(){ int size = vertices.size() - 1; if (size <= 1) return null; // 最小生成树 边 集合 Set<EdgeInfo<V, E>> edgeInfos = new HashSet<>(); // 将所有的边都加入到小顶堆中 MinHeap<Edge<V, E>> heap = new MinHeap<>(edges,edgeComparator); // 初始化并查集,将每个顶点都作为一个集合 UnionFind<Vertex<V, E>> unionFind = new UnionFind<>(); vertices.forEach((V v , Vertex<V, E> vertex) ->{ unionFind.makeSet(vertex); }); while (!heap.isEmpty() && edgeInfos.size() < size){ // 获得权值最小的边 Edge<V, E> edge = heap.remove(); // 判断是否成环 ,成环就抛弃 当前边 if (unionFind.isSame(edge.from, edge.to)) continue; // 将最小的边加入到最小生成树中 edgeInfos.add(edge.info()); // 将当前边的两个顶点加入到并查集中 unionFind.union(edge.from, edge.to); } return edgeInfos; }
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。