File size: 5,894 Bytes
bc20498 |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 |
import { error } from '../../util';
const sqrt2 = Math.sqrt(2);
// Function which colapses 2 (meta) nodes into one
// Updates the remaining edge lists
// Receives as a paramater the edge which causes the collapse
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; // re-use array
// Delete all edges between partition1 and partition2
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);
}
}
// All edges pointing to partition2 should now point to partition1
for( let i = 0; i < newEdges.length; i++ ){
let edge = newEdges[i];
if( edge[1] === partition2 ){ // Check source
newEdges[i] = edge.slice(); // copy
newEdges[i][1] = partition1;
} else if( edge[2] === partition2 ){ // Check target
newEdges[i] = edge.slice(); // copy
newEdges[i][2] = partition1;
}
}
// Move all nodes from partition2 to partition1
for( let i = 0; i < nodeMap.length; i++ ){
if( nodeMap[i] === partition2 ){
nodeMap[i] = partition1;
}
}
return newEdges;
};
// Contracts a graph until we reach a certain number of meta nodes
const contractUntil = function( metaNodeMap, remainingEdges, size, sizeLimit ){
while( size > sizeLimit ){
// Choose an edge randomly
let edgeIndex = Math.floor( (Math.random() * remainingEdges.length) );
// Collapse graph based on edge
remainingEdges = collapse( edgeIndex, metaNodeMap, remainingEdges );
size--;
}
return remainingEdges;
};
const elesfn = ({
// Computes the minimum cut of an undirected graph
// Returns the correct answer with high probability
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;
}
// Now store edge destination as indexes
// Format for each edge (edge index, source node index, target node index)
let edgeIndexes = [];
for( let i = 0; i < numEdges; i++ ){
let e = edges[ i ];
edgeIndexes.push([ i, nodes.indexOf(e.source()), nodes.indexOf(e.target()) ]);
}
// We will store the best cut found here
let minCutSize = Infinity;
let minCutEdgeIndexes = [];
let minCutNodeMap = new Array(numNodes);
// Initial meta node partition
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];
}
};
// Main loop
for( let iter = 0; iter <= numIter; iter++ ){
// Reset meta node partition
for( let i = 0; i < numNodes; i++ ){ metaNodeMap[i] = i; }
// Contract until stop point (stopSize nodes)
let edgesState = contractUntil( metaNodeMap, edgeIndexes.slice(), numNodes, stopSize );
let edgesState2 = edgesState.slice(); // copy
// Create a copy of the colapsed nodes state
copyNodesMap(metaNodeMap, metaNodeMap2);
// Run 2 iterations starting in the stop state
let res1 = contractUntil( metaNodeMap, edgesState, stopSize, 2 );
let res2 = contractUntil( metaNodeMap2, edgesState2, stopSize, 2 );
// Is any of the 2 results the best cut so far?
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);
}
} // end of main loop
// Construct result
let cut = this.spawn( minCutEdgeIndexes.map(e => edges[e[0]]) );
let partition1 = this.spawn();
let partition2 = this.spawn();
// traverse metaNodeMap for best cut
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 );
}
}
// construct components corresponding to each disjoint subset of nodes
const constructComponent = (subset) => {
const component = this.spawn();
subset.forEach(node => {
component.merge(node);
node.connectedEdges().forEach(edge => {
// ensure edge is within calling collection and edge is not in cut
if (this.contains(edge) && !cut.contains(edge)) {
component.merge(edge);
}
});
});
return component;
};
const components = [
constructComponent(partition1),
constructComponent(partition2)
];
let ret = {
cut,
components,
// n.b. partitions are included to be compatible with the old api spec
// (could be removed in a future major version)
partition1,
partition2
};
return ret;
}
}); // elesfn
export default elesfn;
|