// Counts the number of independent sets in a graph, such that: // - The vertices in P are legal choices for our IS, initially set to all vertices in the graph // - None of the vertices in X are used, initially empty // The performDFS boolean is used to check if we should perform a check for connectedness on this level of the recursion private static long Compute(Graph graph, BitSet P, BitSet X, bool performDFS) { // Base case, when P and X are both empty we cannot expand the IS if (P.IsEmpty && X.IsEmpty) return 1; // Base case, if a vertex w in X has no neighbor in P, then it means that this IS will never get maximal // since we could always include w. Thus, the IS will not be valid and we return 0. foreach (int w in X) if ((graph.OpenNeighborhood(w) * P).IsEmpty) return 0; long count = 0; // If a DFS is needed we check if the graph induced by (P + X) is still connected. // If the graph is disconnected, in components c1,...,cn then we can simply count the IS of all these components // after which we simply multiply these numbers. if (performDFS) { if (!DepthFirstSearch.Connected(graph, P + X)) { count = 1; foreach (BitSet component in DepthFirstSearch.ConnectedComponents(graph, P + X)) count *= Compute(graph, component * P, component * X, false); return count; } } // Select a pivot in P to branch on // In this case we pick the vertex with the largest degree int maxDegree = -1; ; int pivot = -1; foreach (int u in P) { int deg = graph.Degree(u); if (deg > maxDegree) { maxDegree = deg; pivot = u; } } // There should always be a pivot after the selection procedure if (pivot == -1) throw new Exception("Pivot has not been selected"); // We branch on the pivot, one branch we include the pivot in the IS. // This might possibly disconnect the subgraph G(P + X), thus we set the performDFS boolean to true. count = Compute(graph, P - graph.ClosedNeighborhood(pivot), X - graph.OpenNeighborhood(pivot), true); // One branch we exclude the pivot of the IS. This will not cause the graph to get possibly disconnected count += Compute(graph, P - pivot, X + pivot, false); return count; }
// Uses depth-first search to check if the graph induced by the subgraph given as a parameter is connected // In other words, we retreive all edges from the original graph and check the subgraph for connectedness public static bool Connected(Graph graph, BitSet subgraph) { // Vertices that are visited Set<int> visited = new Set<int>(); // Stack of vertices yet to visit Stack<int> stack = new Stack<int>(); // Initial vertex int s = subgraph.First(); stack.Push(s); // Continue while there are vertices on the stack while (stack.Count > 0) { int v = stack.Pop(); // If we have not encountered this vertex before, then we check for all neighbors if they are part of the subgraph // If a neighbor is part of the subgraph it means that we have to push it on the stack to explore it at a later stage if (!visited.Contains(v)) { visited.Add(v); foreach (int w in graph.OpenNeighborhood(v)) if (subgraph.Contains(w)) stack.Push(w); } } // If we visited an equal number of vertices as there are vertices in the subgraph then the subgraph is connected return visited.Count == subgraph.Count; }
/*************************/ // Selecting initial vertices /*************************/ // Returns a vertex on the last level of a Breadth-first search (BFS) public static int BFS(Graph graph, int init) { // BFS working queue Queue<int> queue = new Queue<int>(); // Vertices that have already been visited Set<int> visited = new Set<int>(); // Initial vertex is given as input visited.Add(init); queue.Enqueue(init); int current = init; // While there is still a vertex in the queue... while (queue.Count > 0) { //... select the first vertex and process it current = queue.Dequeue(); foreach (int w in graph.OpenNeighborhood(current)) { // Enqueue all neighbors that have not been processed yet if (!visited.Contains(w)) { visited.Add(w); queue.Enqueue(w); } } } // This is the last vertex that has been processed, thus a vertex that is on the last level of the BFS search return current; }
private static List<int> ComputeSequence(Graph graph, BitSet connectedComponent, CandidateStrategy candidateStrategy, int init, out int value) { int n = graph.Size; List<int> sequence = new List<int>() { init }; BitSet left = new BitSet(0, n) { init }; BitSet right = connectedComponent - init; // Initially we store the empty set and the set with init as the representative, ie N(init) * right Set<BitSet> UN_left = new Set<BitSet>() { new BitSet(0, n), graph.OpenNeighborhood(init) * right }; value = int.MinValue; while (!right.IsEmpty) { Set<BitSet> UN_chosen = new Set<BitSet>(); int chosen = Heuristics.TrivialCases(graph, left, right); if (chosen != -1) { UN_chosen = IncrementUN(graph, left, UN_left, chosen); } // If chosen has not been set it means that no trivial case was found // Depending on the criteria for the next vertex we call a different algorithm else { BitSet candidates = Heuristics.Candidates(graph, left, right, candidateStrategy); int min = int.MaxValue; foreach (int v in candidates) { Set<BitSet> UN_v = IncrementUN(graph, left, UN_left, v); if (UN_v.Count < min) { chosen = v; UN_chosen = UN_v; min = UN_v.Count; } } } // This should never happen if (chosen == -1) throw new Exception("No vertex is chosen for next step in the heuristic"); // Add/remove the next vertex in the appropiate sets sequence.Add(chosen); left.Add(chosen); right.Remove(chosen); UN_left = UN_chosen; value = Math.Max(UN_chosen.Count, value); } return sequence; }
// This constructor returns a new bipartite graph by putting all vertices in 'left' on one side, and 'right' on the other side // There will be an edge between two vertices if there was an edge in the original graph public BipartiteGraph(Graph graph, BitSet _left, BitSet _right) : this(_left, _right, _left.Count + _right.Count) { Dictionary<int, int> mapping = new Dictionary<int, int>(); int i = 0; foreach (int v in left + right) mapping[v] = i++; foreach (int v in left) foreach (int w in graph.OpenNeighborhood(v) * right) Connect(mapping[v], mapping[w]); }
// Counts the number of independent sets in a graph, such that: // - All vertices in R are included in the independent set, initially empty // - Some of the vertices in P are included in the independent set, initially all vertices of the graph // - None of the vertices in X are included in the independent set, initially empty private static long Compute(Graph graph, BitSet R, BitSet P, BitSet X) { // Base case, when P and X are both empty we cannot expand the IS if (P.IsEmpty && X.IsEmpty) return 1; long count = 0; int pivot = -1; int min = int.MaxValue; // Procedure to find a pivot // The idea is that in any maximal IS, either vertex u or a neighbor of u is included (else we could expand by adding u to the IS) // We find the u with the smallest neighborhood, so that we will keep the number of branches low foreach (int u in (P + X)) { int size = (P * graph.OpenNeighborhood(u)).Count; if (size < min) { min = size; pivot = u; } } // There should always be a pivot after the selection procedure, else P and X should both have been empty if (pivot == -1) throw new Exception("Pivot has not been selected"); // Foreach vertex v in the set containing the legal choices of the the closed neighborhood of the pivot, // we include each choice in the IS and compute how many maximal IS will include v by going into recursion foreach (int v in (P * graph.ClosedNeighborhood(pivot))) { count += Compute(graph, R + v, P - graph.ClosedNeighborhood(v), X - graph.OpenNeighborhood(v)); P.Remove(v); X.Add(v); } return count; }
// Builds a neighborhood for a set X from the ground on up, without relying on what has been saved previously in O(n^2) time public dNeighborhood(BitSet X, BitSet vector, Graph graph) { // O(n) time copy Vector = vector.Copy(); Occurrences = new Dictionary<int, int>(); // Loops in O(|Vector|) time over all vertices in the vector foreach (int v in Vector) { // Bitset operation of O(n) time BitSet nv = graph.OpenNeighborhood(v) * X; Occurrences[v] = Math.Min(nv.Count, dValue); } }
// Counts the number of independent sets in a graph, such that: // - All vertices in R are included in the independent set, initially empty // - Some of the vertices in P are included in the independent set, initially all vertices of the graph // - None of the vertices in X are included in the independent set, initially empty private static int Compute(Graph graph, BitSet R, BitSet P, BitSet X) { // Base case, when P and X are both empty we cannot expand the IS if (P.IsEmpty && X.IsEmpty) return 1; int count = 0; BitSet copy = P.Copy(); // Foreach vertex v in P we include it in the IS and compute how many maximal IS will include v by going into recursion. foreach (int v in copy) { count += Compute(graph, R + v, P - graph.ClosedNeighborhood(v), X - graph.OpenNeighborhood(v)); P.Remove(v); X.Add(v); } return count; }
// Given a vertex w, we can update the dNeighborhood of a set X to reflect the dNeighborhood of the set X + w in O(n) time public dNeighborhood CopyAndUpdate(Graph graph, int w) { // initialize an empty dNeighborhood in O(|Vector|) time dNeighborhood nx = new dNeighborhood(Vector); // Copy all values in O(|Vector|) time foreach (int v in Vector) nx.Occurrences[v] = Occurrences[v]; // Foreach vertex in N(w) * Vector they will appear one extra time in the multiset // This operation take O(n) time because of the bitset operation foreach (int v in graph.OpenNeighborhood(w) * Vector) nx.Occurrences[v] = Math.Min(dValue, nx.Occurrences[v] + 1); return nx; }
private void FillTable(Graph graph, List<int> sequence) { int n = graph.Size; // Processed vertices BitSet left = new BitSet(0, n); // Unprocessed vertices BitSet right = graph.Vertices; // Lists of representatives that we keep track of on each level of the algorithm LinearRepresentativeList reps = new LinearRepresentativeList(); // Initial insertions, the empty set always has the empty neighborhood set as a representative initially reps.Update(new BitSet(0, n), new BitSet(0, n)); for (int i = 0; i < sequence.Count; i++) { /// We give v the possibility to be a representative instead of being contained in neighborhoods int v = sequence[i]; // Actually move v from one set to the other set left.Add(v); right.Remove(v); // We don't want to disturb any pointers so we create new empty datastructures to save everything for the next iteration LinearRepresentativeList nextReps = new LinearRepresentativeList(); // We are iterating over all representatives saved inside the list of the previous step. For each entry there are only two possibilities to create a new neighborhood foreach (BitSet representative in reps) { // Case 1: The neighborhood possibly contained v (thus v has to be removed), but is still valid BitSet neighborhood = reps.GetNeighborhood(representative) - v; nextReps.Update(representative, neighborhood); // Case 2: The union of the old neighborhood, together with v's neighborhood, creates a new entry. The representative is uniond together with v and saved. BitSet representative_ = representative + v; BitSet neighborhood_ = neighborhood + (graph.OpenNeighborhood(v) * right); nextReps.Update(representative_, neighborhood_); } // Update the values for the next iteration reps = nextReps; // Save the maximum size that we encounter during all iterations; this will be the boolean dimension of the graph. MaxDimension = Math.Max(MaxDimension, reps.Count); // Save the representatives at the current cut in the table Table[left.Copy()] = reps; } }
// Constructs the actual width values // The idea is the following: If we want to know the optimal width for a certain cut A, thus Width[A], then we can obtain this by // either taking the max(Cuts[A], the minimum over all widths of Width[A - a]), which is our recurrence relation. private static void ConstructSequence(Graph graph, BitSet A) { // min saves the minimum size of all neighborhoods of the cut [A - a], where a can be any vertex in A long min = long.MaxValue; // v will be the optimal choice to leave out in the previous iteration in order to obtain A's full neighborhood int v = -1; foreach (int a in A) { BitSet previous = A - a; // If we have not constructed the previous step yet, then go in recursion and do so if (!Neighborhoods.ContainsKey(previous)) ConstructSequence(graph, previous); // Save the minimum value if (Width[previous] < min) { min = Width[previous]; v = a; } } // Obtain the neighborhood of v BitSet nv = graph.OpenNeighborhood(v) * (graph.Vertices - A); // We save the full set of neighborhood vertices at cut A. It does not matter that v was chosen arbitrarely; we always end up in the same collection of neighboring vertices for the set A Set<BitSet> un = new Set<BitSet>(); foreach (BitSet _base in Neighborhoods[A - v]) { un.Add(_base - v); // previous neighbor without v is a possible new neighborhood un.Add((_base - v) + nv); // previous neighbor without v, unioned with the neighborhood of v is a possible new neighborhood } // Save all entries Neighborhoods[A] = un; // List of all neighbors at cut A Cuts[A] = Neighborhoods[A].Count; // Dimension at this cut Width[A] = Math.Max(min, Cuts[A]); // Actual possible width to get to this cut }
// Constructs the actual width values private static void ConstructTree(Graph graph, BitSet A) { long min = A.Count == 1 ? 0 : long.MaxValue; int n = graph.Size; int v = -1; // v is the vertex that if we remove it from A, we have the smallest number of neighbors BitSet optimal = new BitSet(0, n); Set<BitSet> subsets = new Set<BitSet>(new BitSet(0, n)); foreach (int a in A) { Set<BitSet> newSubsets = new Set<BitSet>(); foreach (BitSet j in subsets) { BitSet subset = j + a; BitSet inverse = A - subset; if (subset.Equals(A)) continue; // only consider strict subsets if (!Width.ContainsKey(subset)) ConstructTree(graph, subset); if (!Width.ContainsKey(inverse)) ConstructTree(graph, inverse); newSubsets.Add(subset); // add this for the next iteration long max = Math.Max(Width[subset], Width[inverse]); // either S or A\S will be the bottleneck if (max < min) { min = max; optimal = subset; // it doesn't matter if we take j + a or A - (j + a), since when retrieving the tree we split them anyway if (inverse.Count == 1) v = inverse.First(); } } subsets.AddRange(newSubsets); } v = v == -1 ? A.First() : v; BitSet nv = graph.OpenNeighborhood(v) * (graph.Vertices - (A - v)); Set<BitSet> un = new Set<BitSet>(); foreach (BitSet _base in Neighborhoods[A - v]) { un.Add(_base - v); // previous neighbor without v is a possible new neighborhood un.Add((_base - v) + nv); // previous neighbor without v, unioned with the neighborhood of v is a possible new neighborhood } Neighborhoods[A] = un; Cuts[A] = Neighborhoods[A].Count; Width[A] = Math.Max(min, Cuts[A]); // Actual possible width to get to this cut OptimalChild[A] = optimal; }
// This is an improved version of Algorithm 12 of the PhD thesis by Sadia Sharmin, which runs in O(n) instead of O(n^2) // The RelativeNeighborhood selection method picks the next vertex based on the ratio of neighbors that are already contained in N(Left) // While in the PhD thesis the algorithm loops over all vertices w in N(v) * right to determine if they are internal/external, we can make this faster // by realizing that |((N(v) * right) \ (N(left) * right)| is already equal to the size of the external set. This is also equal to the symmetric difference. private static int RelativeNeighborhood(Graph graph, BitSet left, BitSet right) { // Minimum ratio found so far double minRatio = double.MaxValue; // Vertex that we will choose int chosen = -1; // Set of vertices in right that are adjacent to vertices in left BitSet nl = graph.Neighborhood(left) * right; Dictionary<int, double> ratios = new Dictionary<int, double>(); foreach (int v in nl) { // Neighborhood of v BitSet nv = graph.OpenNeighborhood(v) * right; // Number of vertices outside of N(left) * right, but inside N(v) * right double external_v = (nv - nl).Count; // Number of vertices inside of both N(left) * right and N(v) * right double internal_v = nv.Count - external_v; if (internal_v == 0) internal_v = 1; // cant devide by zero! // Possibly update the minimum ratio found so far double ratio = external_v / internal_v; ratios[v+1] = ratio; if (ratio < minRatio) { chosen = v; minRatio = ratio; } } return chosen; }
/*************************/ // Selection algorithms for the next vertex in the sequence /*************************/ // Implementation of Algorithm 11 of the PhD thesis by Sadia Sharmin // The LeastUncommonNeighbor selection method finds the vertex in right that has the fewest neighbors in right that are // not adjacent to a vertex in left. In other words, we add as few as possibly new vertices to the neighborhoods. private static int LeastUncommonNeighbor(Graph graph, BitSet left, BitSet right) { // Minimum symmetric difference found so far int minSymmDiff = int.MaxValue; // Vertex that we will choose int chosen = -1; // Set of vertices in right that are adjacent to vertices in left BitSet nl = graph.Neighborhood(left) * right; foreach (int v in right) { // Neighborhood of v in right BitSet nv = graph.OpenNeighborhood(v) * right; // Count the difference between N(v) and N(Left) int symmDiff = (nv - nl).Count; // Possibly update the best choice so far if (symmDiff < minSymmDiff) { minSymmDiff = symmDiff; chosen = v; } } return chosen; }
// Returns all connected components in a certain subgraph, where we define a subgraph by the vertices that are contained in it // We apply multiple dfs searches to find all connected parts of the graph public static List<BitSet> ConnectedComponents(Graph graph, BitSet subgraph) { // Each connected component is defined as a bitset, thus the result is a list of these bitsets List<BitSet> result = new List<BitSet>(); // Working stack for the dfs algorithm Stack<int> stack = new Stack<int>(); // Each vertex of the subgraph will either be explored, or it will be the starting point of a new dfs search BitSet todo = subgraph.Copy(); while (!todo.IsEmpty) { int s = todo.First(); stack.Push(s); // Start tracking the new component BitSet component = new BitSet(0, graph.Size); // Default dfs exploring part of the graph while (stack.Count > 0) { int v = stack.Pop(); // If we have not encountered this vertex before (meaning it isn't in this component), then we check for all neighbors if they are part of the subgraph // If a neighbor is part of the subgraph it means that we have to push it on the stack to explore it at a later stage for this component if (!component.Contains(v)) { component.Add(v); // Remove this vertex from the 'todo' list, since it can never be the starting point of a new component todo.Remove(v); foreach (int w in graph.OpenNeighborhood(v)) if (subgraph.Contains(w)) stack.Push(w); } } // The whole connected component has been found, so we can add it to the list of results result.Add(component); } return result; }
/*************************/ // Trivial cases /*************************/ public static int TrivialCases(Graph graph, BitSet left, BitSet right) { int chosen = -1; // Check if any vertex in right belongs to one of the trivial cases, if yes then we can add this vertex directly to the sequence foreach (int v in right) { // Trivial case 1. If the neighbors of a vertex v in right are all contained in left, then select v // What this means is that v has an empty neighborhood, thus it will not add anything to the boolean-dimension if ((graph.OpenNeighborhood(v) - left).IsEmpty) { chosen = v; break; } bool stop = false; // 2. If there are two vertices, v in right and u in left, such that N(v) * right == (N(u)\v) * right, // then v is selected as our next vertex // What this means is that all neighbors of v are already 'represented' by u, thus making the choice for v will not add anything to the dimension foreach (int u in left) { BitSet nv = graph.OpenNeighborhood(v) * right; // N(v) * right BitSet nu = (graph.OpenNeighborhood(u) - v) * right; // (N(u)\v) * right if (nv.Equals(nu)) { chosen = v; stop = true; break; } } if (stop) break; } return chosen; }
// The IUN selection method picks the next vertex based on a greedy choice, namely what is the vertex that will have the least // influence on the boolean dimension at this point, similar to the leastcutvalue. // In contrast to the leastcutvalue heuristic, we do not compute a bipartite graph, but rather construct and count the actual neighborhoods of possible cuts // Implementation of Algorithm 8 of the thesis private static Set<BitSet> IncrementUN(Graph graph, BitSet X, Set<BitSet> UN_X, int v) { Set<BitSet> U = new Set<BitSet>(new BitSet(0, graph.Size)); foreach (BitSet S in UN_X) { U.Add(S - v); BitSet _X = (graph.Vertices - X); U.Add((S - v) + (graph.OpenNeighborhood(v) * (_X - v))); } return U; }