|
import { error } from '../../util'; |
|
|
|
const sqrt2 = Math.sqrt(2); |
|
|
|
|
|
|
|
|
|
const collapse = function( edgeIndex, nodeMap, remainingEdges ){ |
|
if( remainingEdges.length === 0 ){ |
|
error(`Karger-Stein must be run on a connected (sub)graph`); |
|
} |
|
|
|
let edgeInfo = remainingEdges[ edgeIndex ]; |
|
let sourceIn = edgeInfo[1]; |
|
let targetIn = edgeInfo[2]; |
|
let partition1 = nodeMap[ sourceIn ]; |
|
let partition2 = nodeMap[ targetIn ]; |
|
let newEdges = remainingEdges; |
|
|
|
|
|
for( let i = newEdges.length - 1; i >=0; i-- ){ |
|
let edge = newEdges[i]; |
|
let src = edge[1]; |
|
let tgt = edge[2]; |
|
|
|
if( |
|
( nodeMap[ src ] === partition1 && nodeMap[ tgt ] === partition2 ) || |
|
( nodeMap[ src ] === partition2 && nodeMap[ tgt ] === partition1 ) |
|
){ |
|
newEdges.splice(i, 1); |
|
} |
|
} |
|
|
|
|
|
for( let i = 0; i < newEdges.length; i++ ){ |
|
let edge = newEdges[i]; |
|
|
|
if( edge[1] === partition2 ){ |
|
newEdges[i] = edge.slice(); |
|
newEdges[i][1] = partition1; |
|
} else if( edge[2] === partition2 ){ |
|
newEdges[i] = edge.slice(); |
|
newEdges[i][2] = partition1; |
|
} |
|
} |
|
|
|
|
|
for( let i = 0; i < nodeMap.length; i++ ){ |
|
if( nodeMap[i] === partition2 ){ |
|
nodeMap[i] = partition1; |
|
} |
|
} |
|
|
|
return newEdges; |
|
}; |
|
|
|
|
|
const contractUntil = function( metaNodeMap, remainingEdges, size, sizeLimit ){ |
|
while( size > sizeLimit ){ |
|
|
|
let edgeIndex = Math.floor( (Math.random() * remainingEdges.length) ); |
|
|
|
|
|
remainingEdges = collapse( edgeIndex, metaNodeMap, remainingEdges ); |
|
|
|
size--; |
|
} |
|
|
|
return remainingEdges; |
|
}; |
|
|
|
const elesfn = ({ |
|
|
|
|
|
|
|
kargerStein: function(){ |
|
let { nodes, edges } = this.byGroup(); |
|
edges.unmergeBy(edge => edge.isLoop()); |
|
|
|
let numNodes = nodes.length; |
|
let numEdges = edges.length; |
|
let numIter = Math.ceil( Math.pow( Math.log( numNodes ) / Math.LN2, 2 ) ); |
|
let stopSize = Math.floor( numNodes / sqrt2 ); |
|
|
|
if( numNodes < 2 ){ |
|
error( 'At least 2 nodes are required for Karger-Stein algorithm' ); |
|
return undefined; |
|
} |
|
|
|
|
|
|
|
let edgeIndexes = []; |
|
for( let i = 0; i < numEdges; i++ ){ |
|
let e = edges[ i ]; |
|
edgeIndexes.push([ i, nodes.indexOf(e.source()), nodes.indexOf(e.target()) ]); |
|
} |
|
|
|
|
|
let minCutSize = Infinity; |
|
let minCutEdgeIndexes = []; |
|
let minCutNodeMap = new Array(numNodes); |
|
|
|
|
|
let metaNodeMap = new Array(numNodes); |
|
let metaNodeMap2 = new Array(numNodes); |
|
|
|
let copyNodesMap = (from, to) => { |
|
for( let i = 0; i < numNodes; i++ ){ |
|
to[i] = from[i]; |
|
} |
|
}; |
|
|
|
|
|
for( let iter = 0; iter <= numIter; iter++ ){ |
|
|
|
for( let i = 0; i < numNodes; i++ ){ metaNodeMap[i] = i; } |
|
|
|
|
|
let edgesState = contractUntil( metaNodeMap, edgeIndexes.slice(), numNodes, stopSize ); |
|
let edgesState2 = edgesState.slice(); |
|
|
|
|
|
copyNodesMap(metaNodeMap, metaNodeMap2); |
|
|
|
|
|
let res1 = contractUntil( metaNodeMap, edgesState, stopSize, 2 ); |
|
let res2 = contractUntil( metaNodeMap2, edgesState2, stopSize, 2 ); |
|
|
|
|
|
if( res1.length <= res2.length && res1.length < minCutSize ){ |
|
minCutSize = res1.length; |
|
minCutEdgeIndexes = res1; |
|
copyNodesMap(metaNodeMap, minCutNodeMap); |
|
} else if( res2.length <= res1.length && res2.length < minCutSize ){ |
|
minCutSize = res2.length; |
|
minCutEdgeIndexes = res2; |
|
copyNodesMap(metaNodeMap2, minCutNodeMap); |
|
} |
|
} |
|
|
|
|
|
|
|
let cut = this.spawn( minCutEdgeIndexes.map(e => edges[e[0]]) ); |
|
let partition1 = this.spawn(); |
|
let partition2 = this.spawn(); |
|
|
|
|
|
let witnessNodePartition = minCutNodeMap[0]; |
|
for( let i = 0; i < minCutNodeMap.length; i++ ){ |
|
let partitionId = minCutNodeMap[i]; |
|
let node = nodes[i]; |
|
|
|
if( partitionId === witnessNodePartition ){ |
|
partition1.merge( node ); |
|
} else { |
|
partition2.merge( node ); |
|
} |
|
} |
|
|
|
|
|
const constructComponent = (subset) => { |
|
const component = this.spawn(); |
|
|
|
subset.forEach(node => { |
|
component.merge(node); |
|
|
|
node.connectedEdges().forEach(edge => { |
|
|
|
if (this.contains(edge) && !cut.contains(edge)) { |
|
component.merge(edge); |
|
} |
|
}); |
|
}); |
|
|
|
return component; |
|
}; |
|
|
|
const components = [ |
|
constructComponent(partition1), |
|
constructComponent(partition2) |
|
]; |
|
|
|
let ret = { |
|
cut, |
|
components, |
|
|
|
|
|
|
|
partition1, |
|
partition2 |
|
}; |
|
|
|
return ret; |
|
} |
|
}); |
|
|
|
|
|
export default elesfn; |
|
|