Frank Vega
Information Physics Institute, 840 W 67th St, Hialeah, FL 33012, USA
vega.frank@gmail.com
Problem Statement
Given an undirected graph G = ( V , E ) G = (V, E) G=(V,E) , the goal is to find a dominating set S ⊆ V S \subseteq V S⊆V , where a set S S S is dominating if every vertex in V V V is either in S S S or adjacent to a vertex in S S S . We aim to design an algorithm that produces a dominating set S S S such that ∣ S ∣ ≤ 2 ⋅ ∣ O P T ∣ |S| \leq 2 \cdot |OPT| ∣S∣≤2⋅∣OPT∣ , where O P T OPT OPT is the size of a minimum dominating set in G G G , thus achieving a 2-approximation.
Algorithm Description
Consider the algorithm implemented in Python:
<span>import</span> <span>networkx</span> <span>as</span> <span>nx</span><span>def</span> <span>find_dominating_set</span><span>(</span><span>graph</span><span>):</span><span>"""</span><span> Approximate minimum dominating set for an undirected graph by transforming it into a bipartite graph. Args: graph (nx.Graph): A NetworkX Graph object representing the input graph. Returns: set: A set of vertex indices representing the approximate minimum dominating set. Returns an empty set if the graph is empty or has no edges. </span><span>"""</span><span># Subroutine to compute a dominating set in a bipartite component, used to find a dominating set in the original graph </span> <span>def</span> <span>find_dominating_set_via_bipartite_proxy</span><span>(</span><span>G</span><span>):</span><span># Initialize an empty set to store the dominating set for this bipartite component </span> <span>dominating_set</span> <span>=</span> <span>set</span><span>()</span><span># Track which vertices in the bipartite graph are dominated </span> <span>dominated</span> <span>=</span> <span>{</span><span>v</span><span>:</span> <span>False</span> <span>for</span> <span>v</span> <span>in</span> <span>G</span><span>.</span><span>nodes</span><span>()}</span><span># Sort vertices by degree (ascending) to prioritize high-degree nodes for greedy selection </span> <span>undominated</span> <span>=</span> <span>sorted</span><span>(</span><span>list</span><span>(</span><span>G</span><span>.</span><span>nodes</span><span>()),</span> <span>key</span><span>=</span><span>lambda</span> <span>x</span><span>:</span> <span>G</span><span>.</span><span>degree</span><span>(</span><span>x</span><span>))</span><span># Continue processing until all vertices are dominated </span> <span>while</span> <span>undominated</span><span>:</span><span># Pop the next vertex to process (starting with highest degree) </span> <span>v</span> <span>=</span> <span>undominated</span><span>.</span><span>pop</span><span>()</span><span># Check if the vertex is not yet dominated </span> <span>if</span> <span>not</span> <span>dominated</span><span>[</span><span>v</span><span>]:</span><span># Initialize the best vertex to add as the current vertex </span> <span>best_vertex</span> <span>=</span> <span>v</span><span># Initialize the count of undominated vertices covered by the best vertex </span> <span>best_undominated_count</span> <span>=</span> <span>-</span><span>1</span><span># Consider the current vertex and its neighbors as candidates </span> <span>for</span> <span>neighbor</span> <span>in</span> <span>list</span><span>(</span><span>G</span><span>.</span><span>neighbors</span><span>(</span><span>v</span><span>))</span> <span>+</span> <span>[</span><span>v</span><span>]:</span><span># Count how many undominated vertices this candidate covers </span> <span>undominated_neighbors_count</span> <span>=</span> <span>0</span><span>for</span> <span>u</span> <span>in</span> <span>list</span><span>(</span><span>G</span><span>.</span><span>neighbors</span><span>(</span><span>neighbor</span><span>))</span> <span>+</span> <span>[</span><span>neighbor</span><span>]:</span><span>if</span> <span>not</span> <span>dominated</span><span>[</span><span>u</span><span>]:</span><span>undominated_neighbors_count</span> <span>+=</span> <span>1</span><span># Update the best vertex if this candidate covers more undominated vertices </span> <span>if</span> <span>undominated_neighbors_count</span> <span>></span> <span>best_undominated_count</span><span>:</span><span>best_undominated_count</span> <span>=</span> <span>undominated_neighbors_count</span><span>best_vertex</span> <span>=</span> <span>neighbor</span><span># Add the best vertex to the dominating set for this component </span> <span>dominating_set</span><span>.</span><span>add</span><span>(</span><span>best_vertex</span><span>)</span><span># Mark the neighbors of the best vertex as dominated </span> <span>for</span> <span>neighbor</span> <span>in</span> <span>G</span><span>.</span><span>neighbors</span><span>(</span><span>best_vertex</span><span>):</span><span>dominated</span><span>[</span><span>neighbor</span><span>]</span> <span>=</span> <span>True</span><span># Mark the mirror vertex (i, 1-k) as dominated to reflect domination in the original graph </span> <span>mirror_neighbor</span> <span>=</span> <span>(</span><span>neighbor</span><span>[</span><span>0</span><span>],</span> <span>1</span> <span>-</span> <span>neighbor</span><span>[</span><span>1</span><span>])</span><span>dominated</span><span>[</span><span>mirror_neighbor</span><span>]</span> <span>=</span> <span>True</span><span># Return the dominating set for this bipartite component </span> <span>return</span> <span>dominating_set</span><span># Validate that the input is a NetworkX Graph object </span> <span>if</span> <span>not</span> <span>isinstance</span><span>(</span><span>graph</span><span>,</span> <span>nx</span><span>.</span><span>Graph</span><span>):</span><span>raise</span> <span>ValueError</span><span>(</span><span>"</span><span>Input must be an undirected NetworkX Graph.</span><span>"</span><span>)</span><span># Handle edge cases: return an empty set if the graph has no nodes or no edges </span> <span>if</span> <span>graph</span><span>.</span><span>number_of_nodes</span><span>()</span> <span>==</span> <span>0</span> <span>or</span> <span>graph</span><span>.</span><span>number_of_edges</span><span>()</span> <span>==</span> <span>0</span><span>:</span><span>return</span> <span>set</span><span>()</span><span># Initialize the dominating set with all isolated nodes, as they must be included to dominate themselves </span> <span>approximate_dominating_set</span> <span>=</span> <span>set</span><span>(</span><span>nx</span><span>.</span><span>isolates</span><span>(</span><span>graph</span><span>))</span><span># Remove isolated nodes from the graph to process the remaining components </span> <span>graph</span><span>.</span><span>remove_nodes_from</span><span>(</span><span>approximate_dominating_set</span><span>)</span><span># If the graph is empty after removing isolated nodes, return the set of isolated nodes </span> <span>if</span> <span>graph</span><span>.</span><span>number_of_nodes</span><span>()</span> <span>==</span> <span>0</span><span>:</span><span>return</span> <span>approximate_dominating_set</span><span># Initialize an empty bipartite graph to transform the remaining graph </span> <span>bipartite_graph</span> <span>=</span> <span>nx</span><span>.</span><span>Graph</span><span>()</span><span># Construct the bipartite graph B </span> <span>for</span> <span>i</span> <span>in</span> <span>graph</span><span>.</span><span>nodes</span><span>():</span><span># Add an edge between mirror nodes (i, 0) and (i, 1) for each vertex i </span> <span>bipartite_graph</span><span>.</span><span>add_edge</span><span>((</span><span>i</span><span>,</span> <span>0</span><span>),</span> <span>(</span><span>i</span><span>,</span> <span>1</span><span>))</span><span># Add edges reflecting adjacency in the original graph: (i, 0) to (j, 1) for each neighbor j </span> <span>for</span> <span>j</span> <span>in</span> <span>graph</span><span>.</span><span>neighbors</span><span>(</span><span>i</span><span>):</span><span>bipartite_graph</span><span>.</span><span>add_edge</span><span>((</span><span>i</span><span>,</span> <span>0</span><span>),</span> <span>(</span><span>j</span><span>,</span> <span>1</span><span>))</span><span># Process each connected component in the bipartite graph </span> <span>for</span> <span>component</span> <span>in</span> <span>nx</span><span>.</span><span>connected_components</span><span>(</span><span>bipartite_graph</span><span>):</span><span># Extract the subgraph for the current connected component </span> <span>bipartite_subgraph</span> <span>=</span> <span>bipartite_graph</span><span>.</span><span>subgraph</span><span>(</span><span>component</span><span>)</span><span># Compute the dominating set for this component using the subroutine </span> <span>tuple_nodes</span> <span>=</span> <span>find_dominating_set_via_bipartite_proxy</span><span>(</span><span>bipartite_subgraph</span><span>)</span><span># Extract the original node indices from the tuple nodes (i, k) and add them to the dominating set </span> <span>approximate_dominating_set</span><span>.</span><span>update</span><span>({</span><span>tuple_node</span><span>[</span><span>0</span><span>]</span> <span>for</span> <span>tuple_node</span> <span>in</span> <span>tuple_nodes</span><span>})</span><span># Return the final dominating set for the original graph </span> <span>return</span> <span>approximate_dominating_set</span><span>import</span> <span>networkx</span> <span>as</span> <span>nx</span> <span>def</span> <span>find_dominating_set</span><span>(</span><span>graph</span><span>):</span> <span>"""</span><span> Approximate minimum dominating set for an undirected graph by transforming it into a bipartite graph. Args: graph (nx.Graph): A NetworkX Graph object representing the input graph. Returns: set: A set of vertex indices representing the approximate minimum dominating set. Returns an empty set if the graph is empty or has no edges. </span><span>"""</span> <span># Subroutine to compute a dominating set in a bipartite component, used to find a dominating set in the original graph </span> <span>def</span> <span>find_dominating_set_via_bipartite_proxy</span><span>(</span><span>G</span><span>):</span> <span># Initialize an empty set to store the dominating set for this bipartite component </span> <span>dominating_set</span> <span>=</span> <span>set</span><span>()</span> <span># Track which vertices in the bipartite graph are dominated </span> <span>dominated</span> <span>=</span> <span>{</span><span>v</span><span>:</span> <span>False</span> <span>for</span> <span>v</span> <span>in</span> <span>G</span><span>.</span><span>nodes</span><span>()}</span> <span># Sort vertices by degree (ascending) to prioritize high-degree nodes for greedy selection </span> <span>undominated</span> <span>=</span> <span>sorted</span><span>(</span><span>list</span><span>(</span><span>G</span><span>.</span><span>nodes</span><span>()),</span> <span>key</span><span>=</span><span>lambda</span> <span>x</span><span>:</span> <span>G</span><span>.</span><span>degree</span><span>(</span><span>x</span><span>))</span> <span># Continue processing until all vertices are dominated </span> <span>while</span> <span>undominated</span><span>:</span> <span># Pop the next vertex to process (starting with highest degree) </span> <span>v</span> <span>=</span> <span>undominated</span><span>.</span><span>pop</span><span>()</span> <span># Check if the vertex is not yet dominated </span> <span>if</span> <span>not</span> <span>dominated</span><span>[</span><span>v</span><span>]:</span> <span># Initialize the best vertex to add as the current vertex </span> <span>best_vertex</span> <span>=</span> <span>v</span> <span># Initialize the count of undominated vertices covered by the best vertex </span> <span>best_undominated_count</span> <span>=</span> <span>-</span><span>1</span> <span># Consider the current vertex and its neighbors as candidates </span> <span>for</span> <span>neighbor</span> <span>in</span> <span>list</span><span>(</span><span>G</span><span>.</span><span>neighbors</span><span>(</span><span>v</span><span>))</span> <span>+</span> <span>[</span><span>v</span><span>]:</span> <span># Count how many undominated vertices this candidate covers </span> <span>undominated_neighbors_count</span> <span>=</span> <span>0</span> <span>for</span> <span>u</span> <span>in</span> <span>list</span><span>(</span><span>G</span><span>.</span><span>neighbors</span><span>(</span><span>neighbor</span><span>))</span> <span>+</span> <span>[</span><span>neighbor</span><span>]:</span> <span>if</span> <span>not</span> <span>dominated</span><span>[</span><span>u</span><span>]:</span> <span>undominated_neighbors_count</span> <span>+=</span> <span>1</span> <span># Update the best vertex if this candidate covers more undominated vertices </span> <span>if</span> <span>undominated_neighbors_count</span> <span>></span> <span>best_undominated_count</span><span>:</span> <span>best_undominated_count</span> <span>=</span> <span>undominated_neighbors_count</span> <span>best_vertex</span> <span>=</span> <span>neighbor</span> <span># Add the best vertex to the dominating set for this component </span> <span>dominating_set</span><span>.</span><span>add</span><span>(</span><span>best_vertex</span><span>)</span> <span># Mark the neighbors of the best vertex as dominated </span> <span>for</span> <span>neighbor</span> <span>in</span> <span>G</span><span>.</span><span>neighbors</span><span>(</span><span>best_vertex</span><span>):</span> <span>dominated</span><span>[</span><span>neighbor</span><span>]</span> <span>=</span> <span>True</span> <span># Mark the mirror vertex (i, 1-k) as dominated to reflect domination in the original graph </span> <span>mirror_neighbor</span> <span>=</span> <span>(</span><span>neighbor</span><span>[</span><span>0</span><span>],</span> <span>1</span> <span>-</span> <span>neighbor</span><span>[</span><span>1</span><span>])</span> <span>dominated</span><span>[</span><span>mirror_neighbor</span><span>]</span> <span>=</span> <span>True</span> <span># Return the dominating set for this bipartite component </span> <span>return</span> <span>dominating_set</span> <span># Validate that the input is a NetworkX Graph object </span> <span>if</span> <span>not</span> <span>isinstance</span><span>(</span><span>graph</span><span>,</span> <span>nx</span><span>.</span><span>Graph</span><span>):</span> <span>raise</span> <span>ValueError</span><span>(</span><span>"</span><span>Input must be an undirected NetworkX Graph.</span><span>"</span><span>)</span> <span># Handle edge cases: return an empty set if the graph has no nodes or no edges </span> <span>if</span> <span>graph</span><span>.</span><span>number_of_nodes</span><span>()</span> <span>==</span> <span>0</span> <span>or</span> <span>graph</span><span>.</span><span>number_of_edges</span><span>()</span> <span>==</span> <span>0</span><span>:</span> <span>return</span> <span>set</span><span>()</span> <span># Initialize the dominating set with all isolated nodes, as they must be included to dominate themselves </span> <span>approximate_dominating_set</span> <span>=</span> <span>set</span><span>(</span><span>nx</span><span>.</span><span>isolates</span><span>(</span><span>graph</span><span>))</span> <span># Remove isolated nodes from the graph to process the remaining components </span> <span>graph</span><span>.</span><span>remove_nodes_from</span><span>(</span><span>approximate_dominating_set</span><span>)</span> <span># If the graph is empty after removing isolated nodes, return the set of isolated nodes </span> <span>if</span> <span>graph</span><span>.</span><span>number_of_nodes</span><span>()</span> <span>==</span> <span>0</span><span>:</span> <span>return</span> <span>approximate_dominating_set</span> <span># Initialize an empty bipartite graph to transform the remaining graph </span> <span>bipartite_graph</span> <span>=</span> <span>nx</span><span>.</span><span>Graph</span><span>()</span> <span># Construct the bipartite graph B </span> <span>for</span> <span>i</span> <span>in</span> <span>graph</span><span>.</span><span>nodes</span><span>():</span> <span># Add an edge between mirror nodes (i, 0) and (i, 1) for each vertex i </span> <span>bipartite_graph</span><span>.</span><span>add_edge</span><span>((</span><span>i</span><span>,</span> <span>0</span><span>),</span> <span>(</span><span>i</span><span>,</span> <span>1</span><span>))</span> <span># Add edges reflecting adjacency in the original graph: (i, 0) to (j, 1) for each neighbor j </span> <span>for</span> <span>j</span> <span>in</span> <span>graph</span><span>.</span><span>neighbors</span><span>(</span><span>i</span><span>):</span> <span>bipartite_graph</span><span>.</span><span>add_edge</span><span>((</span><span>i</span><span>,</span> <span>0</span><span>),</span> <span>(</span><span>j</span><span>,</span> <span>1</span><span>))</span> <span># Process each connected component in the bipartite graph </span> <span>for</span> <span>component</span> <span>in</span> <span>nx</span><span>.</span><span>connected_components</span><span>(</span><span>bipartite_graph</span><span>):</span> <span># Extract the subgraph for the current connected component </span> <span>bipartite_subgraph</span> <span>=</span> <span>bipartite_graph</span><span>.</span><span>subgraph</span><span>(</span><span>component</span><span>)</span> <span># Compute the dominating set for this component using the subroutine </span> <span>tuple_nodes</span> <span>=</span> <span>find_dominating_set_via_bipartite_proxy</span><span>(</span><span>bipartite_subgraph</span><span>)</span> <span># Extract the original node indices from the tuple nodes (i, k) and add them to the dominating set </span> <span>approximate_dominating_set</span><span>.</span><span>update</span><span>({</span><span>tuple_node</span><span>[</span><span>0</span><span>]</span> <span>for</span> <span>tuple_node</span> <span>in</span> <span>tuple_nodes</span><span>})</span> <span># Return the final dominating set for the original graph </span> <span>return</span> <span>approximate_dominating_set</span>import networkx as nx def find_dominating_set(graph): """ Approximate minimum dominating set for an undirected graph by transforming it into a bipartite graph. Args: graph (nx.Graph): A NetworkX Graph object representing the input graph. Returns: set: A set of vertex indices representing the approximate minimum dominating set. Returns an empty set if the graph is empty or has no edges. """ # Subroutine to compute a dominating set in a bipartite component, used to find a dominating set in the original graph def find_dominating_set_via_bipartite_proxy(G): # Initialize an empty set to store the dominating set for this bipartite component dominating_set = set() # Track which vertices in the bipartite graph are dominated dominated = {v: False for v in G.nodes()} # Sort vertices by degree (ascending) to prioritize high-degree nodes for greedy selection undominated = sorted(list(G.nodes()), key=lambda x: G.degree(x)) # Continue processing until all vertices are dominated while undominated: # Pop the next vertex to process (starting with highest degree) v = undominated.pop() # Check if the vertex is not yet dominated if not dominated[v]: # Initialize the best vertex to add as the current vertex best_vertex = v # Initialize the count of undominated vertices covered by the best vertex best_undominated_count = -1 # Consider the current vertex and its neighbors as candidates for neighbor in list(G.neighbors(v)) + [v]: # Count how many undominated vertices this candidate covers undominated_neighbors_count = 0 for u in list(G.neighbors(neighbor)) + [neighbor]: if not dominated[u]: undominated_neighbors_count += 1 # Update the best vertex if this candidate covers more undominated vertices if undominated_neighbors_count > best_undominated_count: best_undominated_count = undominated_neighbors_count best_vertex = neighbor # Add the best vertex to the dominating set for this component dominating_set.add(best_vertex) # Mark the neighbors of the best vertex as dominated for neighbor in G.neighbors(best_vertex): dominated[neighbor] = True # Mark the mirror vertex (i, 1-k) as dominated to reflect domination in the original graph mirror_neighbor = (neighbor[0], 1 - neighbor[1]) dominated[mirror_neighbor] = True # Return the dominating set for this bipartite component return dominating_set # Validate that the input is a NetworkX Graph object if not isinstance(graph, nx.Graph): raise ValueError("Input must be an undirected NetworkX Graph.") # Handle edge cases: return an empty set if the graph has no nodes or no edges if graph.number_of_nodes() == 0 or graph.number_of_edges() == 0: return set() # Initialize the dominating set with all isolated nodes, as they must be included to dominate themselves approximate_dominating_set = set(nx.isolates(graph)) # Remove isolated nodes from the graph to process the remaining components graph.remove_nodes_from(approximate_dominating_set) # If the graph is empty after removing isolated nodes, return the set of isolated nodes if graph.number_of_nodes() == 0: return approximate_dominating_set # Initialize an empty bipartite graph to transform the remaining graph bipartite_graph = nx.Graph() # Construct the bipartite graph B for i in graph.nodes(): # Add an edge between mirror nodes (i, 0) and (i, 1) for each vertex i bipartite_graph.add_edge((i, 0), (i, 1)) # Add edges reflecting adjacency in the original graph: (i, 0) to (j, 1) for each neighbor j for j in graph.neighbors(i): bipartite_graph.add_edge((i, 0), (j, 1)) # Process each connected component in the bipartite graph for component in nx.connected_components(bipartite_graph): # Extract the subgraph for the current connected component bipartite_subgraph = bipartite_graph.subgraph(component) # Compute the dominating set for this component using the subroutine tuple_nodes = find_dominating_set_via_bipartite_proxy(bipartite_subgraph) # Extract the original node indices from the tuple nodes (i, k) and add them to the dominating set approximate_dominating_set.update({tuple_node[0] for tuple_node in tuple_nodes}) # Return the final dominating set for the original graph return approximate_dominating_set
Enter fullscreen mode Exit fullscreen mode
The algorithm transforms the problem into a bipartite graph setting and uses a greedy approach. Here are the steps:
-
Handle Isolated Nodes:
- Identify all isolated nodes in G G G (vertices with degree 0).
- Add these nodes to the dominating set S S S , as they must be included to dominate themselves.
-
Construct a Bipartite Graph B B B :
- For the remaining graph (after removing isolated nodes), construct a bipartite graph B B B with:
- Vertex Set: Two partitions, where each vertex i ∈ V i \in V i∈V is duplicated as ( i , 0 ) (i, 0) (i,0) and ( i , 1 ) (i, 1) (i,1) .
- Edge Set:
- An edge ( i , 0 ) − ( i , 1 ) (i, 0) – (i, 1) (i,0)−(i,1) for each i ∈ V i \in V i∈V .
- For each edge ( i , j ) ∈ E (i, j) \in E (i,j)∈E in G G G , add edges ( i , 0 ) − ( j , 1 ) (i, 0) – (j, 1) (i,0)−(j,1) and ( j , 0 ) − ( i , 1 ) (j, 0) – (i, 1) (j,0)−(i,1) in B B B .
- For the remaining graph (after removing isolated nodes), construct a bipartite graph B B B with:
-
Greedy Dominating Set in B B B :
- Run a greedy algorithm on B B B to compute a dominating set D B D_B DB :
- While there are undominated vertices in B B B , select the vertex that dominates the maximum number of currently undominated vertices and add it to D B D_B DB .
- Run a greedy algorithm on B B B to compute a dominating set D B D_B DB :
-
Map Back to G G G :
- Define the dominating set S S S for G G G as:
- S = { i ∈ V ∣ ( i , 0 ) ∈ D B or ( i , 1 ) ∈ D B } S = \{ i \in V \mid (i, 0) \in D_B \text{ or } (i, 1) \in D_B \} S={i∈V∣(i,0)∈DB or (i,1)∈DB} .
- Include the isolated nodes identified in Step 1.
- Define the dominating set S S S for G G G as:
Correctness of the Algorithm
Let’s verify that S S S is a dominating set for G G G :
-
Isolated Nodes:
- All isolated nodes are explicitly added to S S S , so they are dominated by themselves.
-
Non-Isolated Nodes:
- Consider any vertex i ∈ V i \in V i∈V in the non-isolated part of G G G :
- Case 1: i ∈ S i \in S i∈S :
- If ( i , 0 ) (i, 0) (i,0) or ( i , 1 ) (i, 1) (i,1) is in D B D_B DB , then i ∈ S i \in S i∈S , and i i i is dominated by itself.
- Case 2: i ∉ S i \notin S i∈/S :
- If neither ( i , 0 ) (i, 0) (i,0) nor ( i , 1 ) (i, 1) (i,1) is in D B D_B DB , then since D B D_B DB dominates all vertices in B B B , ( i , 0 ) (i, 0) (i,0) must be adjacent to some ( j , 1 ) ∈ D B (j, 1) \in D_B (j,1)∈DB .
- In B B B , ( i , 0 ) − ( j , 1 ) (i, 0) – (j, 1) (i,0)−(j,1) exists if ( i , j ) ∈ E (i, j) \in E (i,j)∈E in G G G .
- Thus, j ∈ S j \in S j∈S (because ( j , 1 ) ∈ D B (j, 1) \in D_B (j,1)∈DB ), and i i i is adjacent to j j j in G G G .
-
Conclusion:
- Every vertex in G G G is either in S S S or has a neighbor in S S S , so S S S is a dominating set.
Approximation Analysis Using D u D_u Du
To prove that ∣ S ∣ ≤ 2 ⋅ ∣ O P T ∣ |S| \leq 2 \cdot |OPT| ∣S∣≤2⋅∣OPT∣ , we associate each vertex in the optimal dominating set O P T OPT OPT of G G G with vertices in S S S , ensuring that each vertex in O P T OPT OPT is “responsible” for at most two vertices in S S S .
Definitions
- Let O P T OPT OPT be a minimum dominating set of G G G .
- For each vertex u ∈ O P T u \in OPT u∈OPT , define D u ⊆ S D_u \subseteq S Du⊆S as the set of vertices in S S S that are charged to u u u based on how the greedy algorithm selects vertices in B B B to dominate vertices related to u u u .
Key Idea
The D u D_u Du analysis shows that the greedy algorithm selects at most two vertices per vertex in O P T OPT OPT due to the graph’s structure. Here, we mirror this by analyzing the bipartite graph B B B and the mapping back to G G G , showing that each u ∈ O P T u \in OPT u∈OPT contributes to at most two vertices being added to S S S .
Analysis Steps
-
Role of O P T OPT OPT in G G G :
- Each u ∈ O P T u \in OPT u∈OPT dominates itself and its neighbors in G G G .
- In B B B , the vertices ( u , 0 ) (u, 0) (u,0) and ( u , 1 ) (u, 1) (u,1) correspond to u u u , and we need to ensure they (and their neighbors) are dominated by D B D_B DB .
-
Greedy Selection in B B B :
- The greedy algorithm selects a vertex w = ( j , k ) w = (j, k) w=(j,k) (where k = 0 k = 0 k=0 or 1 1 1 ) in B B B to maximize the number of undominated vertices covered.
- When w w w is added to D B D_B DB , j j j is added to S S S .
-
Defining D u D_u Du :
- For each u ∈ O P T u \in OPT u∈OPT , D u D_u Du includes vertices j ∈ S j \in S j∈S such that the selection of ( j , 0 ) (j, 0) (j,0) or ( j , 1 ) (j, 1) (j,1) in D B D_B DB helps dominate ( u , 0 ) (u, 0) (u,0) or ( u , 1 ) (u, 1) (u,1) .
- We charge j j j to u u u if:
- j = u j = u j=u (i.e., ( u , 0 ) (u, 0) (u,0) or ( u , 1 ) (u, 1) (u,1) is selected), or
- j j j is a neighbor of u u u in G G G , and selecting ( j , k ) (j, k) (j,k) dominates ( u , 0 ) (u, 0) (u,0) or ( u , 1 ) (u, 1) (u,1) via an edge in B B B .
-
Bounding ∣ D u ∣ |D_u| ∣Du∣ :
- Case 1: u ∈ S u \in S u∈S :
- If ( u , 0 ) (u, 0) (u,0) or ( u , 1 ) (u, 1) (u,1) is selected in D B D_B DB , then u ∈ S u \in S u∈S .
- Selecting ( u , 1 ) (u, 1) (u,1) dominates ( u , 0 ) (u, 0) (u,0) (via ( u , 0 ) − ( u , 1 ) (u, 0) – (u, 1) (u,0)−(u,1) ), and vice versa.
- At most one vertex ( u u u ) is added to S S S for u u u , so ∣ D u ∣ ≤ 1 |D_u| \leq 1 ∣Du∣≤1 in this case.
- Case 2: u ∉ S u \notin S u∈/S :
- Neither ( u , 0 ) (u, 0) (u,0) nor ( u , 1 ) (u, 1) (u,1) is in D B D_B DB .
- ( u , 0 ) (u, 0) (u,0) must be dominated by some ( j , 1 ) ∈ D B (j, 1) \in D_B (j,1)∈DB , where ( u , j ) ∈ E (u, j) \in E (u,j)∈E in G G G , so j ∈ S j \in S j∈S .
- ( u , 1 ) (u, 1) (u,1) must be dominated by some ( k , 0 ) ∈ D B (k, 0) \in D_B (k,0)∈DB , where ( u , k ) ∈ E (u, k) \in E (u,k)∈E in G G G , so k ∈ S k \in S k∈S .
- Thus, D u = { j , k } D_u = \{ j, k \} Du={j,k} , and ∣ D u ∣ ≤ 2 |D_u| \leq 2 ∣Du∣≤2 (if j = k j = k j=k , then ∣ D u ∣ = 1 |D_u| = 1 ∣Du∣=1 ).
- Greedy Optimization:
- The greedy algorithm often selects a single vertex that dominates both ( u , 0 ) (u, 0) (u,0) and ( u , 1 ) (u, 1) (u,1) indirectly through neighbors, but in the worst case, two selections suffice.
- Case 1: u ∈ S u \in S u∈S :
-
Total Size of S S S :
- Each vertex j ∈ S j \in S j∈S corresponds to at least one selection ( j , k ) ∈ D B (j, k) \in D_B (j,k)∈DB .
- Since each u ∈ O P T u \in OPT u∈OPT has ∣ D u ∣ ≤ 2 |D_u| \leq 2 ∣Du∣≤2 , the total number of vertices in S S S is: ∣ S ∣ ≤ ∑ u ∈ O P T ∣ D u ∣ ≤ 2 ⋅ ∣ O P T ∣ |S| \leq \sum_{u \in OPT} |D_u| \leq 2 \cdot |OPT| ∣S∣≤u∈OPT∑∣Du∣≤2⋅∣OPT∣
- This accounts for all selections each of which corresponds to a distinct
u ∈ O P T . u \in OPT. u∈OPT.
Conclusion
- Each u ∈ O P T u \in OPT u∈OPT is associated with at most two vertices in S S S via the D u D_u Du charging scheme.
- Thus, ∣ S ∣ ≤ 2 ⋅ ∣ O P T ∣ |S| \leq 2 \cdot |OPT| ∣S∣≤2⋅∣OPT∣ , proving the 2-approximation.
Conclusion
The algorithm computes a dominating set S S S for G G G by:
- Adding all isolated nodes to S S S .
- Constructing a bipartite graph B B B with vertices ( i , 0 ) (i, 0) (i,0) and ( i , 1 ) (i, 1) (i,1) for each i ∈ V i \in V i∈V and edges reflecting G G G ’s structure.
- Using a greedy algorithm to find a dominating set D B D_B DB in B B B .
- Mapping back to S = { i ∣ ( i , 0 ) ∈ D B or ( i , 1 ) ∈ D B } S = \{ i \mid (i, 0) \in D_B \text{ or } (i, 1) \in D_B \} S={i∣(i,0)∈DB or (i,1)∈DB} .
S S S is a dominating set because every vertex in G G G is either in S S S or adjacent to a vertex in S S S . By adapting the D u D_u Du analysis from chordal graphs, we define D u D_u Du for each u ∈ O P T u \in OPT u∈OPT as the vertices in S S S charged to u u u , showing ∣ D u ∣ ≤ 2 |D_u| \leq 2 ∣Du∣≤2 . This ensures ∣ S ∣ ≤ 2 ⋅ ∣ O P T ∣ |S| \leq 2 \cdot |OPT| ∣S∣≤2⋅∣OPT∣ , achieving a 2-approximation ratio for the dominating set problem in general undirected graphs.
Runtime Analysis of the Algorithm
To analyze the runtime of the find_dominating_set
algorithm, we need to examine its key components and determine the time complexity of each step. The algorithm processes a graph to find a dominating set using a bipartite graph construction and a greedy subroutine. Below, we break down the analysis into distinct steps, assuming the input graph G G G has n n n nodes and m m m edges.
Step 1: Handling Isolated Nodes
The algorithm begins by identifying and handling isolated nodes (nodes with no edges) in the graph.
- Identify Isolated Nodes: Using
nx.isolates(graph)
from the NetworkX library, isolated nodes are found by checking the degree of each node. This takes O ( n ) O(n) O(n) time, as there are n n n nodes to examine. - Remove Isolated Nodes: Removing a node in NetworkX is an O ( 1 ) O(1) O(1) operation per node. If there are k k k isolated nodes (where k ≤ n k \leq n k≤n ), the total time to remove them is O ( k ) O(k) O(k) , which is bounded by O ( n ) O(n) O(n) .
Time Complexity for Step 1: O ( n ) O(n) O(n)
Step 2: Constructing the Bipartite Graph
Next, the algorithm constructs a bipartite graph B B B based on the remaining graph after isolated nodes are removed.
- Add Mirror Edges: For each node i i i in G G G , two nodes ( i , 0 ) (i, 0) (i,0) and ( i , 1 ) (i, 1) (i,1) are created in B B B , connected by an edge. With n n n nodes, and each edge addition being O ( 1 ) O(1) O(1) , this step takes O ( n ) O(n) O(n) time.
- Add Adjacency Edges: For each edge ( i , j ) (i, j) (i,j) in G G G , edges ( i , 0 ) − ( j , 1 ) (i, 0) – (j, 1) (i,0)−(j,1) and ( j , 0 ) − ( i , 1 ) (j, 0) – (i, 1) (j,0)−(i,1) are added to B B B . Since G G G is undirected, each edge is processed once, and adding two edges per original edge takes O ( 1 ) O(1) O(1) time. With m m m edges, this is O ( m ) O(m) O(m) .
Time Complexity for Step 2: O ( n + m ) O(n + m) O(n+m)
Step 3: Finding Connected Components
The algorithm identifies connected components in the bipartite graph B B B .
- Compute Connected Components: Using
nx.connected_components
, this operation runs in O ( n ′ + m ′ ) O(n’ + m’) O(n′+m′) time, where n ′ n’ n′ and m ′ m’ m′ are the number of nodes and edges in B B B . In B B B , there are n ′ = 2 n n’ = 2n n′=2n nodes (two per node in G G G ) and m ′ = n + 2 m m’ = n + 2m m′=n+2m edges ( n n n mirror edges plus 2 m 2m 2m adjacency edges). Thus, the time is O ( 2 n + ( n + 2 m ) ) = O ( n + m ) O(2n + (n + 2m)) = O(n + m) O(2n+(n+2m))=O(n+m) .
Time Complexity for Step 3: O ( n + m ) O(n + m) O(n+m)
Step 4: Computing Dominating Sets for Each Component
For each connected component in B B B , the subroutine find_dominating_set_via_bipartite_proxy
computes a dominating set. We analyze this subroutine for a single component with n c n_c nc nodes and m c m_c mc edges, then scale to all components.
Subroutine Analysis: find_dominating_set_via_bipartite_proxy
-
Initialization:
- Create a
dominated
dictionary: O ( n c ) O(n_c) O(nc) . - Sort nodes by degree: O ( n c log n c ) O(n_c \log n_c) O(nclognc) .
- Create a
-
Main Loop:
- The loop continues until all nodes are dominated, running up to O ( n c ) O(n_c) O(nc) iterations in the worst case.
- For each iteration:
- Select a node v v v and evaluate it and its neighbors (its closed neighborhood) to find the vertex that dominates the most undominated nodes.
- For a node v v v with degree d v d_v dv , there are d v + 1 d_v + 1 dv+1 candidates (including v v v ).
- For each candidate, count undominated nodes in its closed neighborhood, taking O ( d candidate ) O(d_{\text{candidate}}) O(dcandidate) time per candidate.
- Total time per iteration is O ( d v + ∑ u ∈ N ( v ) d u ) O(d_v + \sum_{u \in N(v)} d_u) O(dv+∑u∈N(v)du) , which is the sum of degrees in the closed neighborhood of v v v .
- After selecting the best vertex, mark its neighbors and their mirrors as dominated, taking O ( d best ) O(d_{\text{best}}) O(dbest) time.
-
Subroutine Total:
- Sorting: O ( n c log n c ) O(n_c \log n_c) O(nclognc) .
- Loop: Across all iterations, each node is processed once, and the total work is proportional to the sum of degrees, which is O ( m c ) O(m_c) O(mc) .
- Total for one component: O ( n c log n c + m c ) O(n_c \log n_c + m_c) O(nclognc+mc) .
Across All Components
- The components are disjoint, with ∑ n c = 2 n \sum n_c = 2n ∑nc=2n and ∑ m c = n + 2 m \sum m_c = n + 2m ∑mc=n+2m .
- The total time is O ( ∑ ( n c log n c + m c ) ) O(\sum (n_c \log n_c + m_c)) O(∑(nclognc+mc)) .
- The ∑ n c log n c \sum n_c \log n_c ∑nclognc term is maximized when all nodes are in one component, yielding O ( 2 n log 2 n ) = O ( n log n ) O(2n \log 2n) = O(n \log n) O(2nlog2n)=O(nlogn) .
- The ∑ m c = O ( n + m ) \sum m_c = O(n + m) ∑mc=O(n+m) .
Time Complexity for Step 4: O ( n log n + m ) O(n \log n + m) O(nlogn+m)
Overall Time Complexity
Combining all steps:
- Step 1: O ( n ) O(n) O(n)
- Step 2: O ( n + m ) O(n + m) O(n+m)
- Step 3: O ( n + m ) O(n + m) O(n+m)
- Step 4: O ( n log n + m ) O(n \log n + m) O(nlogn+m)
The dominant term is O ( n log n + m ) O(n \log n + m) O(nlogn+m) , so the overall time complexity of the find_dominating_set
algorithm is O ( n log n + m ) O(n \log n + m) O(nlogn+m)
Space Complexity
- Bipartite Graph: O ( n + m ) O(n + m) O(n+m) , with 2 n 2n 2n nodes and n + 2 m n + 2m n+2m edges.
- Auxiliary Data Structures: The
dominated
dictionary and undominated list use O ( n ) O(n) O(n) space.
Space Complexity: O ( n + m ) O(n + m) O(n+m)
Conclusion
The find_dominating_set
algorithm runs in O ( n log n + m ) O(n \log n + m) O(nlogn+m) time and uses O ( n + m ) O(n + m) O(n+m) space, where n n n is the number of nodes and m m m is the number of edges in the input graph. The bottleneck arises from sorting nodes by degree in the subroutine, contributing the n log n n \log n nlogn factor. This complexity makes the algorithm efficient and scalable for large graphs, especially given its 2-approximation guarantee for the dominating set problem.
Research Data
A Python implementation, titled Baldor: Approximate Minimum Dominating Set Solver—in tribute to the distinguished Cuban mathematician, educator, and jurist Aurelio Angel Baldor de la Vega, whose enduring pedagogical legacy shaped generations of thinkers—has been developed to efficiently solve the Approximate Dominating Set Problem. This implementation is publicly accessible through the Python Package Index (PyPI): https://pypi.org/project/baldor/. By constructing an approximate solution, the algorithm guarantees an approximation ratio of at most 2 for the Dominating Set Problem.
Table: Code Metadata
Nr. | Code metadata description | Metadata |
---|---|---|
C1 | Current code version | v0.1.3 |
C2 | Permanent link to code/repository | GitHub |
C3 | Permanent link to Reproducible Capsule | PyPI |
C4 | Legal Code License | MIT License |
C5 | Code versioning system used | git |
C6 | Software code languages, tools, and services used | python |
C7 | Compilation requirements, operating environments & dependencies | Python ≥ 3.10 |
Experimental Results
Methodology and Experimental Setup
To evaluate our algorithm rigorously, we use the benchmark instances from the Second DIMACS Implementation Challenge [Johnson1996]. These instances are widely recognized in the computational graph theory community for their diversity and hardness, making them ideal for testing algorithms on the minimum dominating set problem.
The experiments were conducted on a system with the following specifications:
- Processor: 11th Gen Intel® Core™ i7-1165G7 (2.80 GHz, up to 4.70 GHz with Turbo Boost)
- Memory: 32 GB DDR4 RAM
- Operating System: Windows 10 Pro (64-bit)
Our algorithm was implemented using Baldor: Approximate Minimum Dominating Set Solver (v0.1.3) [Vega25], a custom implementation designed to achieve a 2-approximation guarantee for the minimum dominating set problem. As a baseline for comparison, we employed the weighted dominating set approximation algorithm provided by NetworkX [Vazirani2001], which guarantees a solution within a logarithmic approximation ratio of log ∣ V ∣ ⋅ O P T \log |V| \cdot OPT log∣V∣⋅OPT , where O P T OPT OPT is the size of the optimal dominating set and ∣ V ∣ |V| ∣V∣ is the number of vertices in the graph.
Each algorithm was run on the same set of DIMACS instances, and the results were recorded to ensure a fair comparison. We repeated each experiment three times and report the average runtime to account for system variability.
Performance Metrics
We evaluate the performance of our algorithm using the following metrics:
- Runtime (milliseconds): The total computation time required to compute the dominating set, measured in milliseconds. This metric reflects the algorithm’s efficiency and scalability on graphs of varying sizes.
- Approximation Quality: To quantify the quality of the solutions produced by our algorithm, we compute the upper bound on the approximation ratio, defined as:
log ∣ V ∣ ⋅ ∣ O P T B ∣ ∣ O P T W ∣ \log |V| \cdot \frac{|OPT_B|}{|OPT_W|} log∣V∣⋅∣OPTW∣∣OPTB∣
where:
- ∣ O P T B ∣ |OPT_B| ∣OPTB∣ : The size of the dominating set produced by our algorithm (Baldor).
- ∣ O P T W ∣ |OPT_W| ∣OPTW∣ : The size of the dominating set produced by the NetworkX baseline.
- ∣ V ∣ |V| ∣V∣ : The number of vertices in the graph.
Given the theoretical guarantees, NetworkX ensures ∣ O P T W ∣ ≤ log ∣ V ∣ ⋅ O P T |OPT_W| \leq \log |V| \cdot OPT ∣OPTW∣≤log∣V∣⋅OPT , and our algorithm guarantees ∣ O P T B ∣ ≤ 2 ⋅ O P T |OPT_B| \leq 2 \cdot OPT ∣OPTB∣≤2⋅OPT , where O P T OPT OPT is the optimal dominating set size (unknown in practice). Thus, the metric log ∣ V ∣ ⋅ ∣ O P T B ∣ ∣ O P T W ∣ \log |V| \cdot \frac{|OPT_B|}{|OPT_W|} log∣V∣⋅∣OPTW∣∣OPTB∣ provides insight into how close our solution is to the theoretical 2-approximation bound. A value near 2 indicates that our algorithm is performing near-optimally relative to the baseline.
Results and Analysis
The experimental results for a subset of the DIMACS instances are listed below. Each entry includes the dominating set size and runtime (in milliseconds) for our algorithm ( ∣ O P T B ∣ |OPT_B| ∣OPTB∣ ) and the NetworkX baseline ( ∣ O P T W ∣ |OPT_W| ∣OPTW∣ ), along with the computed approximation quality metric log ∣ V ∣ ⋅ ∣ O P T B ∣ ∣ O P T W ∣ \log |V| \cdot \frac{|OPT_B|}{|OPT_W|} log∣V∣⋅∣OPTW∣∣OPTB∣ .
- p_hat500-1.clq: ∣ O P T B ∣ = 10 |OPT_B| = 10 ∣OPTB∣=10 (259.872 ms), ∣ O P T W ∣ = 14 |OPT_W| = 14 ∣OPTW∣=14 (37.267 ms), log ∣ V ∣ ⋅ ∣ O P T B ∣ ∣ O P T W ∣ = 4.439006 \log |V| \cdot \frac{|OPT_B|}{|OPT_W|} = 4.439006 log∣V∣⋅∣OPTW∣∣OPTB∣=4.439006
- p_hat500-2.clq: ∣ O P T B ∣ = 5 |OPT_B| = 5 ∣OPTB∣=5 (535.828 ms), ∣ O P T W ∣ = 7 |OPT_W| = 7 ∣OPTW∣=7 (31.832 ms), log ∣ V ∣ ⋅ ∣ O P T B ∣ ∣ O P T W ∣ = 4.439006 \log |V| \cdot \frac{|OPT_B|}{|OPT_W|} = 4.439006 log∣V∣⋅∣OPTW∣∣OPTB∣=4.439006
- p_hat500-3.clq: ∣ O P T B ∣ = 3 |OPT_B| = 3 ∣OPTB∣=3 (781.632 ms), ∣ O P T W ∣ = 3 |OPT_W| = 3 ∣OPTW∣=3 (19.985 ms), log ∣ V ∣ ⋅ ∣ O P T B ∣ ∣ O P T W ∣ = 6.214608 \log |V| \cdot \frac{|OPT_B|}{|OPT_W|} = 6.214608 log∣V∣⋅∣OPTW∣∣OPTB∣=6.214608
- p_hat700-1.clq: ∣ O P T B ∣ = 10 |OPT_B| = 10 ∣OPTB∣=10 (451.447 ms), ∣ O P T W ∣ = 22 |OPT_W| = 22 ∣OPTW∣=22 (70.044 ms), log ∣ V ∣ ⋅ ∣ O P T B ∣ ∣ O P T W ∣ = 2.977764 \log |V| \cdot \frac{|OPT_B|}{|OPT_W|} = 2.977764 log∣V∣⋅∣OPTW∣∣OPTB∣=2.977764
- p_hat700-2.clq: ∣ O P T B ∣ = 6 |OPT_B| = 6 ∣OPTB∣=6 (1311.585 ms), ∣ O P T W ∣ = 8 |OPT_W| = 8 ∣OPTW∣=8 (69.925 ms), log ∣ V ∣ ⋅ ∣ O P T B ∣ ∣ O P T W ∣ = 4.913310 \log |V| \cdot \frac{|OPT_B|}{|OPT_W|} = 4.913310 log∣V∣⋅∣OPTW∣∣OPTB∣=4.913310
- p_hat700-3.clq: ∣ O P T B ∣ = 3 |OPT_B| = 3 ∣OPTB∣=3 (1495.283 ms), ∣ O P T W ∣ = 3 |OPT_W| = 3 ∣OPTW∣=3 (72.238 ms), log ∣ V ∣ ⋅ ∣ O P T B ∣ ∣ O P T W ∣ = 6.551080 \log |V| \cdot \frac{|OPT_B|}{|OPT_W|} = 6.551080 log∣V∣⋅∣OPTW∣∣OPTB∣=6.551080
- san1000.clq: ∣ O P T B ∣ = 4 |OPT_B| = 4 ∣OPTB∣=4 (1959.679 ms), ∣ O P T W ∣ = 40 |OPT_W| = 40 ∣OPTW∣=40 (630.204 ms), log ∣ V ∣ ⋅ ∣ O P T B ∣ ∣ O P T W ∣ = 0.690776 \log |V| \cdot \frac{|OPT_B|}{|OPT_W|} = 0.690776 log∣V∣⋅∣OPTW∣∣OPTB∣=0.690776
- san200_0.7_1.clq: ∣ O P T B ∣ = 3 |OPT_B| = 3 ∣OPTB∣=3 (93.572 ms), ∣ O P T W ∣ = 4 |OPT_W| = 4 ∣OPTW∣=4 (5.196 ms), log ∣ V ∣ ⋅ ∣ O P T B ∣ ∣ O P T W ∣ = 3.973738 \log |V| \cdot \frac{|OPT_B|}{|OPT_W|} = 3.973738 log∣V∣⋅∣OPTW∣∣OPTB∣=3.973738
- san200_0.7_2.clq: ∣ O P T B ∣ = 3 |OPT_B| = 3 ∣OPTB∣=3 (103.698 ms), ∣ O P T W ∣ = 5 |OPT_W| = 5 ∣OPTW∣=5 (6.463 ms), log ∣ V ∣ ⋅ ∣ O P T B ∣ ∣ O P T W ∣ = 3.178990 \log |V| \cdot \frac{|OPT_B|}{|OPT_W|} = 3.178990 log∣V∣⋅∣OPTW∣∣OPTB∣=3.178990
- san200_0.9_1.clq: ∣ O P T B ∣ = 2 |OPT_B| = 2 ∣OPTB∣=2 (115.282 ms), ∣ O P T W ∣ = 2 |OPT_W| = 2 ∣OPTW∣=2 (0.000 ms), log ∣ V ∣ ⋅ ∣ O P T B ∣ ∣ O P T W ∣ = 5.298317 \log |V| \cdot \frac{|OPT_B|}{|OPT_W|} = 5.298317 log∣V∣⋅∣OPTW∣∣OPTB∣=5.298317
- san200_0.9_2.clq: ∣ O P T B ∣ = 2 |OPT_B| = 2 ∣OPTB∣=2 (120.091 ms), ∣ O P T W ∣ = 2 |OPT_W| = 2 ∣OPTW∣=2 (5.012 ms), log ∣ V ∣ ⋅ ∣ O P T B ∣ ∣ O P T W ∣ = 5.298317 \log |V| \cdot \frac{|OPT_B|}{|OPT_W|} = 5.298317 log∣V∣⋅∣OPTW∣∣OPTB∣=5.298317
- san200_0.9_3.clq: ∣ O P T B ∣ = 2 |OPT_B| = 2 ∣OPTB∣=2 (110.157 ms), ∣ O P T W ∣ = 3 |OPT_W| = 3 ∣OPTW∣=3 (0.000 ms), log ∣ V ∣ ⋅ ∣ O P T B ∣ ∣ O P T W ∣ = 3.532212 \log |V| \cdot \frac{|OPT_B|}{|OPT_W|} = 3.532212 log∣V∣⋅∣OPTW∣∣OPTB∣=3.532212
- san400_0.5_1.clq: ∣ O P T B ∣ = 4 |OPT_B| = 4 ∣OPTB∣=4 (243.552 ms), ∣ O P T W ∣ = 24 |OPT_W| = 24 ∣OPTW∣=24 (45.267 ms), log ∣ V ∣ ⋅ ∣ O P T B ∣ ∣ O P T W ∣ = 0.998577 \log |V| \cdot \frac{|OPT_B|}{|OPT_W|} = 0.998577 log∣V∣⋅∣OPTW∣∣OPTB∣=0.998577
- san400_0.7_1.clq: ∣ O P T B ∣ = 3 |OPT_B| = 3 ∣OPTB∣=3 (419.706 ms), ∣ O P T W ∣ = 6 |OPT_W| = 6 ∣OPTW∣=6 (20.579 ms), log ∣ V ∣ ⋅ ∣ O P T B ∣ ∣ O P T W ∣ = 2.995732 \log |V| \cdot \frac{|OPT_B|}{|OPT_W|} = 2.995732 log∣V∣⋅∣OPTW∣∣OPTB∣=2.995732
- san400_0.7_2.clq: ∣ O P T B ∣ = 3 |OPT_B| = 3 ∣OPTB∣=3 (405.550 ms), ∣ O P T W ∣ = 6 |OPT_W| = 6 ∣OPTW∣=6 (24.712 ms), log ∣ V ∣ ⋅ ∣ O P T B ∣ ∣ O P T W ∣ = 2.995732 \log |V| \cdot \frac{|OPT_B|}{|OPT_W|} = 2.995732 log∣V∣⋅∣OPTW∣∣OPTB∣=2.995732
- san400_0.7_3.clq: ∣ O P T B ∣ = 3 |OPT_B| = 3 ∣OPTB∣=3 (452.306 ms), ∣ O P T W ∣ = 9 |OPT_W| = 9 ∣OPTW∣=9 (33.302 ms), log ∣ V ∣ ⋅ ∣ O P T B ∣ ∣ O P T W ∣ = 1.997155 \log |V| \cdot \frac{|OPT_B|}{|OPT_W|} = 1.997155 log∣V∣⋅∣OPTW∣∣OPTB∣=1.997155
- san400_0.9_1.clq: ∣ O P T B ∣ = 2 |OPT_B| = 2 ∣OPTB∣=2 (453.124 ms), ∣ O P T W ∣ = 3 |OPT_W| = 3 ∣OPTW∣=3 (20.981 ms), log ∣ V ∣ ⋅ ∣ O P T B ∣ ∣ O P T W ∣ = 3.994310 \log |V| \cdot \frac{|OPT_B|}{|OPT_W|} = 3.994310 log∣V∣⋅∣OPTW∣∣OPTB∣=3.994310
- sanr200_0.7.clq: ∣ O P T B ∣ = 3 |OPT_B| = 3 ∣OPTB∣=3 (96.323 ms), ∣ O P T W ∣ = 4 |OPT_W| = 4 ∣OPTW∣=4 (7.047 ms), log ∣ V ∣ ⋅ ∣ O P T B ∣ ∣ O P T W ∣ = 3.973738 \log |V| \cdot \frac{|OPT_B|}{|OPT_W|} = 3.973738 log∣V∣⋅∣OPTW∣∣OPTB∣=3.973738
- sanr200_0.9.clq: ∣ O P T B ∣ = 2 |OPT_B| = 2 ∣OPTB∣=2 (116.587 ms), ∣ O P T W ∣ = 2 |OPT_W| = 2 ∣OPTW∣=2 (2.892 ms), log ∣ V ∣ ⋅ ∣ O P T B ∣ ∣ O P T W ∣ = 5.298317 \log |V| \cdot \frac{|OPT_B|}{|OPT_W|} = 5.298317 log∣V∣⋅∣OPTW∣∣OPTB∣=5.298317
- sanr400_0.5.clq: ∣ O P T B ∣ = 6 |OPT_B| = 6 ∣OPTB∣=6 (340.535 ms), ∣ O P T W ∣ = 7 |OPT_W| = 7 ∣OPTW∣=7 (20.473 ms), log ∣ V ∣ ⋅ ∣ O P T B ∣ ∣ O P T W ∣ = 5.135541 \log |V| \cdot \frac{|OPT_B|}{|OPT_W|} = 5.135541 log∣V∣⋅∣OPTW∣∣OPTB∣=5.135541
- sanr400_0.7.clq: ∣ O P T B ∣ = 3 |OPT_B| = 3 ∣OPTB∣=3 (490.877 ms), ∣ O P T W ∣ = 5 |OPT_W| = 5 ∣OPTW∣=5 (22.703 ms), log ∣ V ∣ ⋅ ∣ O P T B ∣ ∣ O P T W ∣ = 3.594879 \log |V| \cdot \frac{|OPT_B|}{|OPT_W|} = 3.594879 log∣V∣⋅∣OPTW∣∣OPTB∣=3.594879
Our analysis of the results yields the following insights:
-
Runtime Efficiency: Our algorithm, implemented in Baldor, exhibits competitive runtime performance compared to NetworkX, particularly on larger instances like
san1000.clq
. However, NetworkX is generally faster on smaller graphs (e.g.,san200_0.9_1.clq
with a runtime of 0.000 ms), likely due to its simpler heuristic approach. In contrast, our algorithm’s runtime increases with graph size (e.g., 1959.679 ms forsan1000.clq
), reflecting the trade-off for achieving a better approximation guarantee. This suggests that while our algorithm is more computationally intensive, it scales reasonably well for the improved solution quality it provides. -
Approximation Quality: The approximation quality metric log ∣ V ∣ ⋅ ∣ O P T B ∣ ∣ O P T W ∣ \log |V| \cdot \frac{|OPT_B|}{|OPT_W|} log∣V∣⋅∣OPTW∣∣OPTB∣ frequently approaches the theoretical 2-approximation bound, with values such as 1.997155 for
san400_0.7_3.clq
and 2.977764 forp_hat700-1.clq
. In cases likesan1000.clq
(0.690776), our algorithm significantly outperforms NetworkX, producing a dominating set of size 4 compared to NetworkX’s 40. However, for instances where ∣ O P T B ∣ = ∣ O P T W ∣ |OPT_B| = |OPT_W| ∣OPTB∣=∣OPTW∣ (e.g.,p_hat500-3.clq
), the metric exceeds 2 due to the logarithmic factor, indicating that both algorithms may be far from the true optimum. Overall, our algorithm consistently achieves solutions closer to the theoretical optimum, validating its 2-approximation guarantee.
Discussion and Implications
The results highlight a favorable trade-off between solution quality and computational efficiency for our algorithm. On instances where approximation accuracy is critical, such as san1000.clq
and san400_0.5_1.clq
, our algorithm produces significantly smaller dominating sets than NetworkX, demonstrating its practical effectiveness. However, the increased runtime on larger graphs suggests opportunities for optimization, particularly in reducing redundant computations or leveraging parallelization.
These findings position our algorithm as a strong candidate for applications requiring high-quality approximations, such as network design, facility location, and clustering problems, where a 2-approximation guarantee can lead to substantial cost savings. For scenarios prioritizing speed over solution quality, the NetworkX baseline may be preferable due to its faster execution.
Future Work
Future research will focus on optimizing the runtime performance of our algorithm without compromising its approximation guarantees. Potential directions include:
- Implementing heuristic-based pruning techniques to reduce the search space.
- Exploring parallel and distributed computing to handle larger graphs more efficiently.
- Extending the algorithm to handle weighted dominating set problems, broadening its applicability.
Additionally, we plan to evaluate our algorithm on real-world graphs from domains such as social networks and biological networks, where structural properties may further highlight the strengths of our approach.
References
- [Johnson1996] Johnson, D. S., & Trick, M. A. (1996). Cliques, Coloring, and Satisfiability: Second DIMACS Implementation Challenge. DIMACS Series in Discrete Mathematics and Theoretical Computer Science.
- [Vega25] Vega, D. (2025). Baldor: Approximate Minimum Dominating Set Solver (v0.1.3). Software Library.
- [Vazirani2001] Vazirani, V. V. (2001). Approximation Algorithms. Springer.
Impact of This Result
- Theoretical Insight: These algorithms illustrate how structural properties (e.g., bipartiteness) can be leveraged or induced to solve NP-hard problems like the dominating set problem with approximation guarantees.
- Practical Utility: The polynomial-time O ( n log n + m ) O(n \log n + m) O(nlogn+m) solution for general graphs is applicable in network optimization problems, such as placing facilities or monitors, where covering all nodes efficiently is critical.
- P = N P P = NP P=NP : Our algorithm’s existence would imply P = N P P = NP P=NP (Raz, Ran, and Shmuel Safra. 1997. “A sub-constant error-probability low-degree test, and a sub-constant error-probability PCP characterization of NP.” Proceedings of the 29th Annual ACM Symposium on Theory of Computing (STOC), 475–84. doi: 10.1145/258533.258641), with transformative consequences. This makes P P P vs. N P NP NP not just a theoretical question but one with profound practical implications.
Conclusion
These algorithms advance the field of approximation algorithms by balancing efficiency and solution quality, offering both theoretical depth and practical relevance. Their argument implies that P = N P P = NP P=NP would have far-reaching practical applications, particularly in artificial intelligence, medicine, and industrial sectors. This work is available as a PDF document on Preprints at the following link: https://www.preprints.org/manuscript/202504.0522/v1.
原文链接:Bipartite-Based 2-Approximation for Dominating Sets in General Graphs
暂无评论内容