简体   繁体   English

在所有可达顶点中找到最有价值的顶点

[英]find the most valuable vertex among all reachable vertices

I have a Directed Graph G=(V,E) that each vertex v has two properties:我有一个有向图G=(V,E)每个顶点v有两个属性:

  • r indicating the worthiness r表示价值
  • m indicating the highest v' 's r (where v' is a reachable vertex from v ). m表示最高v'r (其中v'是从v到达的顶点)。

I need to find m s for all vertices in O(|V|+|E|) time.我需要在O(|V|+|E|)时间内找到所有顶点的m s 。

For example,例如,

Initial G初始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.我搜索了 SO 并找到了一些Here ,但其中一个答案没有及时约束,另一个答案的解释非常糟糕。 Is there any simpler idea here?这里有更简单的想法吗?

In practice, I would use use the algorithm from Ehsan's answer, but it's not quite O(V+E).在实践中,我会使用 Ehsan 的答案中的算法,但它并不完全是 O(V+E)。 If you really need that complexity, then you can do this:如果你真的需要那种复杂性,那么你可以这样做:

  1. Divide the graph into strongly-connected components using, eg, Tarjan's algorithm This is O(V+E).使用例如Tarjan 算法将图划分为强连接组件,这是O(V+E)。
  2. Make a graph of the SCCs.制作 SCC 的图表。 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. SCC 中的每个节点都可以相互访问,因此新图中每个 SCC 的节点在 SCC 中获得最高的r值。 You can do this in O(V+E) too.你也可以在 O(V+E) 中做到这一点。
  3. The graph of SCCs is acyclic, so you can do a topological sort . SCC 的图是非循环的,因此您可以进行拓扑排序 All the popular algorithms for that are O(V+E).所有流行的算法都是 O(V+E)。
  4. Process the SCC nodes in reverse topological order, calculating each m from neighbors.以反向拓扑顺序处理 SCC 节点,从邻居中计算每个m 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.这也是 O(V+E)。
  5. Go through the original graph, setting every node's m to the value for its component in the SCC graph. Go 通过原始图,将每个节点的m设置为其在 SCC 图中的组件的值。 O(V) O(V)

Use following O(E+V*log(V)) algorithm:使用以下O(E+V*log(V))算法:

- 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)该算法的时间复杂度是您的要求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.因此,我们需要找到强连接的组件来有效地回答上述问题。

在此处输入图像描述

  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). 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 Kosaraju 算法
  2. Tarjan's algorithm Tarjan 算法
  3. Path-based strong component algorithm 基于路径的强分量算法
  • If you are looking for something simple, go for Kosaraju's algorithm.如果您正在寻找简单的东西,请使用 Kosaraju 算法的 go。 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.如果您正在寻找效率,Kosaraju 的算法需要两次深度优先遍历,但其他两种算法在 1 次深度优先遍历中完成相同的操作。
  • 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. A Space-Efficient Algorithm for Finding Strongly Connected Components提到 Tarjan 的算法最多需要 v(2 + 5w) 位存储空间,其中 w 是机器的字长。 The improvement mentioned in the paper reduces the space requirements to v(1 + 3w) bits in the worst case.论文中提到的改进在最坏的情况下将空间需求减少到 v(1 + 3w) 位。

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.所以我现在将添加 java 实现,如果需要,将遵循 python 实现。

The algorithm算法

This is a tweak on the classic topological sort:这是对经典拓扑排序的调整:

  1. foreach vertex: foreach 顶点:
    1. foreach neighbour: foreach 邻居:
      1. if didn't yet calculate m , calculate.如果尚未计算m ,请计算。
    2. Take the maximum of yourself and neighbours.最大限度地利用自己和邻居。 Mark yourself as visited, and if asked again for m , return the calculated.将自己标记为已访问,如果再次询问m ,则返回计算结果。

It is implemented at calculateMostValuableVertex .它在calculateMostValuableVertex实现。

Time computation complexity时间计算复杂度

  1. foreach vertex (O(|V|)) 2. foreach edge(O(|E|) totally, as it will eventually go over each edge once.): foreach vertex (O(|V|)) 2. foreach edge(O(|E|) 完全,因为它最终会在每条边上 go 一次。):

    1. If not yet computed, compute m .如果尚未计算,请计算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.请注意,对于每个顶点,它将在第 1 阶段或第 3 阶段计算。不是两次,因为在计算之前会检查它。 Therefore the time complexity of this algorithm is O(|V| + |E|)因此该算法的时间复杂度为 O(|V| + |E|)

Assumptions假设

This solution relies heavily on the fact that HashMap in Java does operations such as add/update in O(1) .该解决方案严重依赖于HashMap中的 HashMap 执行诸如添加/更新等操作的事实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.平均而言,这是正确的,但如果这还不够,同样的想法只能用 arrays 完全实现,这将在最坏的情况下将解决方案改进为O(|V|+|E|)

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.定义方法equalshashCode很重要,因此稍后在其 hash 计算将按预期工作。

  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:上面的output是:

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:例如 output 的:

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.我从 Python 中的链接问题中实现了我的答案。 The lines that don't reference minreach closely follow Wikipedia's description of Tarjan's SCC algorithm.未引用minreach的行紧跟 Wikipedia 对 Tarjan 的 SCC 算法的描述。

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)

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM