9

いくつかの背景

基本的に着信データを発信データにマップする関数の制御フロー グラフを分析しています。ほとんどのブロックは次のようなものです。

if (input_variable == SPECIFIC_CONSTANT) {
    output_variable = TRUE;
}
else {
    output_variable = FALSE;
}

このようなコードの典型的な制御フロー グラフは、次のグラフのようになります。

digraph G {
  2 -> 3 -> 5;
  2 -> 4 -> 5;
}

グラフェの写真

3ここで、 andの実行はbut4の値によって条件付けられ、独立しています。input_variable5

質問

有向グラフと開始ノードが与えられた場合、開始ノードからのパスがこのノードを通過するように最も近いノードを見つけるにはどうすればよいですか?

例:このグラフ6が与えられた場合、どのように開始2または12開始を見つけることができます8か?

Lowest Common Ancestor アルゴリズムを逆にすることはできますか?それは効率的ですか? お気に入り

for each node in graph:
    ancestors = node.get_all_ancestors()
    lca = find_lowest_common_ancestor(ancestors)
    junction_node[lca] = node

get_junction_point(node):
    return junction_node[node]

私のプログラミング言語は Python で、NetworkX を発見したばかりですが、どんなアルゴリズムでも構いません。私はグラフ理論に慣れていないので、探しているものを見つけるための基本的な用語集を見逃していると思います。

ご協力いただきありがとうございます!

4

3 に答える 3

2

最も効率的な解決策ではありませんが、次のことから始めてください。

DFS を実行してから、すべてのパス (すべてのパスに存在するノード) の交点を計算します。次に、それらのノードの中から、すべてのパスの先頭に最も近いノードを見つけます。

>>> paths
[]
>>> def dfs(G, s, path):
...     if s not in G or not G[s]:
...             paths.append(path)
...     else:
...             for t in G[s]:
...                     dfs({k:[_t for _t in v if _t!=t] for k,v in G.items()}, t, path+[t])
... 
>>> dfs(G, 2, [])
>>> paths
[[3, 4, 6, 7, 12], [3, 4, 6, 8, 9, 10, 12], [3, 4, 6, 8, 9, 12], [3, 4, 6, 8, 11, 12], [3, 5, 6, 7, 12], [3, 5, 6, 8, 9, 10, 12], [3, 5, 6, 8, 9, 12], [3, 5, 6, 8, 11, 12], [4, 6, 7, 12], [4, 6, 8, 9, 10, 12], [4, 6, 8, 9, 12], [4, 6, 8, 11, 12]]
>>> for path in paths: print(path)
... 
[3, 4, 6, 7, 12]
[3, 4, 6, 8, 9, 10, 12]
[3, 4, 6, 8, 9, 12]
[3, 4, 6, 8, 11, 12]
[3, 5, 6, 7, 12]
[3, 5, 6, 8, 9, 10, 12]
[3, 5, 6, 8, 9, 12]
[3, 5, 6, 8, 11, 12]
[4, 6, 7, 12]
[4, 6, 8, 9, 10, 12]
[4, 6, 8, 9, 12]
[4, 6, 8, 11, 12]
>>> nodes = [set(L) for L in paths]
>>> commons = functools.reduce(set.intersection, nodes)
>>> commons
{12, 6}
>>> min(commons, key=lambda v: max(L.index(v) for L in paths))
6

6ここで、いくつかのパスではインデックス 2 に、他のパスではインデックス 1 にどのように表示されるかに注意してください。ノード (たとえば x) があった場合、6 がインデックス 2 に表示されるパスのインデックス 1 に表示され、6 がインデックス 1 に表示されるインデックス 2 に表示された場合、それは引き分けになります。勝手に壊れます。必要に応じて、このケースをより適切に処理する方法を定義したい場合があります

于 2013-11-12T17:24:03.097 に答える
0

提案されたすべての解決策を読んで、私はアイデアを思いつきました。最初のノードに 1 の量を与えます。再帰的に、すべての子ノードはこの量の均等な割合を受け取ります。順番に、彼らはこの金額を下方に発送します。子供が合計 1 (開始額) を受け取った場合、それが「接合点」です。これが私の実装です(コメントを受け付けています!!)。

BFS コンストラクトがアクセスするノードの数を制限することを願っています。

