参考
-
https://ctolib.com/yueyunyue-liteflow.html
-
DAG有向无环图:https://blog.csdn.net/yanwumuxi/article/details/67633766
-
算法精解:DAG有向无环图:http://www.cnblogs.com/Evsward/p/dag.html
-
https://time.geekbang.org/column/article/76207
拓扑排序
定义:将有向图中的顶点以线性方式进行排序。即对于任何连接自顶点u到顶点v的有向边uv,在最后的排序结果中,顶点u总是在顶点v的前面。有向边总是按照排序顺序向前,而不是向后。若用1~N来对顶点进行编号,编号代表顶点在拓扑排序中的顺序。N是有向图G的顶点个数。因此,只要能对顶点按照1~N进行编号,有向边总是按照编号顺序向前(有向边可以是从编号1的节点到3的节点,不能是从编号3的节点到编号1的节点)。
-
一个有向有环图=>没有拓扑排序
-
一个有向无环图=>有拓扑排序
-
一个有向无环图一定存在一个节点(sink vertex),这个节点只有指向它的边,没有指向别人的边。
假设有向无环图不存在这个节点,那么从一个节点出发,一定可以找到下一个节点(有指向别人的边),假设一共有N个节点,我从一个节点出发走N次,那我至少遍历了N+1个节点,这说明其中某个节点我至少遍历了两次,这个图是一个有向有环图,与条件矛盾,因此一定存在这个节点。
容易得知,在拓扑排序中最后位置的节点一定是一个sink vertex。
算法的思想就是先找到那个sink vertex,将那个sink vertex标记为N,然后从剩下N-1个节点中找出其他的sink vertex,再标记为N-1……一直到标记到第一个节点为止。
更正:若一个节点,在未标记的节点集合中只有指向它的边(在已标记的节点集合中可以有指向别人的边),这个节点就是最大位置的节点。
广度优先遍历
https://yq.aliyun.com/articles/14429
/**
* 判断是否有环
* https://time.geekbang.org/column/article/76207
* @param graph
* @return
*/
public boolean hasCycle(Map<NodeVO, LinkedList<NodeVO>> graph) {
List<NodeVO> list = topoSortByKahn(graph);
if(graph.size() > list.size()){
return true;
}
return false;
}
/**
* 拓扑排序 Kahn 算法
* @param graph
*/
public List<NodeVO> topoSortByKahn(Map<NodeVO, LinkedList<NodeVO>> graph){
List<NodeVO> result = new ArrayList<>();
Map<NodeVO, Integer> inDegree = new HashMap<>();
for(Map.Entry<NodeVO, LinkedList<NodeVO>> entry: graph.entrySet()){
inDegree.put(entry.getKey(), 0);
}
for(Map.Entry<NodeVO, LinkedList<NodeVO>> entry: graph.entrySet()){
System.out.print("key="+entry.getKey().getId()+",value=");
for(NodeVO nodeVO: entry.getValue()){
/**
* entry.key -> nodeVO
*/
inDegree.put(nodeVO, inDegree.get(nodeVO) + 1);
System.out.print(nodeVO.getId()+",");
}
System.out.println();
}
LinkedList<NodeVO> queue = new LinkedList<>();
for (Map.Entry<NodeVO, Integer> entry: inDegree.entrySet()) {
if (entry.getValue() == 0) {
queue.add(entry.getKey());
}
}
System.out.println(queue.get(0).getId());
while (!queue.isEmpty()) {
NodeVO i = queue.remove();
System.out.print("->" + i.getId());
result.add(i);
for(NodeVO nodeVO: graph.get(i)){
inDegree.put(nodeVO, inDegree.get(nodeVO) -1);
if (inDegree.get(nodeVO) == 0) {
queue.add(nodeVO);
}
}
}
return result;
}
/**
* 找出头节点(入度为0)
* @param graph
* @return
*/
public NodeVO getHeadNode(Map<NodeVO, LinkedList<NodeVO>> graph){
Map<NodeVO, Integer> inDegree = new HashMap<>();
for(Map.Entry<NodeVO, LinkedList<NodeVO>> entry: graph.entrySet()){
inDegree.put(entry.getKey(), 0);
}
for(Map.Entry<NodeVO, LinkedList<NodeVO>> entry: graph.entrySet()){
System.out.print("key="+entry.getKey().getId()+",value=");
for(NodeVO nodeVO: entry.getValue()){
/**
* entry.key -> nodeVO
*/
inDegree.put(nodeVO, inDegree.get(nodeVO) + 1);
System.out.print(nodeVO.getId()+",");
}
System.out.println();
}
for (Map.Entry<NodeVO, Integer> entry: inDegree.entrySet()) {
if (entry.getValue() == 0) {
System.out.print(entry.getKey().getId());
return entry.getKey();
}
}
return null;
}
public int getAllHeadNode(Map<NodeVO, LinkedList<NodeVO>> graph){
int result = 0;
Map<NodeVO, Integer> inDegree = new HashMap<>();
for(Map.Entry<NodeVO, LinkedList<NodeVO>> entry: graph.entrySet()){
inDegree.put(entry.getKey(), 0);
}
for(Map.Entry<NodeVO, LinkedList<NodeVO>> entry: graph.entrySet()){
System.out.print("key="+entry.getKey().getId()+",value=");
for(NodeVO nodeVO: entry.getValue()){
/**
* entry.key -> nodeVO
*/
inDegree.put(nodeVO, inDegree.get(nodeVO) + 1);
System.out.print(nodeVO.getId()+",");
}
System.out.println();
}
for (Map.Entry<NodeVO, Integer> entry: inDegree.entrySet()) {
/**
* 起点
*/
if (entry.getValue() == 0) {
System.out.print(entry.getKey().getId());
result++;
}
}
return result;
}
/**
* 广度优先遍历
* @param graph
*/
public void bfs(Map<NodeVO, LinkedList<NodeVO>> graph){
Queue<NodeVO> queue = new LinkedList<>();
queue.offer(getHeadNode(graph));
Map<NodeVO, Boolean> visitedMap = new HashMap<>();
for(Map.Entry<NodeVO, LinkedList<NodeVO>> entry: graph.entrySet()){
visitedMap.put(entry.getKey(), false);
}
while(!queue.isEmpty()){
//从队列中删除下一个节点
NodeVO v = queue.poll();
//将该节点的所有邻接节点加入队列中
for(NodeVO w : graph.get(v)){
//如果没有标记就标记
if(!visitedMap.get(w)){
visitedMap.put(w,true);
System.out.print("->" + w.getId());
queue.add(w);
}
}
}
}
/**
* 深度优先遍历
* @param graph
*/
public void dfs(Map<NodeVO, LinkedList<NodeVO>> graph){
searchTraversing(graph,getHeadNode(graph),new ArrayList<>());
}
/**
* 深度遍历
* DFSearch
* @param node
* 当前节点
* @param visited
* 被访问过的节点列表
*/
public void searchTraversing(Map<NodeVO, LinkedList<NodeVO>> graph, NodeVO node, List<NodeVO> visited) {
// 判断是否遍历过
if (visited.contains(node)) {
return;
}
visited.add(node);
System.out.print("->" + node.getId());
for (int i = 0; i < graph.get(node).size(); i++) {
searchTraversing(graph,graph.get(node).get(i), visited);
}
}