find the most valuable vertex among all reachable vertices

Viewed 1255

I have a Directed Graph G=(V,E) that each vertex v has two properties:

  • r indicating the worthiness
  • m indicating the highest v''s r (where v' is a reachable vertex from v).

I need to find ms for all vertices in O(|V|+|E|) time.

For example,

Initial G

A(r = 1, m = 1) → B(r = 3, m = 3) ← C(r = 2, m = 2)
↓
D(r = 4, m = 4)

has to be

A(r = 1, m = 4) → B(r = 3, m = 3) ← C(r = 2, m = 3)
↓
D(r = 4, m = 4)

I searched SO and found some Here, but one of the answers does not bound in time and another answer is very badly explained. Is there any simpler idea here?

5 Answers

In practice, I would use use the algorithm from Ehsan's answer, but it's not quite O(V+E). If you really need that complexity, then you can do this:

  1. Divide the graph into strongly-connected components using, e.g., Tarjan's algorithm This is O(V+E).
  2. Make a graph of the SCCs. Every node in an SCC is reachable from every other one, so the node for each SCC in the new graph gets the highest r value in the SCC. You can do this in O(V+E) too.
  3. The graph of SCCs is acyclic, so you can do a topological sort. All the popular algorithms for that are O(V+E).
  4. Process the SCC nodes in reverse topological order, calculating each m from neighbors. Because all the edges point from later to earlier nodes, the inputs for each node will be finished by the time you get to it. This is O(V+E) too.
  5. Go through the original graph, setting every node's m to the value for its component in the SCC graph. O(V)

Use following O(E+V*log(V)) algorithm :

- Reverse all directions
- while |V| > 0 do
    find max(v) from remaining nodes in V
    from that node execute DFS and find all reachable nodes and update their m as max(V)
    remove all updated nodes from V

the time-complexity of this algorithm is as your request O(V*log(V)+E)

How to solve the problem?

  1. Reachable vertices in a directed graph
  • Which vertices can a given vertex visit?
  • Which vertices can visit the given vertex?

We are dealing with directed graphs. So, we need to find strongly connected components to answer the questions like above efficiently for this problem.

enter image description here

  1. Once we know the strongly connected components, we can deal with the highest worthiness part.

In every strongly connected component, what is the highest worthiness value? Update accordingly.

Both steps are possible with O(V + E). With proper thought process, I believe it should be able to do both the steps in a single pass.

How to find strongly connected components?

  1. Kosaraju's algorithm
  2. Tarjan's algorithm
  3. Path-based strong component algorithm
  • If you are looking for something simple, go for Kosaraju's algorithm. To me, it is the simplest of the above three.
  • If you are looking for efficiency, Kosaraju's algorithm takes two depth-first traversals but the other two algorithms accomplish the same within 1 depth-first traversal.
  • A Space-Efficient Algorithm for Finding Strongly Connected Components mentions that Tarjan’s algorithm required at most v(2 + 5w) bits of storage, where w is the machine’s word size. The improvement mentioned in the paper reduces the space requirements to v(1 + 3w) bits in the worst case.

Implementation:

Apparently, you are looking for some type of implementation.

I am adding this answer, although there are correct answers with upvotes before me, only because you tagged and . So I will add java implementation now, and if needed the python implementation will follow.

The algorithm

This is a tweak on the classic topological sort:

  1. foreach vertex:
    1. foreach neighbour:
      1. if didn't yet calculate m, calculate.
    2. Take the maximum of yourself and neighbours. Mark yourself as visited, and if asked again for m, return the calculated.

It is implemented at calculateMostValuableVertex.

Time computation complexity

  1. foreach vertex (O(|V|)) 2. foreach edge(O(|E|) totally, as it will eventually go over each edge once.):

    1. If not yet computed, compute m.

Please note that foreach vertex, it will be calculated either in stage 1, or 3. not twice, wince it is checked before the calculation. Therefore the time complexity of this algorithm is O(|V| + |E|)

Assumptions

This solution relies heavily on the fact that HashMap in Java does operations such as add/update in O(1). That is true in average, but if that is not enough, the same idea can be fully implemented only with arrays, which will improve the solution into O(|V|+|E|) in the worst case.

Implementation

Let's first define the basic classes:

  1. Vertex:

    import java.util.ArrayList;
    
    class Vertex {
        String label;
        public int r; // Worthiness
        public int m; // Highest worthiness.
    
        Vertex(String label, int r, int m) {
            this.label = label;
            this.r = r;
            this.m = m;
        }
    
        @Override
        public int hashCode() {
            final int prime = 31;
            int result = 1;
            result = prime * result * r * m
                    + ((label == null) ? 0 : label.hashCode());
            return result;
        }
    
        @Override
        public boolean equals(final Object obj) {
            if (this == obj)
                return true;
            if (obj == null)
                return false;
            if (getClass() != obj.getClass())
                return false;
            final Vertex other = (Vertex) obj;
    
            boolean labelEquals;
            if (label == null) {
                labelEquals = other.label == null;
            } else {
                labelEquals = label.equals(other.label);
            }
            return labelEquals && r == other.r && m == other.m;
        }
    
        @Override
        public String toString() {
            return "Vertex{" +
                    "label='" + label + '\'' +
                    ", r=" + r +
                    ", m=" + m +
                    '}';
        }
    }
    

    It is important to define the methods equals and hashCode so later on their hash computations will work as expected.

  2. Graph:

    class Graph {
    
        private final Map<Vertex, List<Vertex>> adjVertices = new HashMap<>();
        private final Map<String, Vertex> nameToVertex = new HashMap<>();
        private final List<Vertex> vertices = new ArrayList<>();
    
        void addVertex(String label, int r, int m) {
            Vertex vertex = new Vertex(label, r, m);
            adjVertices.putIfAbsent(vertex, new ArrayList<>());
            nameToVertex.putIfAbsent(label, vertex);
            vertices.add(vertex);
        }
    
        void addEdge(String label1, String label2) {
            adjVertices.get(nameToVertex.get(label1)).add(nameToVertex.get(label2));
        }
    
        public void calculateMostValuableVertex() {
            Map<Vertex, Boolean> visitedVertices = new HashMap<>();
            for (Vertex vertex : vertices) {
                visitedVertices.put(vertex, false);
            }
    
            for (Vertex vertex : vertices) {
                if (visitedVertices.get(vertex)) {
                    continue;
                }
    
                calculateMostValuableVertexInternal(vertex, visitedVertices);
            }
        }
    
        public void calculateMostValuableVertexInternal(Vertex vertex, Map<Vertex, Boolean> visitedVertices) {
            List<Vertex> neighbours = adjVertices.get(vertex);
            visitedVertices.put(vertex, true);
            int max = vertex.r;
    
            for (Vertex neighbour: neighbours) {
                if (visitedVertices.get(neighbour)) {
                    max = Math.max(max, neighbour.m);
                } else {
                    calculateMostValuableVertexInternal(neighbour, visitedVertices);
                    max = Math.max(max, neighbour.m);
                }
            }
    
            vertex.m = max;
        }
    
        @Override
        public String toString() {
            StringBuilder sb = new StringBuilder();
            Iterator<Map.Entry<Vertex, List<Vertex>>> iter = adjVertices.entrySet().iterator();
            while (iter.hasNext()) {
                Map.Entry<Vertex, List<Vertex>> entry = iter.next();
                sb.append(entry.getKey());
                sb.append('=').append('"');
                sb.append(entry.getValue());
                sb.append('"');
                if (iter.hasNext()) {
                    sb.append(',').append('\n');
                }
            }
    
            return "Graph{" +
                    "adjVertices=\n" + sb +
                    '}';
        }
    }
    

Finally, to run the above logic, you can do:

Graph g = new Graph();

g.addVertex("A", 1, 1);
g.addVertex("B", 3, 3);
g.addVertex("C", 2, 2);
g.addVertex("D", 4, 4);

g.addEdge("A", "B");
g.addEdge("C", "B");
g.addEdge("A", "D");

g.calculateMostValuableVertex();

System.out.println(g);

The output of the above is:

Graph{adjVertices=
Vertex{label='A', r=1, m=4}="[Vertex{label='B', r=3, m=3}, Vertex{label='D', r=4, m=4}]",
Vertex{label='D', r=4, m=4}="[]",
Vertex{label='B', r=3, m=3}="[]",
Vertex{label='C', r=2, m=3}="[Vertex{label='B', r=3, m=3}]"}

as expected. It supports graphs with cycles as well. For example the output of:

Graph g = new Graph();

g.addVertex("A", 1, 1);
g.addVertex("B", 3, 3);
g.addVertex("C", 2, 2);
g.addVertex("D", 4, 4);
g.addVertex("E", 5, 5);
g.addVertex("F", 6, 6);
g.addVertex("G", 7, 7);

g.addEdge("A", "B");
g.addEdge("C", "B");
g.addEdge("A", "D");
g.addEdge("A", "E");
g.addEdge("E", "F");
g.addEdge("F", "G");
g.addEdge("G", "A");

g.calculateMostValuableVertex();

System.out.println(g);

is:

Graph{adjVertices=
Vertex{label='A', r=1, m=7}="[Vertex{label='B', r=3, m=3}, Vertex{label='D', r=4, m=4}, Vertex{label='E', r=5, m=7}]",
Vertex{label='B', r=3, m=3}="[]",
Vertex{label='C', r=2, m=3}="[Vertex{label='B', r=3, m=3}]",
Vertex{label='D', r=4, m=4}="[]",
Vertex{label='E', r=5, m=7}="[Vertex{label='F', r=6, m=7}]",
Vertex{label='F', r=6, m=7}="[Vertex{label='G', r=7, m=7}]",
Vertex{label='G', r=7, m=7}="[Vertex{label='A', r=1, m=7}]"}

I implemented my answer from the linked question in Python. The lines that don't reference minreach closely follow Wikipedia's description of Tarjan's SCC algorithm.

import random


def random_graph(n):
    return {
        i: {random.randrange(n) for j in range(random.randrange(n))} for i in range(n)
    }


class SCC:
    def __init__(self, graph):
        self.graph = graph
        self.index = {}
        self.lowlink = {}
        self.stack = []
        self.stackset = set()
        self.minreach = {}
        self.components = []

    def dfs(self, v):
        self.lowlink[v] = self.index[v] = len(self.index)
        self.stack.append(v)
        self.stackset.add(v)
        self.minreach[v] = v
        for w in self.graph[v]:
            if w not in self.index:
                self.dfs(w)
                self.lowlink[v] = min(self.lowlink[v], self.lowlink[w])
            elif w in self.stackset:
                self.lowlink[v] = min(self.lowlink[v], self.index[w])
            self.minreach[v] = min(self.minreach[v], self.minreach[w])
        if self.lowlink[v] == self.index[v]:
            component = set()
            while True:
                w = self.stack.pop()
                self.stackset.remove(w)
                self.minreach[w] = self.minreach[v]
                component.add(w)
                if w == v:
                    break
            self.components.append(component)

    def scc(self):
        for v in self.graph:
            if v not in self.index:
                self.dfs(v)
        return self.components, self.minreach


if __name__ == "__main__":
    g = random_graph(6)
    print(g)
    components, minreach = SCC(g).scc()
    print(components)
    print(minreach)
Related