2018-06-05 23:58:10 +08:00
|
|
|
/**
|
|
|
|
* @fileOverview 最大生成森林
|
|
|
|
* @author huangtonger@aliyun.com
|
|
|
|
*/
|
2018-06-14 11:50:35 +08:00
|
|
|
const G6 = require('@antv/g6');
|
2018-06-05 23:58:10 +08:00
|
|
|
const { Util } = G6;
|
|
|
|
const maxSpanningTree = require('./maxSpanningTree');
|
|
|
|
|
|
|
|
function maxSpanningForest(nodes, edges) {
|
|
|
|
const connectedSubsets = [];
|
|
|
|
const forest = {
|
|
|
|
nodes: [],
|
|
|
|
edges: [],
|
|
|
|
maxRankNode: null
|
|
|
|
};
|
|
|
|
const nodeMap = {};
|
|
|
|
let maxRank = -Infinity;
|
|
|
|
Util.each(nodes, node => {
|
|
|
|
node.links = [];
|
|
|
|
node.edges = [];
|
|
|
|
nodeMap[node.id] = node;
|
|
|
|
if (Util.isNil(node.weight)) {
|
|
|
|
node.weight = 1;
|
|
|
|
}
|
|
|
|
if (Util.isNil(node.rank)) {
|
|
|
|
if (node.weight) {
|
|
|
|
node.rank = node.weight;
|
|
|
|
} else {
|
|
|
|
node.rank = 1;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
});
|
|
|
|
Util.each(edges, (edge, i) => {
|
|
|
|
const source = nodeMap[edge.source];
|
|
|
|
const target = nodeMap[edge.target];
|
|
|
|
source.links.push(nodes.indexOf(target));
|
|
|
|
target.links.push(nodes.indexOf(source));
|
|
|
|
source.edges.push(i);
|
|
|
|
target.edges.push(i);
|
|
|
|
if (Util.isNil(edge.weight)) {
|
|
|
|
edge.weight = 1;
|
|
|
|
}
|
|
|
|
});
|
|
|
|
Util.each(nodes, (node, i) => {
|
|
|
|
if (!node.visited) {
|
|
|
|
connectedSubsets.push(getConnectedSubset(i, nodes, edges));
|
|
|
|
}
|
|
|
|
});
|
|
|
|
Util.each(nodes, node => {
|
|
|
|
delete node.links;
|
|
|
|
delete node.edges;
|
|
|
|
delete node.visited;
|
|
|
|
});
|
|
|
|
Util.each(connectedSubsets, connectedSubset => {
|
|
|
|
const tree = maxSpanningTree(connectedSubset);
|
|
|
|
const root = connectedSubset.root;
|
|
|
|
forest.nodes = forest.nodes.concat(tree.nodes);
|
|
|
|
forest.edges = forest.edges.concat(tree.edges);
|
|
|
|
if (root.rank > maxRank) {
|
|
|
|
maxRank = root.rank;
|
|
|
|
forest.maxRankNode = root;
|
|
|
|
}
|
|
|
|
});
|
|
|
|
return forest;
|
|
|
|
}
|
|
|
|
// 获取连通子集
|
|
|
|
function getConnectedSubset(start, nodes, edges) {
|
|
|
|
const connectedSubset = {
|
|
|
|
nodes: [],
|
|
|
|
edges: [],
|
|
|
|
root: null
|
|
|
|
};
|
|
|
|
const edgeMap = {};
|
|
|
|
const subEdges = [];
|
|
|
|
let maxRank = -Infinity;
|
|
|
|
dfs(start, nodes, index => {
|
|
|
|
const node = nodes[index];
|
|
|
|
if (node.rank > maxRank) {
|
|
|
|
maxRank = node.rank;
|
|
|
|
connectedSubset.root = node;
|
|
|
|
}
|
|
|
|
connectedSubset.nodes.push(node);
|
|
|
|
connectedSubset.edges = connectedSubset.edges.concat(node.edges);
|
|
|
|
});
|
|
|
|
// 边索引去重
|
|
|
|
Util.each(connectedSubset.edges, edgeIndex => {
|
|
|
|
if (!edgeMap[edgeIndex]) {
|
|
|
|
subEdges.push(edges[edgeIndex]);
|
|
|
|
edgeMap[edgeIndex] = true;
|
|
|
|
}
|
|
|
|
});
|
|
|
|
connectedSubset.edges = subEdges;
|
|
|
|
return connectedSubset;
|
|
|
|
}
|
|
|
|
|
|
|
|
// 深度优先遍历算法
|
|
|
|
function dfs(start, nodes, callback) {
|
|
|
|
const listToExplore = [ start ];
|
|
|
|
nodes[ start ].visited = true;
|
|
|
|
callback(start);
|
|
|
|
while (listToExplore.length > 0) {
|
|
|
|
const nodeIndex = listToExplore.pop();
|
|
|
|
nodes[ nodeIndex ].links.forEach(function(childIndex) {
|
|
|
|
if (!nodes[ childIndex ].visited) {
|
|
|
|
nodes[ childIndex ].visited = true;
|
|
|
|
callback(childIndex);
|
|
|
|
listToExplore.push(childIndex);
|
|
|
|
}
|
|
|
|
});
|
|
|
|
}
|
|
|
|
}
|
|
|
|
module.exports = maxSpanningForest;
|