You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
353 lines
14 KiB
353 lines
14 KiB
|
4 months ago
|
"use strict";
|
||
|
|
|
||
|
|
function _typeof(obj) { "@babel/helpers - typeof"; return _typeof = "function" == typeof Symbol && "symbol" == typeof Symbol.iterator ? function (obj) { return typeof obj; } : function (obj) { return obj && "function" == typeof Symbol && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; }, _typeof(obj); }
|
||
|
|
Object.defineProperty(exports, "__esModule", {
|
||
|
|
value: true
|
||
|
|
});
|
||
|
|
exports.detectAllUndirectedCycle = exports.detectAllDirectedCycle = exports.detectAllCycles = exports.default = void 0;
|
||
|
|
var _dfs = _interopRequireDefault(require("./dfs"));
|
||
|
|
var _connectedComponent = _interopRequireWildcard(require("./connected-component"));
|
||
|
|
var _util = require("./util");
|
||
|
|
function _getRequireWildcardCache(nodeInterop) { if (typeof WeakMap !== "function") return null; var cacheBabelInterop = new WeakMap(); var cacheNodeInterop = new WeakMap(); return (_getRequireWildcardCache = function _getRequireWildcardCache(nodeInterop) { return nodeInterop ? cacheNodeInterop : cacheBabelInterop; })(nodeInterop); }
|
||
|
|
function _interopRequireWildcard(obj, nodeInterop) { if (!nodeInterop && obj && obj.__esModule) { return obj; } if (obj === null || _typeof(obj) !== "object" && typeof obj !== "function") { return { default: obj }; } var cache = _getRequireWildcardCache(nodeInterop); if (cache && cache.has(obj)) { return cache.get(obj); } var newObj = {}; var hasPropertyDescriptor = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var key in obj) { if (key !== "default" && Object.prototype.hasOwnProperty.call(obj, key)) { var desc = hasPropertyDescriptor ? Object.getOwnPropertyDescriptor(obj, key) : null; if (desc && (desc.get || desc.set)) { Object.defineProperty(newObj, key, desc); } else { newObj[key] = obj[key]; } } } newObj.default = obj; if (cache) { cache.set(obj, newObj); } return newObj; }
|
||
|
|
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
|
||
|
|
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];
|
||
|
|
(0, _dfs.default)(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
|
||
|
|
*/
|
||
|
|
var detectAllUndirectedCycle = function detectAllUndirectedCycle(graphData, nodeIds, include) {
|
||
|
|
var _a, _b;
|
||
|
|
if (include === void 0) {
|
||
|
|
include = true;
|
||
|
|
}
|
||
|
|
var allCycles = [];
|
||
|
|
var components = (0, _connectedComponent.default)(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 = (0, _util.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
|
||
|
|
*/
|
||
|
|
exports.detectAllUndirectedCycle = detectAllUndirectedCycle;
|
||
|
|
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 = (0, _util.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 = (0, _connectedComponent.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为该节点在环中指向的下一个节点
|
||
|
|
*/
|
||
|
|
exports.detectAllDirectedCycle = detectAllDirectedCycle;
|
||
|
|
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);
|
||
|
|
};
|
||
|
|
exports.detectAllCycles = detectAllCycles;
|
||
|
|
var _default = detectDirectedCycle;
|
||
|
|
exports.default = _default;
|