【问题标题】:What is the AccessTime for Dictionary<Dictionary<char,int>,List<string>> , is it still O(1)?Dictionary<Dictionary<char,int>,List<string>> 的 AccessTime 是多少,还是 O(1)?
【发布时间】:2012-01-14 23:32:26
【问题描述】:

我想用Dictionary&lt;Dictionary&lt;char,int&gt;, List&lt;string&gt;&gt; 实现一个算法来查找字典中的字谜单词。

由于我需要为此字典实现我的自定义EqualityComparer,所以访问时间仍然是 O(1),即大 O(1) 吗?

第二个问题,作为EqualityComparer 的一部分,我还需要实现GetHashCode()。为Dictionary&lt;Dictionary&lt;char,int&gt;, List&lt;string&gt;&gt; 确定GetHashCode() 的有效方法是什么?

我只是想出了这个方法,有没有更好的选择?

public int GetHashCode(Dictionary<char, int> obj)
    {
        unchecked
        {
            int hashCode = 17;
            foreach (var item in obj)
            {
                hashCode += 23 * item.Key.GetHashCode();
            }
            return hashCode;
        }
    }

感谢任何建议。谢谢!

【问题讨论】:

  • 具有可变键的字典是痛苦的秘诀
  • 但它们不是典型 .net 代码 Ben 中最常见的字典键吗?
  • 不是最常见的字典键吗?不,字典和其他集合类型不经常用作其他字典的键。
  • 我的错误是我错误地将您的评论视为不可变!所以这让我想知道!是的,我明白你在说什么。

标签: .net generics dictionary big-o iequalitycomparer


【解决方案1】:

如何将单词“need”转换为字符串“d1e2n1”而不是使用字典作为键?为了构建此字符串,您可以使用二叉树。字符将用作键,字符数用作值。二叉树是按key自动排序的,字典不是这样的。

您可以通过将单个哈希值的二进制表示与 XOR 运算相结合来计算组合哈希值。使用 C#,您可以执行以下操作:

public override int GetHashCode()
{
    // Combine hashcode of a and b
    return a.GetHashCode() ^ b.GetHashCode();
}

在未排序列表中查找条目是 O(n) 操作。如果使用二进制搜索,则在排序列表中查找条目是 O(log(n)) 操作。

在字典中的列表中查找单词是 O(1 + n) 操作,与 O(n) 操作相同,或者 O(1 + log(n)) 操作,即与 O(log(n)) 操作相同。


编辑:

这是一个可能的实现:

var anagrams = new Dictionary<string, List<string>>();
foreach (string word in words) {
    string key = GetFrequency(word);
    List<string> list;
    if (anagrams.TryGetValue(key, out list)) {
        list.Add(word);
    } else {
        list = new List<string> { word };
        anagrams.Add(key, list);
    }
}

它使用这种方法来获取密钥:

private string GetFrequency(string word)
{
    var dict = new SortedDictionary<char, int>(); // Implemented as binary tree
    foreach (char c in word.ToLower()) {
        int count;
        if (dict.TryGetValue(c, out count)) {
            dict[c] += 1;
        } else {
            dict[c] = 1;
        }
    }
    return dict.Aggregate(new StringBuilder(), (sb, item) => sb.Append(item.Key).Append(item.Value), sb => sb.ToString());
}

对单词使用这个定义......

var words = new List<string> { "need", "eden", "team", "meat", "meta", "Nat", "tan" };

这个测试...

foreach (var item in anagrams.OrderBy(x => x.Key)) {
    Console.WriteLine();
    Console.WriteLine(item.Key + ":");
    foreach (string word in item.Value.OrderBy(w => w)) {
        Console.WriteLine("    " + word);
    }
}

...产生这个输出

a1e1m1t1:
    meat
    meta
    team

a1n1t1:
    Nat
    tan

d1e2n1:
    eden
    need

编辑 #2:

这是 Ben Voigt 建议的频率计算

private string GetFrequencyByBenVoigt(string word)
{
    char[] chars = word.ToLower().ToCharArray();
    Array.Sort(chars);
    return new string(chars);
}

测试结果是

aemt:
    meat
    meta
    team

ant:
    Nat
    tan

deen:
    eden
    need

【讨论】:

  • 一般来说是个好主意,但是“deen”怎么样(即按字母顺序对字母进行排序并保留重复)?
  • 真的有可能,在将 CharacterHashMap 作为分析中单词的字典后,我可以将其设为字符串并将其作为键。但在我得到字符串表示之前,它需要对 CharacterHashMap 进行排序。
  • 并且可以帮助我更好地理解如果两个单词不是字谜但它们的 HashCode 相等但比较失败会发生什么?它们将如何存储在字典中? stackoverflow.com/a/3809835/253032
  • 字典自动处理哈希值冲突。别担心。
  • 您将使用 Dictionary> 其中键是构造的频率字符串(按字母顺序排序的字母 + 它们的频率)。该列表将包含所有相应的字谜。
【解决方案2】:

Dictionary&lt;TKey, TValue&gt; 的访问时间接近 O(1) 但并非完全如此。在理想情况下(良好的分布/很少的冲突),您可以将其视为 O(1)。在由于 GetHashCode 值的低方差导致大量冲突的情况下,访问时间会降低并且可能接近 O(N)。

【讨论】:

    【解决方案3】:

    基于容器内容的哈希码将在容器中的项目计数中为 O(n)。您可以将字典包装成另一种类型并缓存哈希码,因此它只需要计算一次......但我可以想到几种比字典更有效的方法来存储该数据。

    【讨论】:

    • 何时达到理论上的 O(1) 访问时间?对于这种情况,您有什么建议可以关闭吗?
    • 当字典内部使用的哈希表与其大小相比只包含很少的条目并且哈希码分布良好时,将达到O(1)。微软的字典实现会在性能下降太多之前自动增加哈希表大小。
    • @Oliver:每个元素的复杂度是线性的,但字典中元素的数量不是线性的。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2021-10-04
    • 2018-01-25
    • 2014-08-13
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-05-15
    相关资源
    最近更新 更多