有没有办法对集合类型进行概率恒定时间相等性检查?

时间:2012-03-26 18:03:16

标签: performance algorithm

问题

我想知道如何有效地比较两种集合类型(列表,集合,地图等)。应该指出的是,结构平等是期望的,而不是基于参考的平等。

通常必须遍历集合的所有元素并在它们之间进行比较,每次比较的成本为O(1),从而产生惊人的O(n)比较时间。

这会影响使用列表的哈希表,其中冲突检查相当昂贵或使用契约设计(例如,比较旧集合和新集合)。

当前解决方案

我有很多方法可以确定快速解决方案,但它们看起来都是行为主义/非确定性的。如果能够使用可存储和比较的所有元素的某种独特散列,则可以使用这些想法。一个好的散列算法应该提供足够的enthropy,以便碰撞的可能性很小。

这种基于散列的比较技术可以通过使用列表头的一些恒定时间比较来加强(比如前10个元素)。在开始时使用相同元素并使用良好散列算法的两个列表在理论上应该提供一些独特的比较。

问题

是否有可能创建一种常数时间比较(在某些时候像整数一样通用和专用),是否可以通过唯一哈希技术实现?

更新

为了澄清这个问题,我不需要一个完美的平等检查,而是一个快速的“平等前”检查,作为一种加速真正的平等检查的方法。虽然许多哈希代码实现对于集合比较很有用,但我也对列表(有序)比较感兴趣。

8 个答案:

答案 0 :(得分:2)

使用基于哈希的比较。

Hash(SetA)vs Hash(SetB)。

PS:在计算哈希值之前,您需要对集合中的元素进行排序(或任何其他确定性排序)。散列可能匹配,但集合可能不匹配(由于散列冲突),但发生这种情况的可能性非常低。

PS:PS:我假设这些集合是静态的(或几乎是静态的)。在这种情况下,您可以在创建集合本身期间预先计算哈希值。因此每次比较都是O(1)。否则,正如Groo所提到的,使用非常有效的基于XOR的散列。

跟进:使用信息理论可以证明,如果X和Y各自可以采用2 ^ n个唯一值,则需要进行至少O(n)次比较。没有绕过那个。哈希给你的是有效比较的能力。

答案 1 :(得分:2)

我花了几分钟时间在C#中编写了这样一个集合类,源代码如下。我使用了泛型System.Collections.ObjectModel.Collection<T>,因为它很容易覆盖它的功能。

根本没有测试过,但它应该是一个坚实的开始恕我直言。请注意,UpdateHash会考虑索引(使哈希函数稍好一些),而模拟HashedSet<T>会跳过此部分。

此外,由于XOR运算符的可逆性,重新计算添加/删除时的哈希需要O(1)复杂度。如果需要更好的哈希值,这些操作将增长到O(n),因此我建议进行性能分析,然后确定最佳值。

public class HashedList<T> : Collection<T>, IEquatable<HashedList<T>>
{
    private int _hash;
    private void UpdateHash(int index, T item)
    {
        _hash ^= index;
        if (item != null)
            _hash ^= item.GetHashCode();
    }

    #region Overriden collection methods

    protected override void InsertItem(int index, T item)
    {
        UpdateHash(index, item);
        base.InsertItem(index, item);
    }

    protected override void RemoveItem(int index)
    {
        UpdateHash(index, this[index]);
        base.RemoveItem(index);
    }

    protected override void ClearItems()
    {
        _hash = 0;
        base.ClearItems();
    }

    protected override void SetItem(int index, T item)
    {
        UpdateHash(index, this[index]);
        UpdateHash(index, item);
        base.SetItem(index, item);
    }

    #endregion 

    #region Value equality

    public bool Equals(HashedList<T> other)
    {
        if (other == null)
            return false;

        if (object.ReferenceEquals(this, other))
            return true;

        if (other.Count != this.Count)
            return false;

        if (other._hash != this._hash)
            return false;

        return CompareElements(other);
    }

    private bool CompareElements(HashedList<T> other)
    {
        for (int i = 0; i < this.Count; i++)
        {
            if (this[i] == null)
            {
                if (other[i] != null)
                    return false;
            }

            if (this[i].Equals(other[i]) == false)
                return false;
        }

        return true;
    }

