0

0

I am working on a problem where I need to find all nodes at distance k from each other. So if k=3, then I need to find all nodes where they are connected by a path of distance 3. There are no self edges so if i has an edge pointing to s, s can't point back to i directly. I think I thought of two implementations here, both involving BFS. I noticed an edge case where BFS might not visit all edges because nodes might already be visited.

Do BFS at each node. Keep track of the "level" of each node in some array, where distance[0] is the root node, distance1 is all nodes adjacent to the root node, distance[2] are all nodes that are grandchildren of the root node and so on. Then to find all nodes at distance k we look at distance[i] and distance[i+k].

Do BFS once, using the same distance algorithm as above, but don't do BFS at each node. Reverse all of the edges and do BFS again to find any missed paths. This would have a much better time complexity than approach 1, but I am not sure if it would actually explore every edge and path (in my test cases it seemed to).

Is there a better approach to this? As an example in this graph with k = 2:

enter image description here

The paths would be 1 to 3, 1 to 5, 2 to 6, 2 to 5, 4 to 3, 1 to 4.

EDIT: The reversal of edges won't work, my current best bet is just do a BFS and then a DFS at each node until a depth of k is reached.

Citut
  • 847
  • 2
  • 10
  • 25
  • 2 to 5 doesn't seem right in your example. You are also missing 2 to 6 – smac89 Oct 24 '19 at 04:00
  • Is the graph sure to be acyclic? – Beta Oct 24 '19 at 04:18
  • Instead of marking nodes as "visited", mark them as "visited at distance D". That way, nodes can be visited multiple times, but only once at a particular distance. – user3386109 Oct 24 '19 at 05:00
  • @smac89 Sorry, meant to put 2 to 6 there. – Citut Oct 24 '19 at 05:00
  • @Citut When k=2 both {2,4,5}, and {2,4,6} are valid. – user3386109 Oct 24 '19 at 05:02
  • it'd be wise to be sure about whether you want all paths of size k between two nodes or the pairs of node at distance k from each other (the path of minimum size between them is of size k). In the latter case, MBo answered but your 2-5 example cited was unclear – grodzi Oct 24 '19 at 05:05
  • @user753642 all paths are needed. i guess i just missed one when i first put the example up. should be updated now. – Citut Oct 24 '19 at 05:10
  • @user3386109 All paths should be there now. The distances is an interesting idea, I'll try that out by hand and see what happens. However, wouldn't that method still need to use a BFS at every node? Or would a single BFS suffice there? – Citut Oct 24 '19 at 05:12
  • ok and lastly (It is on me I was not clear in previous comment): do you need the paths or just the pairs of node for which there exists such a path of size k? – grodzi Oct 24 '19 at 05:12
  • 1
    @user753642 I am just looking for pairs of nodes. The path doesn't matter – Citut Oct 24 '19 at 05:13
  • @Citut You would need a BFS starting from each node. In an acyclic graph, worse case running time would be O(V^2), since each BFS would visit each node at most once. Given a graph with cycles, the time complexity would be O(k*V^2) since each node could be visited `k/3` times for each BFS. – user3386109 Oct 24 '19 at 05:17
  • @user3386109 does it have to be a bfs? could i just use bfs+dfs? I could use BFS to go to each node and then DFS at every node and terminate when the depth of the dfs is 2. Although I think this would have the same time complexity – Citut Oct 24 '19 at 05:20
  • *"I could use BFS to go to each node"* Typically, a graph is stored as either 1) an array of nodes with an adjacency list for the edges that originate at each node, or 2) an array of nodes, and an adjacency matrix. Either way, you just iterate the array of nodes. You don't need to "find" the nodes. And in fact, it's not guaranteed that every node is reachable from a given start node. – user3386109 Oct 24 '19 at 05:33
  • As for using a DFS. That would work fine with a acyclic graph. I'd have to think about whether you can make it work for a graph that has cycles. My first impression is that it would be trickier than a BFS. – user3386109 Oct 24 '19 at 05:37
  • @user3386109 Good point. I have an adjacency list in my case. The BFS at every node implementation would need a BFS method that takes a source node and depth as well correct? Thinking about it, the "outer" BFS just has visited nodes (like a standard BFS implementation), and the "inner" BFS would need to keep track of where it came from to create the pair when a path of length k is found right? – Citut Oct 24 '19 at 05:39
  • @Citut Yup, that's right. – user3386109 Oct 24 '19 at 05:59

1 Answers1

1

You may consider a basic adjacentry matrix M in which elements are not a 0 or 1 in order to indicate a connection but instead they hold the available paths of size k.

e.g for 2->5 you would store M(2,5) = {1,2} (because there exists a path of length 1 and of length 2 between node 2 and 5)

let a and b two elems of M

a * b is defined as:

ab_res = {} //a set without duplicates
for all size i in a
    for all size j in b
        s = i+j
        append(s) to ab_res
ab_res;

a + b is defined as:

ab_res = {}
for all size i in a
    append(i) to ab_res
for all size j in a
    append(j) to ab_res

This approach allows not to recompute paths of inferior size. It would work with cycles as well.

Below an unoptimized version to illustrate algorithm.

const pathk = 2;
let G = {
    1:[2],
    2:[3,4,5],
    4:[6],
    6:[3]
}
//init M
let M = Array(6).fill(0).map(x=>Array(6).fill(0).map(y=>new Set));
Object.keys(G).forEach(m=>{
    G[m].forEach(to=>{
        M[m-1][to-1] = new Set([1]);
    })
});


function add(sums){
    let arr = sums.flatMap(s=>[...s]);
    return new Set(arr);
}
function times(a,b){
    let s = new Set;
    [...a].forEach(i=>{
        [...b].forEach(j=>{
            s.add(i+j);
        })
    });
    return s;
}
function prod(a,b, pathk){
    //the GOOD OL ugly matrix product :)
    const n = a.length;
    let M = Array(6).fill(0).map(x=>Array(6).fill(0).map(y=>new Set));
    a.forEach((row,i)=>{
        for(let bcol = 0; bcol<n; ++bcol){

            let sum = [];
            for(let k = 0; k<n; ++k){
                sum.push( times(a[i][k], b[k][bcol]) );
            }
            M[i][bcol] = add(sum);
            if(M[i][bcol].has(pathk)){
                console.log('got it ', i+1, bcol+1);
            }
        }
    })
    return M;
}

//note that
//1. you can do exponentiation to fasten stuff
//2. you can discard elems in the set if they equal k (or more...)
//3. you may consider operating the product on an adjency list to save computation time & memory..
let Mi = M.map(r=>r.map(x=>new Set([...x])));//copy
for(let i = 1; i<=pathk; ++i){
    Mi = prod(Mi,M, pathk);
}
marc_s
  • 732,580
  • 175
  • 1,330
  • 1,459
grodzi
  • 5,633
  • 1
  • 15
  • 15