有向无环图的哈希值

27

如何将有向无环图转换为哈希值,使得任意两个同构图哈希到相同的值?在下面的代码中,默认情况下两个同构图会哈希到不同的值,这是可以接受但不理想的。我们可以假设图中顶点的数量最多为11。

我特别关注Python代码。

这是我做的。如果self.lt是从节点到后代(而不是子节点)的映射,则我根据修改过的拓扑排序对节点进行重新标记(如果可能,则更喜欢首先按照具有更多后代元素的顺序排序)。然后,我对排序后的字典进行哈希处理。一些同构图将哈希到不同的值,特别是当节点数量增加时。

我已经包含了所有的代码来说明我的使用案例。我正在计算找到7个数字的中位数所需的比较次数。同构图哈希到相同值的越多,就需要重新执行的工作就越少。我考虑将更大的连通组件放在前面,但没想到如何快速实现。

from tools.decorator import memoized  # A standard memoization decorator


class Graph:
    def __init__(self, n):
        self.lt = {i: set() for i in range(n)}

    def compared(self, i, j):
        return j in self.lt[i] or i in self.lt[j]

    def withedge(self, i, j):
        retval = Graph(len(self.lt))
        implied_lt = self.lt[j] | set([j])
        for (s, lt_s), (k, lt_k) in zip(self.lt.items(),
                                        retval.lt.items()):
            lt_k |= lt_s
            if i in lt_k or k == i:
                lt_k |= implied_lt
        return retval.toposort()

    def toposort(self):
        mapping = {}
        while len(mapping) < len(self.lt):
            for i, lt_i in self.lt.items():
                if i in mapping:
                    continue
                if any(i in lt_j or len(lt_i) < len(lt_j)
                       for j, lt_j in self.lt.items()
                       if j not in mapping):
                    continue
                mapping[i] = len(mapping)
        retval = Graph(0)
        for i, lt_i in self.lt.items():
            retval.lt[mapping[i]] = {mapping[j]
                                     for j in lt_i}
        return retval

    def median_known(self):
        n = len(self.lt)
        for i, lt_i in self.lt.items():
            if len(lt_i) != n // 2:
                continue
            if sum(1
                   for j, lt_j in self.lt.items()
                   if i in lt_j) == n // 2:
                return True
        return False

    def __repr__(self):
        return("[{}]".format(", ".join("{}: {{{}}}".format(
            i,
            ", ".join(str(x) for x in lt_i))
                                       for i, lt_i in self.lt.items())))

    def hashkey(self):
        return tuple(sorted({k: tuple(sorted(v))
                             for k, v in self.lt.items()}.items()))

    def __hash__(self):
        return hash(self.hashkey())

    def __eq__(self, other):
        return self.hashkey() == other.hashkey()


@memoized
def mincomps(g):
    print("Calculating:", g)
    if g.median_known():
        return 0
    nodes = g.lt.keys()
    return 1 + min(max(mincomps(g.withedge(i, j)),
                       mincomps(g.withedge(j, i)))
                   for i in nodes
                   for j in nodes
                   if j > i and not g.compared(i, j))


g = Graph(7)
print(mincomps(g))

