@antv/algorithm
Version:
graph algorithm
339 lines • 12.2 kB
JavaScript
import dfs from './dfs';
import getConnectedComponents, { detectStrongConnectComponents } from './connected-component';
import { getNeighbors } from './util';
var detectDirectedCycle = function detectDirectedCycle(graphData) {
var cycle = null;
var _a = graphData.nodes,
nodes = _a === void 0 ? [] : _a;
var dfsParentMap = {};
// 所有没有被访问的节点集合
var unvisitedSet = {};
// 正在被访问的节点集合
var visitingSet = {};
// 所有已经被访问过的节点集合
var visitedSet = {};
// 初始化 unvisitedSet
nodes.forEach(function (node) {
unvisitedSet[node.id] = node;
});
var callbacks = {
enter: function enter(_a) {
var currentNode = _a.current,
previousNode = _a.previous;
if (visitingSet[currentNode]) {
// 如果当前节点正在访问中,则说明检测到环路了
cycle = {};
var currentCycleNode = currentNode;
var previousCycleNode = previousNode;
while (previousCycleNode !== currentNode) {
cycle[currentCycleNode] = previousCycleNode;
currentCycleNode = previousCycleNode;
previousCycleNode = dfsParentMap[previousCycleNode];
}
cycle[currentCycleNode] = previousCycleNode;
} else {
// 如果不存在正在访问集合中,则将其放入正在访问集合,并从未访问集合中删除
visitingSet[currentNode] = currentNode;
delete unvisitedSet[currentNode];
// 更新 DSF parents 列表
dfsParentMap[currentNode] = previousNode;
}
},
leave: function leave(_a) {
var currentNode = _a.current;
// 如果所有的节点的子节点都已经访问过了,则从正在访问集合中删除掉,并将其移入到已访问集合中,
// 同时也意味着当前节点的所有邻居节点都被访问过了
visitedSet[currentNode] = currentNode;
delete visitingSet[currentNode];
},
allowTraversal: function allowTraversal(_a) {
var nextNode = _a.next;
// 如果检测到环路则需要终止所有进一步的遍历,否则会导致无限循环遍历
if (cycle) {
return false;
}
// 仅允许遍历没有访问的节点,visitedSet 中的都已经访问过了
return !visitedSet[nextNode];
}
};
// 开始遍历节点
while (Object.keys(unvisitedSet).length) {
// 从第一个节点开始进行 DFS 遍历
var firsetUnVisitedKey = Object.keys(unvisitedSet)[0];
dfs(graphData, firsetUnVisitedKey, callbacks);
}
return cycle;
};
/**
* 检测无向图中的所有Base cycles
* refer: https://www.codeproject.com/Articles/1158232/Enumerating-All-Cycles-in-an-Undirected-Graph
* @param graph
* @param nodeIds 节点 ID 的数组
* @param include 包含或排除指定的节点
* @return [{[key: string]: INode}] 返回一组base cycles
*/
export var detectAllUndirectedCycle = function detectAllUndirectedCycle(graphData, nodeIds, include) {
var _a, _b;
if (include === void 0) {
include = true;
}
var allCycles = [];
var components = getConnectedComponents(graphData, false);
// loop through all connected components
for (var _i = 0, components_1 = components; _i < components_1.length; _i++) {
var component = components_1[_i];
if (!component.length) continue;
var root = component[0];
var rootId = root.id;
var stack = [root];
var parent_1 = (_a = {}, _a[rootId] = root, _a);
var used = (_b = {}, _b[rootId] = new Set(), _b);
// walk a spanning tree to find cycles
while (stack.length > 0) {
var curNode = stack.pop();
var curNodeId = curNode.id;
var neighbors = getNeighbors(curNodeId, graphData.edges);
var _loop_1 = function _loop_1(i) {
var _c;
var neighborId = neighbors[i];
var neighbor = graphData.nodes.find(function (node) {
return node.id === neighborId;
});
// const neighborId = neighbor.get('id');
if (neighborId === curNodeId) {
// 自环
allCycles.push((_c = {}, _c[neighborId] = curNode, _c));
} else if (!(neighborId in used)) {
// visit a new node
parent_1[neighborId] = curNode;
stack.push(neighbor);
used[neighborId] = new Set([curNode]);
} else if (!used[curNodeId].has(neighbor)) {
// a cycle found
var cycleValid = true;
var cyclePath = [neighbor, curNode];
var p = parent_1[curNodeId];
while (used[neighborId].size && !used[neighborId].has(p)) {
cyclePath.push(p);
if (p === parent_1[p.id]) break;else p = parent_1[p.id];
}
cyclePath.push(p);
if (nodeIds && include) {
// 如果有指定包含的节点
cycleValid = false;
if (cyclePath.findIndex(function (node) {
return nodeIds.indexOf(node.id) > -1;
}) > -1) {
cycleValid = true;
}
} else if (nodeIds && !include) {
// 如果有指定不包含的节点
if (cyclePath.findIndex(function (node) {
return nodeIds.indexOf(node.id) > -1;
}) > -1) {
cycleValid = false;
}
}
// 把 node list 形式转换为 cycle 的格式
if (cycleValid) {
var cycle = {};
for (var index = 1; index < cyclePath.length; index += 1) {
cycle[cyclePath[index - 1].id] = cyclePath[index];
}
if (cyclePath.length) {
cycle[cyclePath[cyclePath.length - 1].id] = cyclePath[0];
}
allCycles.push(cycle);
}
used[neighborId].add(curNode);
}
};
for (var i = 0; i < neighbors.length; i += 1) {
_loop_1(i);
}
}
}
return allCycles;
};
/**
* Johnson's algorithm, 时间复杂度 O((V + E)(C + 1))$ and space bounded by O(V + E)
* refer: https://www.cs.tufts.edu/comp/150GA/homeworks/hw1/Johnson%2075.PDF
* refer: https://networkx.github.io/documentation/stable/_modules/networkx/algorithms/cycles.html#simple_cycles
* @param graph
* @param nodeIds 节点 ID 的数组
* @param include 包含或排除指定的节点
* @return [{[key: string]: INode}] 返回所有的 simple cycles
*/
export var detectAllDirectedCycle = function detectAllDirectedCycle(graphData, nodeIds, include) {
if (include === void 0) {
include = true;
}
var path = []; // stack of nodes in current path
var blocked = new Set();
var B = []; // remember portions of the graph that yield no elementary circuit
var allCycles = [];
var idx2Node = {};
var node2Idx = {};
// 辅助函数: unblock all blocked nodes
var unblock = function unblock(thisNode) {
var stack = [thisNode];
while (stack.length > 0) {
var node = stack.pop();
if (blocked.has(node)) {
blocked.delete(node);
B[node.id].forEach(function (n) {
stack.push(n);
});
B[node.id].clear();
}
}
};
var circuit = function circuit(node, start, adjList) {
var closed = false; // whether a path is closed
if (nodeIds && include === false && nodeIds.indexOf(node.id) > -1) return closed;
path.push(node);
blocked.add(node);
var neighbors = adjList[node.id];
for (var i = 0; i < neighbors.length; i += 1) {
var neighbor = idx2Node[neighbors[i]];
if (neighbor === start) {
var cycle = {};
for (var index = 1; index < path.length; index += 1) {
cycle[path[index - 1].id] = path[index];
}
if (path.length) {
cycle[path[path.length - 1].id] = path[0];
}
allCycles.push(cycle);
closed = true;
} else if (!blocked.has(neighbor)) {
if (circuit(neighbor, start, adjList)) {
closed = true;
}
}
}
if (closed) {
unblock(node);
} else {
for (var i = 0; i < neighbors.length; i += 1) {
var neighbor = idx2Node[neighbors[i]];
if (!B[neighbor.id].has(node)) {
B[neighbor.id].add(node);
}
}
}
path.pop();
return closed;
};
var _a = graphData.nodes,
nodes = _a === void 0 ? [] : _a;
// Johnson's algorithm 要求给节点赋顺序,先按节点在数组中的顺序
for (var i = 0; i < nodes.length; i += 1) {
var node = nodes[i];
var nodeId = node.id;
node2Idx[nodeId] = i;
idx2Node[i] = node;
}
// 如果有指定包含的节点,则把指定节点排序在前,以便提早结束搜索
if (nodeIds && include) {
var _loop_2 = function _loop_2(i) {
var nodeId = nodeIds[i];
node2Idx[nodes[i].id] = node2Idx[nodeId];
node2Idx[nodeId] = 0;
idx2Node[0] = nodes.find(function (node) {
return node.id === nodeId;
});
idx2Node[node2Idx[nodes[i].id]] = nodes[i];
};
for (var i = 0; i < nodeIds.length; i++) {
_loop_2(i);
}
}
// 返回 节点顺序 >= nodeOrder 的强连通分量的adjList
var getMinComponentAdj = function getMinComponentAdj(components) {
var _a;
var minCompIdx;
var minIdx = Infinity;
// Find least component and the lowest node
for (var i = 0; i < components.length; i += 1) {
var comp = components[i];
for (var j = 0; j < comp.length; j++) {
var nodeIdx_1 = node2Idx[comp[j].id];
if (nodeIdx_1 < minIdx) {
minIdx = nodeIdx_1;
minCompIdx = i;
}
}
}
var component = components[minCompIdx];
var adjList = [];
for (var i = 0; i < component.length; i += 1) {
var node = component[i];
adjList[node.id] = [];
for (var _i = 0, _b = getNeighbors(node.id, graphData.edges, 'target').filter(function (n) {
return component.map(function (c) {
return c.id;
}).indexOf(n) > -1;
}); _i < _b.length; _i++) {
var neighbor = _b[_i];
// 对自环情况 (点连向自身) 特殊处理:记录自环,但不加入adjList
if (neighbor === node.id && !(include === false && nodeIds.indexOf(node.id) > -1)) {
allCycles.push((_a = {}, _a[node.id] = node, _a));
} else {
adjList[node.id].push(node2Idx[neighbor]);
}
}
}
return {
component: component,
adjList: adjList,
minIdx: minIdx
};
};
var nodeIdx = 0;
while (nodeIdx < nodes.length) {
var subgraphNodes = nodes.filter(function (n) {
return node2Idx[n.id] >= nodeIdx;
});
var sccs = detectStrongConnectComponents({
nodes: subgraphNodes,
edges: graphData.edges
}).filter(function (component) {
return component.length > 1;
});
if (sccs.length === 0) break;
var scc = getMinComponentAdj(sccs);
var minIdx = scc.minIdx,
adjList = scc.adjList,
component = scc.component;
if (component.length > 1) {
component.forEach(function (node) {
B[node.id] = new Set();
});
var startNode = idx2Node[minIdx];
// startNode 不在指定要包含的节点中,提前结束搜索
if (nodeIds && include && nodeIds.indexOf(startNode.id) === -1) return allCycles;
circuit(startNode, startNode, adjList);
nodeIdx = minIdx + 1;
} else {
break;
}
}
return allCycles;
};
/**
* 查找图中所有满足要求的圈
* @param graph
* @param directed 是否为有向图
* @param nodeIds 节点 ID 的数组,若不指定,则返回图中所有的圈
* @param include 包含或排除指定的节点
* @return [{[key: string]: Node}] 包含所有环的数组,每个环用一个Object表示,其中key为节点id,value为该节点在环中指向的下一个节点
*/
export var detectAllCycles = function detectAllCycles(graphData, directed, nodeIds, include) {
if (include === void 0) {
include = true;
}
if (directed) return detectAllDirectedCycle(graphData, nodeIds, include);
return detectAllUndirectedCycle(graphData, nodeIds, include);
};
export default detectDirectedCycle;