    public override bool Equals(object obj)
    {
        var hashed = obj as HashedList<T>;
        if (hashed != null)
            return Equals(hashed);

        return base.Equals(obj);
    }

    public override int GetHashCode()
    {
        return _hash;
    }

    #endregion
}

如果传递了具有相同元素的任何object.Equals实现,您也可以认为IList<T>应该返回true,但由于它们的哈希码会有所不同,因此会破坏一致性。这是object.Equals IIRC的推荐实施。

答案 2 :(得分:1)

可以使用bloom filters执行此任务[for sets]。每组还附有一个布隆过滤器。

如果两个过滤器相同 - 结构可能相同

如果这两个过滤器不相同 - 结构彼此明确不同

向上:
没有假阴性。如果过滤器不同 - 结构不同。

不利方面:
你可能有误报。您需要额外检查[完全遍历]以确保2个结构确实相同。

请注意,误报率是布隆过滤器大小的函数 - 它越大 - 得到的误报就越少。

另请注意:由于布隆过滤器实际上是位集 - 比较两个布隆过滤器可以非常有效地实现。

答案 3 :(得分:1)

以下是关于该主题的非常有用(且详细)的讨论,包括几种集合类型的参考实现。

http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2009/n2986.pdf

  

通常,计算排列是二次运算。然而,   给出两个使用相同哈希的无序容器   密钥等价函数,元素将被分割成   密钥等价组使比较效率更高。

答案 4 :(得分:0)

如果使用安全散列函数,则碰撞的可能性非常小(如果您使用最新的散列函数,则可以在发现碰撞时写一篇论文: - ))。

如果您的集合是作为树实现的,那么您可以维护从叶子到根计算的哈希值,其成本是常量因子乘以您必须执行的树更新的成本。不幸的是,计算安全散列的常数因素可能非常大。不幸的是,你需要两个具有相同对象的集合才能拥有相同的树结构。这适用于http://en.wikipedia.org/wiki/Radix_tree,但不适用于典型的平衡树,其中历史或更新会影响树结构。

完美的哈希函数通常被调整为适合特定集合,这可能不适用于您的情况。如果散列函数映射到数字1..N,那么给定N + 1个对象,总会有至少一次碰撞。

答案 5 :(得分:0)

不,这理论上不可能:如果您的哈希值为32位,则只能区分2 ^ 32个变体,但列表可以任意增大。使用相同的参数,在运行时&lt; = k循环中,您只能进行大致的k比较。

如果您不想100%保证,您当然可以使用哈希函数。但是,我不会重新发明轮子,这通常会导致使用标准库的结果更糟。例如,您可能会忘记:

答案 6 :(得分:0)

我会去

hash(structure) := hast(item1) ^ hash(item2) ^ ... ^ hash(item_n)

取决于散列函数(首先,取决于其输出大小),这将为您提供良好的误报概率。它不会产生假阴性,并且很容易在短暂的恒定时间内插入和移除。他们击败布隆过滤器,因为假阳性概率不依赖于元素的数量。

对于数组或列表 - 具有不同顺序的相同内容的数组的可能性有多大?如果是,您可以轻松地使哈希值取决于项目位置:

hash(structure) := hast(item1, 1) ^ hash(item2, 2) ^ ... ^ hash(item_n, n)

在这种情况下,删除和插入可以是数组末尾的O(1)。中间的随机插入更加困难,但是再次,无论如何它们都是数组的O(n)。

答案 7 :(得分:0)

我在阅读你的问题时的第一个想法就是“概率论”的意思。您是否正在考虑将概率方法作为获得某种正确(无错误)的精确(可数)答案的方法?或者您是否愿意在结果中产生一些错误?

在后一种情况下,在应用日志函数后比较数据时,可以利用渐近“等价”。考虑Linear Counting

  • 创建大小为 m 的零初始化位图 b
  • 选择哈希函数 f
  • 将f应用于每个输入,获取值 v
  • 将位置 v 的位图设置为1

要计算计数,公式为:

n = - m * ln(联合国 / m

其中:

  • n - &gt;近似计数
  • 联合国 - &gt; m
  • 中的零位数

要适当调整 m 的大小,请参阅上面原始论文的链接。另外,请参阅最近的博客文章,其中还包括HyperLogLog:

http://highscalability.com/blog/2012/4/5/big-data-counting-how-to-count-a-billion-distinct-objects-us.html