Home > database >  Find connected components from lists of edges
Find connected components from lists of edges

Time:04-23

I need to find the connected components of a graph given its edges.

The graph edges are represented as a list of tuples, and the result needs to be a list of lists of vertices representing the connected components, eg [(1,2), (2,3), (2,6), (5,6) (6,7), (8,9), (9,10)] -> [[1,2,3,5,6,7], [8,9,10]]. There could be any number of connected edges and unconnected graph components in the list. The tuples will always be in ascending order though, if that helps.

I have the signature groupEdges :: [(Int, Int)] -> [[Int]] but I just can't think of how to get from tuples to lists.

I thought of taking one tuple at a time and searching the rest for matching elements, but I don't know how to make this list of sublists.

This question is similar to this question on the CS Stack Exchange, but I don't want to use Data.Graph. I would like to do this without other packages if possible.

-- edit - comments from chepner and ThibautM have given me the first step. I can convert from tuples to lists by calling the function with groupEdges map (\(x,y) -> [x,y]) pairs.

Now I need to take this list of lists and group the connected components eg. [[1,2], [2,3], [2,6], [5,6], [6,7], [8,9], [9,10]] -> [[1,2,3,5,6,7], [8,9,10]]

CodePudding user response:

You mentioned not using packages. The 4 functions I used are relatively easy to implement yourself if you really want. (And I encourage you to either do so or at least lookup their implementation)

Lists are used as set, which is a lot worse in performance than using a dedicated structure e.g. Data.Set. Using a disjoint-set (union-find, merge-find) data structure (referenced in your linked answer) would be even better, but probably not very good as a starting point for understanding

import Data.List (elem, intersect, union, partition)

pairs = [(1,2), (2,3), (2,6), (5,6), (6,7), (8,9), (9,10)]
pairs2 = map (\(x,y) -> [x,y]) pairs

-- add item to list, only if its no already present - using list as set
addItem item list | elem item list = list
                  | otherwise      = item : list

-- used to test whether subgraphs are connected i.e. contain common node
intersects a b = not $ null $ intersect a b

unionAll :: (Eq a) => [[a]] -> [a]
unionAll (x1:x2:xs) = unionAll ((union x1 x2):xs)
unionAll [x] = x
unionAll [] = []

-- find edges that are connected to first edge/subgraph and merge them
groupFirst :: (Eq a) => [[a]] -> [[a]]
groupFirst (x:xs) = (unionAll (x:connected)) : disconnected
    where
-- separate 'xs' edges/subgraphs into those that are connected to 'x' and the rest
      (connected, disconnected) = partition (intersects x) xs
groupFirst [] = []

-- if no more edges/subgraphs can be connected with first edge, continue with remaining (disconnected) edge/subgraph ([5,6] in second iteration)
groupAll :: (Eq a) => [[a]] -> [[a]]
groupAll (x:xs) = y:(groupAll ys)
    where
      (y:ys) = groupFirst (x:xs)
groupAll [] = []

-- after first 'groupAll pairs2' - [[1,2,3,6],[5,6,7],[8,9,10]]

-- repeat this process until no more components can be connected together
groupAllRepeat x = if x /= groupAll x then groupAllRepeat (groupAll x) else x

main = print (groupAllRepeat pairs2)
  • Related