修剪杂散节点的大图

Cha*_*les 5 language-agnostic algorithm graph-theory graphviz

我有一个图表,包含大约35,000个以纯文本表示的节点:

node1 -> node35000
node29420 -> node35000
node2334 -> node4116
...
Run Code Online (Sandbox Code Playgroud)

我想通过删除不属于链的节点至少三个长来修剪它.所以,如果我只有

1 -> 2;
2 -> 3;
3 -> 4;
0 -> 4;
Run Code Online (Sandbox Code Playgroud)

我想保留1,2,3和4(因为1 -> 2 -> 3 -> 4四个节点长)但丢弃0,即删除0 -> 4.

有没有想过这样做的好方法?我尝试了Perl和shell函数的组合,但我认为我需要一个更好的方法.除非有工具可以做到这一点?数据采用graphviz格式,但我没有看到该套件中的任何工具与手头的任务相关.

哦,如果有一种简单的方法可以做这样的事情,我会接受建议 - 它不一定是我建议的任务.我只是想找到一种方法来消除大块周围的大部分噪音(这种情况很少见,而且大部分只是一些相交的链条).

mar*_*pet 5

作为graphviz工具一部分的工具gvpr允许将规则应用于图形并输出修改后的图形.

从描述:

它将输入图复制到其输出,可能转换其结构和属性,创建新图,...

看起来您想要删除所有具有0的indegree并且仅具有outdegree为0的链接节点(后继者)的节点.

这是我的gvpr脚本版本nostraynodes.gv:

BEGIN {node_t n; int candidates[]; int keepers[];}
E{
  if (tail.indegree == 0 && head.outdegree == 0)
  {
    candidates[tail] = 1;
    candidates[head] = 1;
  }
  else if (tail.indegree == 0)
  {
    keepers[tail] = 1;
  }
  else if (head.outdegree == 0)
  {
    keepers[head] = 1;
  }
}

END_G {
  for (candidates[n]){
    if (n in keepers == 0)
    {
       delete(NULL, n);
    }
  }
}
Run Code Online (Sandbox Code Playgroud)

这是脚本的作用:

  1. 遍历所有边一个时间和填充两个列表:

    • 候选人 - 可能必须删除的节点列表,以及
    • keepers - 节点列表,可能最终出现在候选者中但不应被删除.

    那么什么被添加到哪个列表?

    • 任何两个节点彼此连接,其中尾节点没有任何进入边缘,并且头节点没有任何输出边缘,形成仅2个节点的链,因此是要删除的候选节点; 也就是说,除非相同的节点是长于2个节点的另一个链的一部分:
    • 没有任何入射边缘但连接到自身具有输出边缘的头节点的尾节点是保持器 ; 和
    • 没有任何输出边缘但连接到尾节点的头节点也是一个保持器,该节点本身具有输入边缘.
  2. 删除所有不在饲养员中的候选人

此解决方案不是通用的,仅适用于问题中所述的问题,即仅保留链长度至少为3个节点.它也不会删除短循环(两个节点相互连接).

您可以使用以下行调用它:

gvpr -c -f .\nostraynodes.gv .\graph.dot
Run Code Online (Sandbox Code Playgroud)

使用示例图表的输出是:

digraph g {
    1 -> 2;
    2 -> 3;
    3 -> 4;
}
Run Code Online (Sandbox Code Playgroud)

请注意,这是我的第一个gvpr脚本 - 可能有更好的方法来编写它,我不知道它如何处理35000个节点,但我相信这不应该是一个大问题.


另请参见Graphviz/Dot - 如何用独特的颜色标记树中的所有叶子?有关图变换的简单示例.


Jam*_*at7 2

假设任何给定节点可以有任意多个前驱或后继,则节点的入度和出度与解决问题无关。

以下是在路径长度 3 准则下针对 N 个节点和 E 个边的所有图的简单 O(N+E) 算法。该算法可以很容易地用 Perl 或 C 实现。该方法基于定义和断言:将“生成节点”定义为具有父节点和子节点(前驱节点和后继节点)的任何节点。将保留的每个节点都是已创建节点,或者是已创建节点的父节点或子节点。

  1. 将状态数组 S[Nmax] 初始化为全零。Nmax 是最大节点数。如果一开始不知道 Nmax,请读取所有数据并找出答案。

  2. 读入给定的边列表。每个输入项指定从节点 p 到节点 q 的有向边 (p, q)。对于读入的每个 (p, q) 项:将 S[p] 设置为 S[p] | 1 表示 p 有一个孩子,并将 S[q] 设置为 S[q] | 2 表示 q 有一个父对象。(在这一步之后,每个生成的节点 n 都有 S[n] == 3。)

  3. 再次阅读边列表。对于读入的每个 (p, q) 项: 如果 (S[p]==3) 或 (S[q] == 3) 输出边 (p,q)。

要将此方法扩展到除 3 之外的路径长度 K,请将边列表保留在内存中,使用父链和子链的长度维护 Sp[] 和 Sc[],并执行 K/2 次额外传递。也许可以在 O(N+K*E) 时间内完成。该问题没有指定图是否是 DAG(有向无环图),但给出的示例是 DAG。当 K>3 时,可能会有所不同。

更新 1这是 K>3 算法的更精确表述,其中 H[i].p 和 H[i].q 是边 #i 的端点,pc[j]、cc[j] 是前趋的长度,关于节点 j 的后继链。另外,令 E = 边数;N = 节点数;K = 保持边缘所需的最小链条长度。

  1. 将 E 边沿数据条目读入 H[ ] 数组。将所有 pc[j]、cc[j] 条目设置为 0。

  2. 对于 i = 1 到 E,设置 cc[H[i].p]=1 和 pc[H[i].q]=1。

  3. 对于 j = 1 到 K+1,{ 对于 i = 1 到 E,{ 令 p=H[i].p 且 q=H[i].q。设置 cc[p] = max(cc[p], 1+cc[q]) 和 pc[q] = max(pc[q], 1+pc[p])。} }

  4. 对于 i = 1 到 E,{令 p=H[i].p 且 q=H[i].q。如果 pc[p]+cc[p]+1 >= K 且 pc[q]+cc[q]+1 >= K,则输出边缘 (p,q)。}

如果图不是 DAG 并且包含短循环路径,则此方法可能会出错。例如,如果图边包括 (1,2) 和 (2,1),并且没有其他节点链接到节点 1 或 2,则不应输出这些边;但我们最终得到这些节点的 cc[] 和 pc[] 的 K+2,因此它们无论如何都会得到输出。