C# 二进制搜索和哈希表搜索

C# 二进制搜索和哈希表搜索,c#,.net,data-structures,hashtable,binary-search,C#,.net,Data Structures,Hashtable,Binary Search,我想找出字典查找和数组的二进制搜索查找之间的折衷点。我希望字典的查找时间是恒定的,而二进制搜索的查找时间是对数的,这取决于集合的大小,对于较小的集合,二进制搜索的效果更好 然而,当我看到以下结果时,我感到惊讶: 我很惊讶:1。二进制搜索开始是对数增长的,然后增长得更快。2.散列一开始是相当一致的,但随后也开始缓慢增长。3.二进制搜索永远不会比哈希查找更好。下面是我的代码。我做错了什么 class Program { static void Main(string[] args)

我想找出字典查找和数组的二进制搜索查找之间的折衷点。我希望字典的查找时间是恒定的,而二进制搜索的查找时间是对数的,这取决于集合的大小,对于较小的集合,二进制搜索的效果更好

然而,当我看到以下结果时,我感到惊讶:

我很惊讶: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;
        }
    }
}
类程序
{
静态void Main(字符串[]参数)
{
var r=新的随机变量();
var targets=Enumerable.Range(0,1000*1000)。选择(=>r.Next(int.MaxValue)).ToList();
对于(int totalCount=1;totalCount<1000*1000*10;totalCount*=2)
{
var a=Enumerable.Range(0,totalCount).Select(=>r.Next(int.MaxValue)).Distinct().Select(v=>newthing(v)).OrderBy(t=>t.value).ToArray();
var d=a.ToDictionary(t=>t.value);
var-watch=新系统.Diagnostics.Stopwatch();
{
watch.Start();
var found=targets.Select(t=>BinarySearch(t,a)).Where(t=>t!=null.Count();
看,停;
WriteLine(string.Format(“通过二进制搜索在{1}ms中的{2}中找到{0}个东西”,find,watch.elapsedmillesons,a.Length));
}
{
watch.Restart();
var found=targets.Select(t=>HashSearch(t,d)).Where(t=>t!=null.Count();
看,停;
WriteLine(string.Format(“通过哈希搜索在{1}ms中的{2}中找到{0}个东西”,find,watch.elapsedmillesons,d.Keys.Count));
}
}
Console.ReadLine();
}
静态事物哈希搜索(int指针、字典哈希)
{
如果(!hash.ContainsKey(针))
返回null;
返回散列[指针];
}
静态对象二进制搜索(int针,对象[]sortedHaystack)
{
返回二进制搜索(指针,sortedHaystack,0,sortedHaystack.Length-1);
}
静态事物二进制搜索(int针,thing[]sortedHaystack,int最小值,int最大值)
{
如果(最小值>最大值)
返回null;
var中间=(最小值+最大值)/2;
如果(指针==分拣系统堆栈[中间].值)
返回分拣系统堆栈[中间];
if(指针
(与评论中的注释非常相似。)

我想你主要看到的是缓存未命中的影响。当集合较大时,您将获得大量缓存未命中-尤其是使用二进制搜索时,可能需要接触集合中的许多点才能找到元素

在低大小的情况下,我怀疑您也会看到缓存未命中,但这一次在您的
目标
列表上,还有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));
}
{
//每次使用相同的种子以保持一致性。不必为0。
随机数=新随机数(0);
watch.Start();
int=0;
对于(int i=0;i<1000*1000;i++)
{
if(BinarySearch(t,random.Next(int.MaxValue))!=null)
{
发现++;
}
}
看,停;
Console.WriteLine(string.Format
“使用二进制搜索在{1}ms中的{2}中找到{0}个东西”,
发现,watch.elapsedmills,a.Length);
}
当然,你会遇到在循环中包含随机数生成的问题。。。如果你能找到一个比System.random快的随机数生成器,你可能想看看它的用法。或者使用其他方法确定要查找的元素


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

到底是什么让你感到惊讶?字典搜索不是完全恒定的事实?或者说,即使是小规模的收藏,这本词典也能胜出?请注意,通过对一个大列表进行迭代,您最终将只在迭代中出现大量缓存未命中。@JonSkeet说得不错。我已经澄清了我的问题,让我感到惊讶的是,我不会说二进制搜索是指数增长的——至少,考虑到你的x轴是对数轴,它显然不是指数增长的。将其绘制在线性x轴上,可能会更加清晰。老实说,其余的可能仅仅是通过缓存未命中来解释的——集合越大,即使在名义上的恒定时间查找中,也会得到越多的缓存未命中。(换言之:这些查找的时间复杂度计算通常假定每次内存读取的成本相同。当一些读取未命中缓存而其他读取未命中缓存时,情况并非如此。)@JonSkeet你的速度太快了。我在发表评论后立即修复了指数部分。缓存问题可以解释为什么两种算法在同一点上开始变慢。正如你所预测的,迭代二进制搜索没有显著效果。@JonSkeet
,我个人重写二进制搜索,使用迭代而不是重复cursion
为什么?@JuanM.Elosegui:为什么无缘无故占用堆栈?在这里递归没有任何好处-循环二进制搜索非常容易