3
搜索“Hash value for a graph”将我带到了Ashish Kundu和Elisa Bertino写的一篇名为“On Hashing Graph” 的有趣论文,介绍了一种对有向无环图进行哈希的解决方案(使用两个O(1)操作)。我不能用简短的答案总结这篇论文,但我很享受阅读它 :) - Jason Sperske
1
还有一种叫做“Merkle签名方案”的东西,Kundu&Bertino论文中提到它作为一个起始解决方案,如果这有所帮助。 - Jason Sperske
5
顶点或边缘上有标签吗?如果没有,同构图是否应哈希为相同的值? - Henry
9
哈希值需要是唯一的,还是通常上唯一即可?(对于Python对象哈希函数而言,仅需要后者。) - user149341
2
@NeilG,你是在暗示你正在寻找一种有效检测两个图是否同构(GI)的算法吗?你知道目前还不确定GI是否属于PNP(假设NP != P),对吧?我不知道有什么比nauty(http://cs.anu.edu.au/~bdm/nauty/)更好的正确方法。我记得几年前有人证明GI属于`P`(作者还包括了一个`O(n^5)`的算法),但这个证明是有缺陷的,我不确定它是否最终被发表了。 - mmgp
显示剩余13条评论
10个回答

12

为了有效地测试图同构,您需要使用nauty。对于Python语言,有一个名为pynauty的包装器,但我无法保证其质量(为了正确编译它,我不得不对其setup.py进行一些简单的修补)。如果这个包装器正确地完成了所有操作,那么它将大大简化您感兴趣的用途的nauty,并且只需哈希pynauty.certificate(somegraph)即可--对于同构图,此哈希值将是相同的。

一些快速测试表明,pynauty对于每个图形(具有相同数量的顶点)都提供相同的证书。但这只是由于在将图形转换为nauty格式时,包装器存在一些小问题。经过修复后,它对我有用(我还使用了http://funkybee.narod.ru/graphs.htm上的图形进行比较)。下面是一个短补丁,其中还考虑到了setup.py中需要的修改:

diff -ur pynauty-0.5-orig/setup.py pynauty-0.5/setup.py
--- pynauty-0.5-orig/setup.py   2011-06-18 20:53:17.000000000 -0300
+++ pynauty-0.5/setup.py        2013-01-28 22:09:07.000000000 -0200
@@ -31,7 +31,9 @@

 ext_pynauty = Extension(
         name = MODULE + '._pynauty',
-        sources = [ pynauty_dir + '/' + 'pynauty.c', ],
+        sources = [ pynauty_dir + '/' + 'pynauty.c',
+            os.path.join(nauty_dir, 'schreier.c'),
+            os.path.join(nauty_dir, 'naurng.c')],
         depends = [ pynauty_dir + '/' + 'pynauty.h', ],
         extra_compile_args = [ '-O4' ],
         extra_objects = [ nauty_dir + '/' + 'nauty.o',
diff -ur pynauty-0.5-orig/src/pynauty.c pynauty-0.5/src/pynauty.c
--- pynauty-0.5-orig/src/pynauty.c      2011-03-03 23:34:15.000000000 -0300
+++ pynauty-0.5/src/pynauty.c   2013-01-29 00:38:36.000000000 -0200
@@ -320,7 +320,7 @@
     PyObject *adjlist;
     PyObject *p;

-    int i,j;
+    Py_ssize_t i, j;
     int adjlist_length;
     int x, y;

1
我想我有选择赏金获胜者的责任,所以如果这个答案对@NeilG有效,那么它对我也有效,然而对于这个问题的整体回应对我来说是一个很好的学习经验,因为我正在涉足更高级的计算机科学课题(最近一直在研究图形)。我觉得SO可以像一个临时课堂讨论任何话题,而所有的代价只是花费了我一周时间去回答其他问题,我喜欢这个网站 :) - Jason Sperske

8

有向无环图同构问题仍然是NP完全问题。 因此目前没有已知的(最坏情况下亚指数级别的)解决方案可以保证两个同构的有向无环图会产生相同的哈希值。只有在已知不同图之间的映射时,例如所有顶点具有唯一标签的情况下,才能有效地保证匹配哈希。

好的,让我们针对少量顶点进行暴力计算。我们必须找到一个表示图形的表示方式,该表示与输入中顶点的顺序无关,并且因此保证同构图形产生相同的表示。此外,此表示必须确保没有两个非同构图形产生相同的表示。

最简单的解决方案是构建所有n!个顶点排列的邻接矩阵,将邻接矩阵解释为n2位整数。然后我们可以选择这些数字中最小或最大的作为规范表示。该数字完全编码了图形,因此确保没有两个非同构图形产生相同的数字 - 可以将此函数视为完美哈希函数。因为我们在所有可能的顶点排列下选择编码图形的最小或最大数字,所以进一步确保同构图形产生相同的表示。
在11个顶点的情况下,这种方法好坏如何?表示将具有121位。我们可以减少11位,因为在无环图中表示环将全部为零,并且剩下110位。理论上,可以进一步减少此数字;并非所有2110个剩余图形都是无环图形,并且对于每个图形,可能有多达11!-大约225 - 同构表示,但在实践中,这可能很难做到。有人知道如何计算具有n个顶点的不同有向无环图的数量吗?
需要多长时间才能找到这个表示法?天真地说,需要11!或39,916,800次迭代。这已经很不可行了,但我没有实现和测试它。但我们可能可以加快一些速度。如果我们将邻接矩阵解释为整数,通过从上到下、从左到右连接行,我们希望在第一行的左边有许多1(0)以获得一个大(小)的数字。因此,我们选择具有最大(最小)度数(入度或出度取决于表示)的一个顶点作为第一个顶点,然后选择与该顶点相连(不相连)的顶点作为后续位置,以将1(0)移到左侧。
可能还有更多剪枝搜索空间的可能性,但我不确定是否足够使其成为实际解决方案。也许有,或者其他人至少可以在这个想法上构建一些东西。

谢谢,这是一个非常有用的答案。然而,如果顶点数最多只有11个,指数时间对我的目的来说是可以接受的 :) - Neil G
2
“hash(input) = 1” 显然存在多个哈希冲突,但它是次指数级的,并且任何两个同构的有向无环图将产生相同的哈希。 - example
2
由于有向无环图可以进行拓扑排序,因此您可以假设顶点已经按拓扑排序,并且邻接矩阵在对角线以下只有零。对于您迄今为止的工作给予加一分。 - Neil G
执行拓扑排序涉及对顶点进行排序以最大化(最小化)表示。也许可以尝试仅考虑拓扑排序来找到最大(最小)表示,但我无法确定这个约束条件是否会使问题更容易或更难。 - Daniel Brückner
由于您可以将可用位数减少到55,因此这更容易。 - Neil G
这是正确的,但您不再能够交换任意顶点,因为这可能会破坏拓扑排序。我倾向于认为这不是一个大问题,并且远远被减少的搜索空间所超越,但我真的不确定。 - Daniel Brückner

3
哈希需要有多好?我假设你不想要一个完整的图形序列化。哈希很少保证没有第二个(但是不同的)元素(图形)会评估为相同的哈希。如果对您非常重要,同构图(在不同的表示中)具有相同的哈希,则仅使用变换下不变的值。例如:
- 节点总数 - (有向)连接总数 - 具有(入度,出度)=(i,j)的节点总数,对于任何元组(i,j),最大为(max(入度),max(出度))(或限制为元组最大值(m,n))。
所有这些信息可以在O(#节点)[假设图形已正确存储]中收集。将它们串联起来,您就有了一个哈希。如果您喜欢,可以在这些串联信息上使用一些知名的哈希算法,例如sha。如果选择的哈希算法具有这些属性,则不需要额外的哈希,它是一个连续哈希(允许找到类似的图形),并且在大小上是固定的。
作为现有的功能,它已经足够好了,可以注册任何添加或删除的连接。但是,它可能会错过一些已更改的连接(例如a -> c而不是a -> b)。
这种方法是模块化的,可以根据需要进行扩展。包含的任何额外属性都会减少碰撞次数,但会增加获取哈希值所需的工作量。一些更多的想法:
  • 与上述相同,但具有二阶入度和出度。即可以通过 node->child->child 链路到达的节点数量(= 二阶出度),或者分别是两步到达给定节点的节点数。
  • 或更一般的 n 阶入度和出度(可以在 O((平均连接数) ^ (n-1) * #节点) 内计算)
  • eccentricity = x 的节点数(同样适用于任何 x)
  • 如果节点存储任何信息(除了其邻居),则使用所有节点内容的任何哈希的 xor。由于 xor,添加节点到哈希中的特定顺序无关紧要。

您请求“唯一哈希值”,显然我无法提供。但我认为术语“哈希”和“每个图形都是唯一的”是相互排斥的(当然不完全正确),并决定回答“哈希”部分而不是“唯一”部分。一个“唯一哈希”(perfect hash)基本上需要是图的完整序列化(因为哈希中存储的信息量必须反映图中的总信息量)。如果这确实是您想要的,只需定义一些节点的唯一顺序(例如,按自己的出度排序,然后入度,然后子节点的出度等,直到顺序是明确的),并以任何方式序列化图形(使用上述排序中的位置作为节点的索引)。当然,这要复杂得多。

我之前的问题表述不够清晰,我已经尝试重新表述以消除任何困惑。请告诉我现在是否清楚了... - Neil G
@NeilG:“对于两个同构图哈希到不同值是可以接受但不理想的” - 但是,对于两个不同的图具有相同的哈希值是否完全可接受? - example
@NeilG 好的,那我就坚持我的答案 =) - example

3
多年前,我创建了一个简单而灵活的算法来解决这个问题(通过哈希在化学结构数据库中查找重复结构)。我将其命名为“Powerhash”,为了创建该算法,需要两个见解。第一个是幂迭代图算法,也用于PageRank。第二个是能够用任何我们想要的东西替换幂迭代内部步骤函数的能力。我用以下函数替换了它,在每个步骤和每个节点上执行以下操作:
- 对节点邻居的哈希进行排序 - 哈希连接排序后的哈希值
在第一步中,节点的哈希受其直接邻居的影响。在第二步中,节点的哈希受到其邻域2跳之外的影响。在第N步中,节点的哈希将受到其周围N跳的影响。因此,您只需要继续运行 Powerhash N = graph_radius 步。最后,图形中心节点的哈希将受到整个图形的影响。
要生成最终哈希,请对最终步骤的节点哈希进行排序并将它们连接在一起。之后,您可以比较最终哈希以查找两个图是否同构。如果有标签,则将它们添加到您为每个节点(以及每个步骤)计算的内部哈希中。

更多相关内容,请查看我在这里发布的文章:

https://plus.google.com/114866592715069940152/posts/fmBFhjhQcZF

上述算法已在“madIS”功能关系数据库中实现。您可以在此处找到算法的源代码:

https://github.com/madgik/madis/blob/master/src/functions/aggregate/graph.py


1
我将描述一种算法,用于哈希任意有向图,不考虑图是否无环。实际上,即使计算给定阶数的无环图也是非常复杂的任务,我认为这只会使哈希变得更加复杂,从而降低速度。
可以通过邻域列表来给出图的唯一表示。对于每个顶点,创建一个包含所有邻居的列表。将所有列表依次写在一起,将每个列表的邻居数附加到前面。此外,保持邻居按升序排序,以使每个图的表示唯一。因此,例如假设您有以下图形:
1->2, 1->5
2->1, 2->4
3->4
5->3

我提议的是你将其转换为({2,2,5}, {2,1,4}, {1,4}, {0}, {1,3}),这里的花括号仅用于可视化表示,不是python语法的一部分。因此,列表实际上是(2,2,5, 2,1,4, 1,4, 0, 1,3)
现在要计算唯一哈希值,您需要以某种方式对这些表示进行排序并分配唯一编号。我建议您使用字典序排序。假设您有两个序列(a1, b1_1, b_1_2,...b_1_a1,a2, b_2_1, b_2_2,...b_2_a2,...an, b_n_1, b_n_2,...b_n_an)(c1, d1_1, d_1_2,...d_1_c1,c2, d_2_1, d_2_2,...d_2_c2,...cn, d_n_1, d_n_2,...d_n_cn),其中c和a是每个顶点的邻居数,b_i_j和d_k_l是相应的邻居。对于排序,首先比较序列(a1,a2,...an)(c1,c2, ...,cn),如果它们不同,则使用此来比较序列。如果这些序列不同,请从左到右比较列表,首先将(b_1_1, b_1_2...b_1_a1)(d_1_1, d_1_2...d_1_c1)进行字典序比较,依此类推,直到第一个不匹配为止。
实际上,我建议使用大小为N的单词的词汇顺序号作为哈希值,该单词由选择{1,2,3,...N}元素子集的所有可能形成的字母表组成。给定顶点的邻居列表是一个在此字母表上的字母,例如{2,2,5}是由集合中两个元素25组成的子集。

对于集合{1,2,3},其可能的字母(即可能的字母表)如下(按字典顺序排序):

{0}, {1,1}, {1,2}, {1,3}, {2, 1, 2}, {2, 1, 3}, {2, 2, 3}, {3, 1, 2, 3}

像上面一样,第一个数字是给定子集中元素的数量,剩余的数字是子集本身。因此,从这个字母表中形成所有的三个字母单词,你将得到所有可能的具有3个顶点的有向图。

现在,集合{1,2,3,....N}的子集数量为2^N,因此这个字母表的字母数为2^N。现在,我们用来自该字母表恰好N字母单词对每个有向图进行编码,因此可能的哈希代码数量精确地为:(2^N)^N。这是为了说明哈希码随着N的增加而呈指数级增长。此外,这也是具有N个节点的可能不同有向图的数量,因此我建议采用双射的最佳哈希方式,没有更小的哈希可以是唯一的。

有一个线性算法可以得到给定集合的所有子集在字典序中的排序,例如 {1,2,....N}。这里是我编写的用于编码/解码子集的代码,可以互相转换。它是用 C++ 编写的,但我希望它很容易理解。对于哈希,你只需要使用代码函数,但由于我提出的哈希是可逆的,所以我添加了解码函数 - 你将能够从哈希中重构图形,这非常酷。

typedef long long ll;

// Returns the number in the lexicographical order of all combinations of n numbers
// of the provided combination. 
ll code(vector<int> a,int n)
{
    sort(a.begin(),a.end());  // not needed if the set you pass is already sorted.
    int cur = 0;
    int m = a.size();

    ll res =0;
    for(int i=0;i<a.size();i++)
    {
        if(a[i] == cur+1)
        {
            res++;
            cur = a[i];
            continue;
        }
        else
        {
            res++;
            int number_of_greater_nums = n - a[i];
            for(int j = a[i]-1,increment=1;j>cur;j--,increment++)
                res += 1LL << (number_of_greater_nums+increment);
            cur = a[i];
        }
    }
    return res;
}
// Takes the lexicographical code of a combination of n numbers and returns the 
// combination
vector<int> decode(ll kod, int n)
{
    vector<int> res;
    int cur = 0;

    int left = n; // Out of how many numbers are we left to choose.
    while(kod)
    {
        ll all = 1LL << left;// how many are the total combinations
        for(int i=n;i>=0;i--)
        {
            if(all - (1LL << (n-i+1)) +1 <= kod)
            {
                res.push_back(i);
                left = n-i;
                kod -= all - (1LL << (n-i+1)) +1;
                break;
            }
        }
    }
    return res;
}

此代码将结果存储在long long变量中,只适用于少于64个元素的图形。具有64个节点的所有可能散列的图形将是(2^64)^64。这个数字大约有1280个数字,所以可能是一个很大的数字。尽管如此,我描述的算法将非常快速,并且我相信您应该能够对具有许多顶点的图进行哈希和反哈希。

还请查看此问题


两个同构图将具有不同的哈希值。只要至少一个节点的邻域列表不同,哈希就会不同。 - Ivaylo Strandjev
这不就是对邻接字典进行哈希处理吗? - Neil G
实际上是一个列表,但是是的。因此,顶点按其编号进行编号和排序,并且每个顶点的列表按顺序打印。 - Ivaylo Strandjev
好的。看一下我对问题的修改。我本可以省略拓扑排序并哈希邻接字典。但问题在于我正在使用DAG作为动态规划的关键。同构图哈希到相同值越多,就会减少重复工作。 - Neil G

1
我不确定它是否百分之百有效,但这是一个想法:
将图形编码为字符串,然后对其进行哈希。
空图的哈希值为"";没有出边的顶点的哈希值为".";有出边的顶点的哈希值是每个子哈希与某个分隔符(例如“,”)的连接。
在第3步连接之前,为了产生同构图的相同哈希值,只需对哈希进行排序(例如按字典顺序)。
对于图的哈希值,只需取其根节点的哈希值(如果有几个根,则按排序后的顺序连接)。
编辑:虽然我希望生成的字符串描述图形时不会发生冲突,但hynekcer发现有时非同构的图形会获得相同的哈希值。当一个顶点有多个父节点时,就会发生这种情况-然后它会被“复制”到每个父节点中。例如,该算法不能区分“钻石”{A->B->C,A->D->C}和{A->B->C,A->D->E}的情况。

我不熟悉Python,很难理解示例中图形的存储方式,但这里有一些C++代码,很可能可以轻松转换为Python:

THash GetHash(const TGraph &graph)
{
    return ComputeHash(GetVertexStringCode(graph,FindRoot(graph)));
}
std::string GetVertexStringCode(const TGraph &graph,TVertexIndex vertex)
{
    std::vector<std::string> childHashes;
    for(auto c:graph.GetChildren(vertex))
        childHashes.push_back(GetVertexStringCode(graph,*c));
    std::sort(childHashes.begin(),childHashes.end());
    std::string result=".";
    for(auto h:childHashes)
        result+=*h+",";
    return result;
}

有趣。你可以尝试更改哈希函数,看看它是否能击败我的解决方案,然后看看你的版本打印了多少行。 - Neil G
很遗憾,我不够熟练的Python语言来插入相关代码到你的解决方案中。我能做的最好的是提供一个说明性的C++代码。 - maxim1000
1
很抱歉,这对于非树形图形无法正常工作,例如“钻石”(A->B->D,A->C->D)将与树(A->B->D,A->C->E)具有相同的哈希值。您还可以拥有反向树,其中每个顶点可以有更多的父节点但只有一个子节点,或者混合使用。 图像http://en.wikipedia.org/wiki/Directed_acyclic_graph - hynekcer
@hynekcer,谢谢。确实这是哈希冲突(比MD5更有可能:))。我已经在我的答案中添加了一条注释,不幸的是我没有看到快速解决问题的方法(而不会使算法变得太复杂)。 - maxim1000
我不能测试你的算法。你更了解它,可以确认或拒绝我猜测的问题。我不是指MD5,SHA之类的碰撞,而是它们接受相同的输入。良好哈希算法的冲突是可以接受的,因为目标的每一个小变化都会修改其哈希值,找到冲突不应该是微不足道的事情。将两个分支合并成一个是一个小变化。 - hynekcer
@hynekcer,我在整个哈希过程中使用了“碰撞”这个术语,你是对的——它们不仅更有可能发生,而且对于小的更改也会发生,因此这个哈希的性质比例如MD5要差得多。 - maxim1000

1
当我看到这个问题时,我的想法与@example基本相同。我编写了一个提供图形标签的函数,使得两个同构图的标签一致。
该标签由升序排列的出度序列组成。您可以使用您选择的字符串哈希函数对此标签进行哈希,以获得图形的哈希值。
编辑:我在@NeilG的原始问题的背景下表达了我的建议。唯一需要修改他的代码的地方是重新定义hashkey函数为:
def hashkey(self): 
    return tuple(sorted(map(len,self.lt.values())))

不错,你试过用我的代码来测试它是否表现更好吗? - Neil G
老实说,我不太理解你的代码。你能试试这个函数,或者多注释一下你的代码吗?谢谢。 - naitoon
1
self.ltGraph 中从节点到后代的映射。顺便说一下,你的组件计数有问题,因为一个单独的组件可以有许多没有入边的节点。 - Neil G
@NeilG 我已经将这个哈希策略包含在你的代码中,并进行了时间测试,但我没有看到任何改进(我看到的执行时间几乎相同,只有不到一分钟)。你确定在执行过程中有很多同构图吗?顺便说一下,我所做的唯一更改是 def hashkey(self): return tuple(sorted(map(len,self.lt.values()))) - naitoon
“hashkey” 实际上为每个同构图族返回一个独特的“证书”。你有没有数一下打印出来的行数是否不同?行数测量了考虑的图形数量。 - Neil G
显示剩余3条评论

1

在我看来,如果能够对图进行拓扑排序,就存在非常直接的解决方案。

  1. 对于每个索引为i的顶点,你可以构建一个唯一的哈希值(例如,使用字符串哈希技术),表示其(已排序的)直接邻居(例如,如果顶点1有直接邻居{43,23,2,7,12,19,334},则哈希函数应该将数组{2,7,12,19,23,43,334}哈希化)
  2. 对于整个DAG,你可以创建一个哈希,作为每个节点哈希字符串的哈希:Hash(DAG) = Hash(vertex_1) U Hash(vertex_2) U ..... Hash(vertex_N);我认为该过程的复杂度在最坏情况下为(N*N)。如果无法对图进行拓扑排序,则仍然可以采用所提出的方法,但需要以唯一的方式对顶点进行排序(这是困难的部分)

2
这就是我所做的,但拓扑排序并不唯一。例如,如果有两个不相连的组件,它们的节点可以以任何方式交错。 - Neil G

1
我假设顶点或边上没有共同的标签,否则您可以将图形放在规范形式中,这本身就是完美的哈希。因此,这个提议仅基于同构。
为此,请结合尽可能多的DAG简单聚合特征的哈希,选择那些计算速度快的特征。以下是一个入门列表:
1.节点的2d直方图入度和出度。 2.边缘的4d直方图a->b,其中a和b都由入度/出度表征。

加法 让我更明确一些。对于1,我们会计算一组三元组<I,O;N>(其中没有两个三元组具有相同的IO值),表示有N个节点的入度为I,出度为O。您可以哈希这组三元组或者更好的方式是使用整个集合按某种规范顺序排列,例如按字典顺序排序。对于2,我们计算一组五元组<aI,aO,bI,bO;N>,表示有N条边从入度为aI,出度为aO的节点到入度为bI和出度为bO的节点。同样地,哈希这些五元组,或者将它们按规范顺序直接用于最终哈希的另一部分。

从这里开始,然后查看仍然发生的冲突可能会提供关于如何改进的见解。


0

通过适当地对您的后代进行排序(如果您有单个根节点,则不是必须的,但可以通过包含虚拟根节点来实现适当的排序),哈希树的方法应该可以通过轻微修改来实现。

这个StackOverflow答案中的示例代码中,修改将是在哈希父节点之前以某种确定性顺序(递增哈希?)对子节点进行排序。

即使您有多个可能的根,也可以创建一个合成的单个根,其中所有根都是子节点。


1
正如你所暗示的,链接问题中的算法需要一个唯一可识别的根节点。否则它可能会以不同方式哈希同构图形。 - alexis
1
@alexis 如果您没有唯一可识别的根节点,您可以合成一个,将所有根节点作为子节点。 - Vatine

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接