Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Adding BFS support to AdjacencyMaps. #185

Open
wants to merge 8 commits into
base: main
Choose a base branch
from

Conversation

TheChouzanOne
Copy link

@TheChouzanOne TheChouzanOne commented Apr 7, 2019

I've added a bfsForest algorithm that makes use of "helper" functions for a total of 4 new functions. These are located in AdjacencyMap/Algorithm.hs, however, I believe that the 3 helped functions might be rellocated to keep Algorithm.hs clean.

Complexity analysis is still missing.

Also, the next functions need to be implemented.

-- Run BFS from the list of initial vertices, returning the resulting BFS forest.
bfsForestFrom :: Ord a => [a] -> AdjacencyMap a -> Forest a

-- Run BFS from the list of initial vertices (roots), returning the resulting list of 
-- levels, i.e. vertices that are at the same distance from the roots.
bfs :: Ord a => [a] -> AdjacencyMap a -> [[a]]

A link to posts regarding the development of this PR can be found here.

@TheChouzanOne
Copy link
Author

TheChouzanOne commented Apr 7, 2019

I've started implementing the missing functions. bfsForestFrom seems to be ready, but I need to clarify bfs. To easily see the implementation please refer to this link.

By running bfs on a graph:
a) do you want vertices that are on the same level but different trees to be included in the same list? Or b) should they be in different lists?

For example, let g = (1*2) + (3*4) + (5*6). Running bfs [1,2] g could return
a) [[1,3],[2,4]] or
b) [ [ [1],[2] ] , [ [3],[4] ] ]

The type signature indicates the option (a) is the correct one, but (b) splits the different trees generated, which would require [[[a]]] to be returned.

@snowleopard
Copy link
Owner

snowleopard commented Apr 13, 2019

a) do you want vertices that are on the same level but different trees to be included in the same list?

Yes. The first element of the list contains the roots that are present in the graph, the second element contains all vertices reachable from roots in 1 step, etc.

For example, let g = (1*2) + (3*4) + (5*6). Running bfs [1,2] g could return
a) [[1,3],[2,4]] or
b) [ [ [1],[2] ] , [ [3],[4] ] ]

Neither a) not b) matches the desired output, which in this case should be just [[1,2]], because nothing else is reachable from the roots 1 and 2.

Another example with your graph: bfs [1,3] g == [[1,3], [2,4]].


-- | Compute the /breadth-first search/ AdjacencyMap of a graph that corresponds to
-- searching from a single vertex of the graph.
-- This is just for internal use. Might move it to `*.Internal` then?
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No need to create an internal module, simply don't export internal functions.

@TheChouzanOne
Copy link
Author

Another example with your graph: bfs [1,3] g == [[1,3], [2,4]].

There was an error with my example. bfs [1,3] g was what I meant. Thank you for the clarification.

… rewrote bfsForest to reutilize code and make it cleaner.
@snowleopard
Copy link
Owner

@TheChouzanOne Thanks for adding complexity bounds. O(n^2 * log(n)) sounds too slow though! Can we get closer to linear O(n+m) or at least O((n+m)*log(n))?

@TheChouzanOne
Copy link
Author

TheChouzanOne commented Apr 24, 2019

Can we get closer to linear O(n+m) or at least O((n+m)*log(n))?

I've just realized that my rough estimate was completely wrong and the real bound was O (n^2). Still, pretty slow. I've changed the implementation of bfsTreeAdjacencyMap to run in O (v + e * log(v) ) time now, much better.

That log (v) comes from the fact that Map.lookup runs in O (log(n)). If time complexity was constant, the algorithm could run in O(v+e), but I don't know which Haskell's data structure could help here.

Also, I added a new internal function to achieve less running time, which I've documented. I've been pretty busy and haven't been able to update my blog, but plan to do so with these last updates once I have time.

Anyways, what do you think about the current state of the code?

@TheChouzanOne
Copy link
Author

TheChouzanOne commented Apr 24, 2019

Wait, I've just realized something and am confused. I am not sure if I am tired (it's 3 a.m.), but isn't O (v + e * log(v) ) not much of an improvement? Given a full graph, e=(v * (v-1)) /2 = O(v^2), so time complexity would be O (v^2 * log(v) ), which is worse than expected. Am I correct or tired?

@snowleopard
Copy link
Owner

but isn't O (v + e * log(v) ) not much of an improvement?

@TheChouzanOne This is a big improvement, because graphs are rarely fully connected! In the most typical case we have something like m = O(n).

Thanks for the revision, I'll take a look soon.

@snowleopard
Copy link
Owner

P.S.: I'm using n = |V| and m = |E|, as in the Alga documentation.

@TheChouzanOne
Copy link
Author

I've changed documentation notation to n and m for vertices and edges, respectively.

@snowleopard
Copy link
Owner

snowleopard commented Apr 29, 2019

@TheChouzanOne Thanks for the fixes!

At the moment the implementation looks unnecessarily complex to me. I don't understand why we need auxiliary functions like bfsTreeAdjacencyMap and others.

BFS is a very simple algorithm conceptually: you traverse all vertices starting with a root, marking each vertex with its parent. This information should be sufficient for extracting a tree for each root.

Furthermore, each example should have an associated test in the testsuite. You can have a look at how each example is tested by looking at the implementation of DFS.

@TheChouzanOne
Copy link
Author

At the moment the implementation looks unnecessarily complex to me. I don't understand why we need auxiliary functions like bfsTreeAdjacencyMap and others.

I agree that implementation looks too complex. The reason I decided to use auxiliary functions like the one you mention was because I thought it owuld be easier to build an AdjacencyMap that represents a tree, and then parse it into an actual Tree type.

I will do some research on how this problem is normally solved with functional programming and rewrite the algorithms in a simpler manner. This might take some time given that I have many end of semester school projects at the moment.

Furthermore, each example should have an associated test in the testsuite. You can have a look at how each example is tested by looking at the implementation of DFS.

Sounds good. I'll look into it. Does this have something to do with Travis CI build failing? I don't really have much experience with continous integration software.

@snowleopard
Copy link
Owner

I will do some research on how this problem is normally solved with functional programming and rewrite the algorithms in a simpler manner. This might take some time given that I have many end of semester school projects at the moment.

@TheChouzanOne No problem, take your time. I'll ping you if this becomes more urgent.

Does this have something to do with Travis CI build failing? I don't really have much experience with continous integration software.

No, I don't think the testsuite is related to the CI failures. I've restarted Travis -- hopefully it will complete successully this time.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

2 participants