summaryrefslogtreecommitdiffstats
path: root/lib/puppet/gratr/biconnected.rb
blob: c976b2c04782fe5209b264a92c2e2122d6a4d7c0 (plain)
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
#--
# Copyright (c) 2006 Shawn Patrick Garbett
# 
# Redistribution and use in source and binary forms, with or without modification,
# are permitted provided that the following conditions are met:
# 
#     * Redistributions of source code must retain the above copyright notice(s),
#       this list of conditions and the following disclaimer.
#     * Redistributions in binary form must reproduce the above copyright notice,
#       this list of conditions and the following disclaimer in the documentation
#       and/or other materials provided with the distribution.
#     * Neither the name of the Shawn Garbett nor the names of its contributors
#       may be used to endorse or promote products derived from this software
#       without specific prior written permission.
# 
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE
# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#++


require 'set'

module GRATR
  module Graph    
    # Biconnected is a module for adding the biconnected algorithm to 
    # UndirectedGraphs
    module Biconnected

      # biconnected computes the biconnected subgraphs
      # of a graph using Tarjan's algorithm based on DFS. See: Robert E. Tarjan
      # _Depth_First_Search_and_Linear_Graph_Algorithms_. SIAM Journal on 
      # Computing, 1(2):146-160, 1972
      #
      # The output of the algorithm is a pair, the first value is an 
      # array of biconnected subgraphs. The second is the set of
      # articulation vertices.
      #
      # A connected graph is biconnected if the removal of any single vertex 
      # (and all edges incident on that vertex) cannot disconnect the graph.
      # More generally, the biconnected components of a graph are the maximal
      # subsets of vertices such that the removal of a vertex from a particular
      # component will not disconnect the component. Unlike connected components,
      # vertices may belong to multiple biconnected components: those vertices
      # that belong to more than one biconnected component are called articulation
      # points or, equivalently, cut vertices. Articulation points are vertices
      # whose removal would increase the number of connected components in the graph.
      # Thus, a graph without articulation points is biconnected.
      def biconnected
        dfs_num     = 0
        number      = {}; predecessor = {}; low_point   = {}
        stack       = []; result      = []; articulation= []

        root_vertex  = Proc.new {|v| predecessor[v]=v }
        enter_vertex = Proc.new {|u| number[u]=low_point[u]=(dfs_num+=1) }
        tree_edge  = Proc.new do |e|
          stack.push(e)
          predecessor[e.target] = e.source
        end
        back_edge  = Proc.new do |e|
          if e.target != predecessor[e.source]
            stack.push(e)
            low_point[e.source] = [low_point[e.source], number[e.target]].min
          end
        end
        exit_vertex = Proc.new do |u|
          parent = predecessor[u]
          is_articulation_point = false
          if number[parent] > number[u]
            parent = predecessor[parent]
            is_articulation_point = true
          end
          if parent == u
            is_articulation_point = false if (number[u] + 1) == number[predecessor[u]]
          else
            low_point[parent] = [low_point[parent], low_point[u]].min
            if low_point[u] >= number[parent]
              if number[parent] > number[predecessor[parent]]
                predecessor[u] = predecessor[parent]
                predecessor[parent] = u
              end
              result << (component = self.class.new)
              while number[stack[-1].source] >= number[u]
                component.add_edge!(stack.pop)
              end
              component.add_edge!(stack.pop)
              if stack.empty?
                predecessor[u] = parent
                predecessor[parent] = u
              end
            end
          end
          articulation << u if is_articulation_point
        end

        # Execute depth first search
        dfs({:root_vertex  => root_vertex,
             :enter_vertex => enter_vertex, 
             :tree_edge    => tree_edge,
             :back_edge    => back_edge,
             :exit_vertex  => exit_vertex})
           
        [result, articulation]
      end # biconnected

    end # Biconnected

  end # Graph
end # GRATR