【问题标题】:Binary Search and Hashtable Search二进制搜索和哈希表搜索
【发布时间】:2013-10-22 20:04:37
【问题描述】:

我想找出字典查找和数组的二进制搜索查找之间的权衡点。我期待字典的恒定时间查找,以及二进制搜索的对数时间查找,具体取决于集合的大小,对于较小的集合,二进制搜索的性能更好。

但是,当我看到以下结果时,我感到很惊讶:

我很惊讶: 1. 二分搜索起初呈对数增长,然后增长得更快。 2. 哈希起初相当一致,但随后也开始缓慢增长。 3. 二分查找永远比散列查找好。下面是我的代码。我做错了什么?

class Program
{
    static void Main(string[] args)
    {
        var r = new Random();
        var targets = Enumerable.Range(0, 1000 * 1000).Select(_ => r.Next(int.MaxValue)).ToList();

        for (int totalCount = 1; totalCount < 1000*1000*10; totalCount*=2)
        {
            var a = Enumerable.Range(0, totalCount).Select(_ => r.Next(int.MaxValue)).Distinct().Select(v => new thing(v)).OrderBy(t => t.value).ToArray();
            var d = a.ToDictionary(t => t.value);

            var watch = new System.Diagnostics.Stopwatch();

            {
                watch.Start();
                var found = targets.Select(t => BinarySearch(t, a)).Where(t => t != null).Count();
                watch.Stop();
                Console.WriteLine(string.Format("found {0} things out of {2} in {1} ms with binary search", found, watch.ElapsedMilliseconds, a.Length));
            }
            {
                watch.Restart();
                var found =  targets.Select(t => HashSearch(t, d)).Where(t => t != null).Count();
                watch.Stop();
                Console.WriteLine(string.Format("found {0} things out of {2} in {1} ms with hash search", found, watch.ElapsedMilliseconds, d.Keys.Count));
            }
        }
        Console.ReadLine();
    }

    static thing HashSearch(int needle, Dictionary<int, thing> hash)
    {
        if (!hash.ContainsKey(needle))
            return null;
        return hash[needle];
    }

    static thing BinarySearch(int needle, thing[] sortedHaystack)
    {
        return BinarySearch(needle, sortedHaystack, 0, sortedHaystack.Length - 1);
    }
    static thing BinarySearch(int needle, thing[] sortedHaystack, int minimum, int maximum)
    {
        if (minimum > maximum)
            return null;
        var middle = (minimum + maximum) / 2;
        if (needle == sortedHaystack[middle].value)
            return sortedHaystack[middle];
        if (needle < sortedHaystack[middle].value)
            return BinarySearch(needle, sortedHaystack, minimum, middle - 1);
        return BinarySearch(needle, sortedHaystack, middle + 1, maximum);
    }

    class thing
    {
        public int value;
        public thing(int v)
        {
            value = v;
        }
    }
}

【问题讨论】:

  • 究竟有什么让你吃惊的?字典搜索不是完全恒定的事实?或者字典即使对于小型收藏也能胜出这一事实?请注意,通过迭代一个大列表,您最终会遇到很多缓存未命中只是为了迭代
  • @JonSkeet 好点。我已经用令我惊讶的事情澄清了我的问题。
  • 我不会说二分搜索正在呈指数增长 - 至少,它不是明显这样做,因为您的 x 轴是对数轴。将其绘制在线性 x 轴上,它可能会更清晰。其余的可能只是通过缓存未命中来解释,老实说 - 集合越大,即使使用概念上的恒定时间查找,您也会获得更多的缓存未命中。
  • (换一种说法:这些查找的时间复杂度计算通常假设每次内存读取的成本相同。当一些读取错过缓存而其他读取没有时,情况并非如此。)
  • @JonSkeet 你太快了。我在发表评论后立即修复了指数部分。缓存问题可以解释为什么两种算法在同一点左右开始变慢。

标签: c# .net data-structures hashtable binary-search


【解决方案1】:

(与 cmets 中的注释差不多。)

我怀疑您主要看到的是缓存未命中的影响。当集合很大时,您会遇到很多缓存未命中 - 特别是使用二分搜索时,它可能需要触及集合中的很多点才能找到元素。

在小尺寸时,我怀疑您也会看到缓存未命中,但这次是在您的 targets 列表中 - 以及 LINQ 本身的开销。 LINQ 速度很快,但当您所做的只是对中间的一个小集合执行一次搜索时,它仍然很重要。

我建议将您的循环重写为:

{
    // Use the same seed each time for consistency. Doesn't have to be 0.
    Random random = new Random(0);
    watch.Start();
    int found = 0;
    for (int i = 0; i < 1000 * 1000; i++)
    {
        if (BinarySearch(t, random.Next(int.MaxValue)) != null)
        {
            found++;
        }
    }
    watch.Stop();
    Console.WriteLine(string.Format
         "found {0} things out of {2} in {1} ms with binary search",
         found, watch.ElapsedMilliseconds, a.Length));
}

当然,你会遇到在循环中包含随机数生成的问题......如果你能找到一个比System.Random 更快的随机数生成器,你可能想看看。或者使用其他方式来确定要查找的元素。

哦,我个人会重写二进制搜索以使用迭代而不是递归,但那是另一回事。我不认为它会产生重大影响。

【讨论】:

  • 如您所料,迭代二分查找没有显着效果。
  • @JonSkeet and I'd personally rewrite the binary search to use iteration rather than recursion 为什么?
  • @JuanM.Elosegui:为什么要无缘无故地占用堆栈?在这里递归没有任何好处 - 循环二进制搜索非常容易编写,并且堆栈溢出的风险较小。
猜你喜欢
  • 2020-04-20
  • 2014-04-07
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2017-07-06
  • 2017-07-09
  • 2017-11-23
  • 1970-01-01
相关资源
最近更新 更多