class Node(object):
  """
    Object representing a node in a graph where we search the junction node that
    concentrates all paths starting from a start node.

    ``reference``: Attaches the real object that contains the relationships.
    ``initial_value``: Initial amount for the node. Typically 1 for the start
      node, 0 for the others.
    ``path``: Set of already traversed nodes that reached the node. Used to
      prune circular dependencies.
  """
  def __init__(self, reference, initial_value, path=set()):
    self.reference = reference
    # See dispatch() for explaination
    self.value_can_dispatch = self.value_has_received = initial_value
    self.path = path

  def receive(self, value):
    """
      Give ``value`` to the node. If the node received 1 (or more for security)
      in total, it will return True. Else it returns False.
    """
    self.value_has_received += value
    self.value_can_dispatch += value
    if self.value_has_received >= 1.:
      return True
    return False

  def dispatch(self, children):
    """
      Dispatch the value received to the children.
      Returns a filtered list of ``children`` where children involved in a
      circular dependency are removed.
      If one child signals that it has received a total of 1, the function will
      stop and return this one child.
    """
    # Filter successors that are in the path used to access this node so to cut
    # out cycles
    true_successors = [child for child in children if child not in self.path]
    # Cut the received value into equal pieces
    amount = self.value_can_dispatch/len(true_successors)
    # We transmit only the value received after the last time it was dispatched
    # because paths may lead to the same node at different iterations (path
    # through one node may be longer than through another) and thus the same
    # node can be asked to dispatch to its children more than once.
    # The total amount of received value is kept in value_has_received because
    # the node may receive the complete amount in several steps. Thus, part of
    # its value may have been dispatched before we notice that the node received
    # the total amount of 1.
    self.value_can_dispatch = Fraction(0)
    for child in true_successors:
      # If child signaled that he received 1, then raise the winner
      if child.receive(amount):
        return child
    return set(true_successors)

  def touch(self, other_node):
    """
      "Touches" a node with another, notifying that the node is reachable
      through another path than the known ones.
      It adds the elements of the new path as ancestors of the node.
    """
    self.path |= other_node.path | {other_node}

  def make_child(self, reference):
    """
      Creates a child of the node, pointing to reference. The child receives
      its path from the current node.
    """
    # This is were the algorithm can go mad. If child is accessed through two
    # paths, the algorithm will only protect recursion into the first
    # path. If the successors recurse into the second path, we will not detect
    # it. => We should update the child's path when a second path reaches it.
    return self.__class__(reference, Fraction(0), self.path | {self})

  def __repr__(self):
    return "<{} {}>".format(self.__class__.__name__, self.reference)

def find_junction_node(first_reference, get_uid, get_successors, max_iterations=100):
  """
    Find the junction node of all paths starting from ``first_reference`` in
    a directed graph. ``get_uid`` is a function accepting a reference to a node
    in your graph and returning a unique identifier for this reference.
    ``get_successors`` is a function accepting a reference to a node in your
    graph. It should return a list of references to all its the children nodes.
    It may return None if the node has no child.
    ``max_iterations`` limits the number of pass the algorithm use to find the
    junction node. If reached, the funciton raises a RuntimeError.

    Returns ``(jp, ln)`` where ``jp`` is a reference to a node in your graph
    which is the junction node and ``ln`` the list of nodes in the subgraph
    between the start node and the junction node.
  """
  # Mapping to already created nodes
  nodes = {}
  # Initialise first node with an amount of 1
  node = Node(first_reference, Fraction(1, 1))
  nodes[get_uid(first_reference)] = node
  # Initialise first iteration of DFS
  successors = set()
  successors.add(node)
  # Max iteration provides security as I'm not sure the algorithm cannot loop
  for i in range(max_iterations):
    next_successors = set()
    # Process one level of nodes
    for node in successors:
      # Find successors in data graph
      sub_references = get_successors(node.reference)
      # This happens when we reach the end of the graph, node has no children
      if sub_references is None:
        continue
      # Make a list of Node that are children of node
      children = set()
      for reference in sub_references:
        uid = get_uid(reference)
        # Does it exist?
        child = nodes.get(uid, None)
        if not child:
          child = node.make_child(reference)
          nodes[uid] = child
        else:
          child.touch(node)
        children.add(child)
      # Dispatch the value of node equally between its children
      result = node.dispatch(children)
      #print("Children of {}: {!r}".format(node, result)) # DEBUG
      # If one child received a total of 1 from its parents, it is common to
      # all paths
      if isinstance(result, Node):
        return result.reference,  [node.reference for node in result.path]
      # Else, add the filtered list of children to the set of node to process
      # in the next level
      else:
        next_successors |= result
    successors = next_successors
    # Reached end of graph by all paths without finding a junction point
    if len(successors) == 0:
      return None
  raise RuntimeError("Max iteration reached")
于 2013-11-12T18:46:14.613 に答える