哈希查找或二进制搜索哪个更快?


69

如果给定了一组静态对象(静态的,一旦加载就很少加载,就意味着它永远不会变化),需要在其中进行重复并发查找以达到最佳性能,更好的情况是,HashMap还是使用某些自定义比较器进行二进制搜索的数组?

答案是对象还是结构类型的函数?哈希和/或相等函数性能?哈希唯一性?清单大小? Hashset尺寸/布景尺寸?

我正在查看的布景大小可以在500k到10m之间,以防信息有用。

当我在寻找C#答案时,我认为真正的数学答案不在于语言,因此我不包含该标签。但是,如果有C#特定的事情要注意,则需要该信息。


1
什么是“查找”?您是否只想测试成员资格(是否存在特定元素)?还是您有键/值对,并想找到与某个键关联的值?
ShreevatsaR

取决于哈希函数的完善程度。
jmucchiello,2009年

Answers:


22

好吧,我会尽量简短。

C#简短答案:

测试两种不同的方法。

.NET为您提供了通过一行代码来更改方法的工具。否则,请使用System.Collections.Generic.Dictionary,并确保将其初始化为大容量作为初始容量,否则,由于GC必须执行收集旧存储桶阵列的工作,因此您将在余下的时间里插入项目。

更长的答案:

哈希表具有ALMOST恒定的查找时间,并且在现实世界中访问哈希表中的项目并不仅仅需要计算哈希。

要获得某项,您的哈希表将执行以下操作:

  • 获取密钥的哈希
  • 获取该哈希的存储桶编号(通常,地图函数看起来像此bucket = hash%bucketsCount)
  • 遍历从该存储桶开始的项目链(基本上是共享相同存储桶的项目列表,大多数哈希表使用这种处理存储桶/哈希冲突的方法),并将每个键与您要添加/添加的项目之一进行比较/删除/更新/检查是否包含。

查找时间取决于散列函数的“良好”程度(输出的稀疏程度)和快速程度,所使用的存储桶数以及键比较器的速度如何,这并不总是最佳的解决方案。

更好更深入的解释:http : //en.wikipedia.org/wiki/Hash_table


55

对于非常小的馆藏,差异将可忽略不计。如果您要进行大量查找,则在范围的低端(500k项),您会开始发现差异。二进制搜索将为O(log n),而哈希查找将为O(1),分期偿还。这与真正的常数并不相同,但是您仍然必须拥有一个非常糟糕的哈希函数才能获得比二进制搜索更差的性能。

(当我说“可怕的哈希”时,我的意思是:

hashCode()
{
    return 0;
}

是的,它本身速度很快,但是会导致您的哈希映射成为链接列表。)

ialiashkevich使用数组和Dictionary编写了一些C#代码,以比较这两种方法,但是它使用Long值作为键。我想测试在查找过程中实际执行哈希函数的内容,因此我修改了该代码。我将其更改为使用String值,并且将填充和查找部分重构为它们自己的方法,因此更容易在事件探查器中查看。作为比较,我还留了使用Long值的代码。最后,我摆脱了自定义二进制搜索功能,并在Array类中使用了该功能。

这是代码:

class Program
{
    private const long capacity = 10_000_000;

    private static void Main(string[] args)
    {
        testLongValues();
        Console.WriteLine();
        testStringValues();

        Console.ReadLine();
    }

    private static void testStringValues()
    {
        Dictionary<String, String> dict = new Dictionary<String, String>();
        String[] arr = new String[capacity];
        Stopwatch stopwatch = new Stopwatch();

        Console.WriteLine("" + capacity + " String values...");

        stopwatch.Start();

        populateStringArray(arr);

        stopwatch.Stop();
        Console.WriteLine("Populate String Array:      " + stopwatch.ElapsedMilliseconds);

        stopwatch.Reset();
        stopwatch.Start();

        populateStringDictionary(dict, arr);

        stopwatch.Stop();
        Console.WriteLine("Populate String Dictionary: " + stopwatch.ElapsedMilliseconds);

        stopwatch.Reset();
        stopwatch.Start();

        Array.Sort(arr);

        stopwatch.Stop();
        Console.WriteLine("Sort String Array:          " + stopwatch.ElapsedMilliseconds);

        stopwatch.Reset();
        stopwatch.Start();

        searchStringDictionary(dict, arr);

        stopwatch.Stop();
        Console.WriteLine("Search String Dictionary:   " + stopwatch.ElapsedMilliseconds);

        stopwatch.Reset();
        stopwatch.Start();

        searchStringArray(arr);

        stopwatch.Stop();
        Console.WriteLine("Search String Array:        " + stopwatch.ElapsedMilliseconds);

    }

    /* Populate an array with random values. */
    private static void populateStringArray(String[] arr)
    {
        for (long i = 0; i < capacity; i++)
        {
            arr[i] = generateRandomString(20) + i; // concatenate i to guarantee uniqueness
        }
    }

    /* Populate a dictionary with values from an array. */
    private static void populateStringDictionary(Dictionary<String, String> dict, String[] arr)
    {
        for (long i = 0; i < capacity; i++)
        {
            dict.Add(arr[i], arr[i]);
        }
    }

    /* Search a Dictionary for each value in an array. */
    private static void searchStringDictionary(Dictionary<String, String> dict, String[] arr)
    {
        for (long i = 0; i < capacity; i++)
        {
            String value = dict[arr[i]];
        }
    }

    /* Do a binary search for each value in an array. */
    private static void searchStringArray(String[] arr)
    {
        for (long i = 0; i < capacity; i++)
        {
            int index = Array.BinarySearch(arr, arr[i]);
        }
    }

    private static void testLongValues()
    {
        Dictionary<long, long> dict = new Dictionary<long, long>(Int16.MaxValue);
        long[] arr = new long[capacity];
        Stopwatch stopwatch = new Stopwatch();

        Console.WriteLine("" + capacity + " Long values...");

        stopwatch.Start();

        populateLongDictionary(dict);

        stopwatch.Stop();
        Console.WriteLine("Populate Long Dictionary: " + stopwatch.ElapsedMilliseconds);

        stopwatch.Reset();
        stopwatch.Start();

        populateLongArray(arr);

        stopwatch.Stop();
        Console.WriteLine("Populate Long Array:      " + stopwatch.ElapsedMilliseconds);

        stopwatch.Reset();
        stopwatch.Start();

        searchLongDictionary(dict);

        stopwatch.Stop();
        Console.WriteLine("Search Long Dictionary:   " + stopwatch.ElapsedMilliseconds);

        stopwatch.Reset();
        stopwatch.Start();

        searchLongArray(arr);

        stopwatch.Stop();
        Console.WriteLine("Search Long Array:        " + stopwatch.ElapsedMilliseconds);
    }

    /* Populate an array with long values. */
    private static void populateLongArray(long[] arr)
    {
        for (long i = 0; i < capacity; i++)
        {
            arr[i] = i;
        }
    }

    /* Populate a dictionary with long key/value pairs. */
    private static void populateLongDictionary(Dictionary<long, long> dict)
    {
        for (long i = 0; i < capacity; i++)
        {
            dict.Add(i, i);
        }
    }

    /* Search a Dictionary for each value in a range. */
    private static void searchLongDictionary(Dictionary<long, long> dict)
    {
        for (long i = 0; i < capacity; i++)
        {
            long value = dict[i];
        }
    }

    /* Do a binary search for each value in an array. */
    private static void searchLongArray(long[] arr)
    {
        for (long i = 0; i < capacity; i++)
        {
            int index = Array.BinarySearch(arr, arr[i]);
        }
    }

    /**
     * Generate a random string of a given length.
     * Implementation from https://stackoverflow.com/a/1344258/1288
     */
    private static String generateRandomString(int length)
    {
        var chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
        var stringChars = new char[length];
        var random = new Random();

        for (int i = 0; i < stringChars.Length; i++)
        {
            stringChars[i] = chars[random.Next(chars.Length)];
        }

        return new String(stringChars);
    }
}

以下是几种不同大小的集合的结果。(时间以毫秒为单位。)

500000长值...
填充长字典:26
填充长数组:2
搜索长字典:9
搜索长数组:80

500000字符串值...
填充字符串数组:1237
填充字符串字典:46
排序字符串数组:1755
搜索字符串字典:27
搜索字符串数组:1569

1000000长值...
填充长字典:58
填充长数组:5
搜索长字典:23
搜索长数组:136

1000000字符串值...
填充字符串数组:2070
填充字符串字典:121
排序字符串数组:3579
搜索字符串字典:58
搜索字符串数组:3267

3000000长值...
填充长字典:207
填充长数组:14
搜索长字典:75
搜索长数组:435

3000000字符串值...
填充字符串数组:5553
填充字符串字典:449
排序字符串数组:11695
搜索字符串字典:194
搜索字符串数组:10594

10000000长值...
填充长字典:521
填充长数组:47
搜索长字典:202
搜索长数组:1181

10000000字符串值...
填充字符串数组:18119
填充字符串字典:1088
排序字符串数组:28174
搜索字符串字典:747
搜索字符串数组:26503

为了进行比较,以下是该程序的最后一次运行的探查器输出(1000万个记录和查找)。我重点介绍了相关功能。他们与上述秒表计时指标非常吻合。

Profiler的输出可进行1000万条记录和查找

您可以看到字典查找比二进制搜索快得多,并且(如预期的那样)集合越大,差异就越明显。因此,如果您具有合理的散列功能(相当快且几乎没有冲突),则散列查找应胜过二进制搜索此范围内的集合。


1
md5完全不适合作为在哈希表中查找值的哈希。这是一个加密哈希。
比尔蜥蜴

11
不是“完全不合适”,只是缓慢。甚至良好的非加密哈希函数的确可能比小尺寸二进制搜索的速度慢。
尼克·约翰逊

2
小校正-随机数据和良好哈希函数的平均值为O(1)。没有O(1)摊销。
orip

2
不,getHashCode比比较慢。长字符串慢很多。
斯蒂芬·埃格蒙特

29
令人惊讶的是,这个答案被大肆抨击,因为这个答案是错误的-二进制搜索比哈希表更快是很常见的。log n是一个很小的因子,对于缓存大小,恒定的缩放因子以及任何大小的数据而言,log n都很容易抵消-毕竟,数据需要适合这个宇宙;实际上,在开始更具体地考虑性能之前,没有任何数据结构可能包含超过2 ^ 64个项目,并且可能不超过2 ^ 30个项目。
Eamon Nerbonne

39

Bobby,Bill和Corbin的答案是错误的。对于固定/有界n,O(1)不比O(log n)慢:

log(n)是常数,因此它取决于常数时间。

对于慢速哈希函数,有没有听说过md5?

默认的字符串哈希算法可能会触及所有字符,并且很容易比长字符串键的平均比较慢100倍。去过也做过。

您可能可以(部分)使用基数。如果您可以分成256个大致相同大小的块,那么您正在寻找2k到40k的二进制搜索。这可能会提供更好的性能。

[编辑]太多的人投票反对他们不了解的内容。

用于二进制搜索的字符串比较排序集具有一个非常有趣的属性:它们越接近目标,速度就越慢。首先,他们将打破第一个角色,最后只有最后一个角色。假设他们的时间固定是不正确的。


11
@Stephan:我们三个都说O(1)比O(log n)快。您还需要查看O表示法的含义。当输入大小改变时,它比较算法的相对资源使用情况。谈论固定的n是没有意义的。
比尔蜥蜴

1
嗯... @迈克:n保持稳定很重要。如果n是常数且较小,则O(log n)可能比O(1)快得多,因此O(1)中的恒定时间运算会花费很长时间。但是,如果n不恒定,则O(log n)不可能比O(1)快。
克劳迪(Claudiu)

1
@Bill:问题是关于一个几乎不变的集合。当然,散列可以更快,但也可能有20倍以上的冲突。您必须比较实际的实现。
斯蒂芬·艾格蒙特

3
实际上,当字符串比较接近目标时,字符串比较变慢的点并不是二进制搜索中固有的,因为在缩小子集范围时可以跟踪公共前缀。(并非有人这样​​做。)
Mike Dunlavey

1
@StephanEggermont感谢您的回答。迭代次数只是性能方面的一个考虑因素,因为较小的二元搜索查找时间很可能胜过哈希图。
贾斯汀·迈纳斯

21

这个问题的唯一合理答案是:这取决于。它取决于数据的大小,数据的形状,哈希实现,二进制搜索实现以及数据的存放位置(即使问题中未提及)。其他几个答案也可以这么说,所以我可以删除它。但是,最好将我从反馈中学到的知识分享给我的原始答案。

  1. 我写道:“哈希算法是O(1),而二进制搜索是O(log n)。 ”-如注释中所述,Big O表示法估计的是复杂度,而不是速度。这是绝对正确的。值得注意的是,我们通常使用复杂度来了解算法的时间和空间要求。因此,尽管认为复杂度与速度严格相同是愚蠢的,但是在没有时间或空间的情况下估计复杂度是不寻常的。我的建议:避免使用Big O表示法。
  2. 我写道:“当n接近无穷大时……”-这是我在答案中可能包含的最愚蠢的事情。无限与您的问题无关。您提到的上限是一千万。忽略无限。正如评论者所指出的那样,非常大的数字会产生各种哈希值问题。(很多人也不会在公园散步时进行二进制搜索。)我的建议:除非提及无穷大,否则不要提及无穷大。
  3. 同样从注释中可以:请注意默认的字符串散列(您是否在对字符串进行散列?您不会提到。),数据库索引通常是b树(让人深思)。我的建议:考虑所有选择。考虑其他数据结构和方法...例如老式的trie(用于存储和检索字符串)或R-tree(用于空间数据)或MA-FSA(最小非循环有限状态自动机-占用空间小)。

给定这些注释,您可能会假设使用哈希表的人员处于混乱状态。哈希表鲁re和危险吗?这些人疯了吗?

原来他们不是。正如二叉树擅长某些事情(顺序数据遍历,存储效率)一样,哈希表也有自己的亮点。特别是,它们可以很好地减少获取数据所需的读取次数。散列算法可以生成一个位置,然后直接在内存或磁盘中跳转到该位置,而二进制搜索则在每次比较期间读取数据以确定下一步要读取什么。每次读取都有可能导致高速缓存未命中,它比CPU指令慢一个数量级(或更多)。

这并不是说哈希表比二进制搜索更好。他们不是。也不建议所有哈希和二进制搜索实现都相同。他们不是。如果我有观点,就是这样:两种方法都存在是有原因的。由您决定哪个最适合您的需求。

原始答案:


哈希算法为O(1),而二进制搜索为O(log n)。因此,当n接近无穷大时,散列性能相对于二进制搜索有所提高。您的里程取决于n,哈希实现和二进制搜索实现。

关于O(1)的有趣讨论。释义:

O(1)并不是瞬时的。这意味着性能不会随着n大小的增长而改变。您可以设计一种哈希算法,这种算法是如此之慢,没人会使用它,而它仍然是O(1)。我相当确定.NET / C#不会遭受成本过高的哈希处理;但是)


1
不知道为什么这会被否决-好的答案和一个有趣的观点。+1。
xan

10
-1:大O表示法衡量的是复杂度,而不是相对于其他算法的速度。哈希为O(1)并因此比O(log n)二进制搜索快的说法并不是严格正确的。
朱丽叶

3
而且实际上还不正确。默认的字符串哈希值会影响整个字符串,并且比比较慢。
斯蒂芬·埃格蒙特

2
@斯蒂芬:同意!更好的选择是字符串长度+前8个字符的哈希值,或者长度+前4个+最后4个字符的哈希值。
Zan Lynx 2010年

1
@Corbin-但是散列的宽度无论如何都会对表的大小施加恒定的限制,对于二进制搜索而言,这是不存在的。忘记替换旧的32位哈希函数,也许您的哈希表将在O(1)与O(log n)变得相关之前就停止工作了。如果考虑到表变大时需要更宽的哈希值,则基本上回到O(log n),其中n是表中键的最大数目(而不是像二进制文件那样实际存在的项数)树)。当然,这是对理论的一种批评-哈希在实践中通常更快。
Steve314 2011年

7

如果您的对象集确实是静态且不变的,则可以使用完美的哈希值来保证O(1)性能。我见过gperf提到过几次,尽管我从来没有机会亲自使用过。


1
如果您可以在任何算法或数据结构的大小上设置恒定的上限,则可以为其性能声明O(1)界限。这通常是在现实中完成的-例如,在B树的一个节点内进行搜索的性能被认为是恒定的,因为(与线性搜索或二进制搜索无关)节点的最大大小是恒定的。+1是一个很好的建议,但对于O(1)声明,我认为您在作弊。
Steve314 2011年

1
@ Steve314,我想您错过了完美哈希的意义。通过自定义哈希函数,可以确保您不会发生冲突,因此,在拥有哈希值之后,确实是一种访问数据的操作,另外还要进行一次比较以确保您没有在表中查找内容。
马克·兰索姆

但我的意思是,您可以针对特定且恒定量的数据自定义哈希。您对完美散列的优点是完全正确的,但是由于它不能应付变化的n(就此而言,甚至不能改变n中的数据),它仍然在作弊。
Steve314 2011年

6

尽管二进制搜索具有更好的最坏情况特征,但哈希通常更快。哈希访问通常是一种获取哈希值以确定记录将位于哪个“存储桶”的计算,因此性能通常取决于记录的分布均匀程度以及用于搜索存储桶的方法。不好的哈希函数(会留下几个带有大量记录的存储桶)以及对存储桶进行线性搜索会导致搜索缓慢。(第三,如果您正在读取磁盘而不是内存,则散列存储区可能是连续的,而二叉树几乎可以保证非本地访问。)

如果通常需要快速,请使用哈希。如果您确实想要保证有限的性能,则可以使用二叉树。


树还具有退化的案例,可以有效地转化为列表。当然,大多数变体都有严格的不变式来避免这些。
哈维尔

1
误导性答案。在实践中经常破坏哈希的性能问题是哈希函数,而不是冲突。
斯蒂芬·埃格蒙特

@Javier-实用的二叉树(AVL,红黑等)没有那些退化的情况。也就是说,某些哈希表也不会,因为冲突处理策略是一种选择。IIRC是D的开发人员,使用(不平衡)二叉树方案处理Dscript的哈希表冲突,并通过这样做显着提高了平均情况下的性能。
Steve314 2011年

5

没有人惊讶地提到Cuckoo散列,它提供有保证的O(1),并且与完美散列不同,它能够使用它分配的所有内存,因为完美散列最终可以保证有O(1),但浪费了它的大部分分配。警告?插入时间可能非常慢,尤其是随着元素数量的增加,因为所有优化都在插入阶段执行。

我相信此版本的某些版本会在路由器硬件中用于ip查找。

查看链接文字


1
完美的哈希可以使用它分配的所有内存。它通常不是因为找到这样一个完美的完美哈希函数而需要进行的工作,但是对于小型数据集而言,它是完全可行的。
Steve314 2011年

4

与数组相比,字典/哈希表正在使用更多的内存,并且需要花费更多的时间来填充。但是通过字典而不是数组内的二进制搜索可以更快地完成搜索。

这是千万个Int64的数字要搜索和填充的项目。加上示例代码,您可以自己运行。

词典记忆: 462,836

阵列记忆体: 88,376

填充字典:402

填充数组: 23

搜索词典: 176

搜索数组: 680

using System;
using System.Collections.Generic;
using System.Diagnostics;

namespace BinaryVsDictionary
{
    internal class Program
    {
        private const long Capacity = 10000000;

        private static readonly Dictionary<long, long> Dict = new Dictionary<long, long>(Int16.MaxValue);
        private static readonly long[] Arr = new long[Capacity];

        private static void Main(string[] args)
        {
            Stopwatch stopwatch = new Stopwatch();

            stopwatch.Start();

            for (long i = 0; i < Capacity; i++)
            {
                Dict.Add(i, i);
            }

            stopwatch.Stop();

            Console.WriteLine("Populate Dictionary: " + stopwatch.ElapsedMilliseconds);

            stopwatch.Reset();

            stopwatch.Start();

            for (long i = 0; i < Capacity; i++)
            {
                Arr[i] = i;
            }

            stopwatch.Stop();

            Console.WriteLine("Populate Array:      " + stopwatch.ElapsedMilliseconds);

            stopwatch.Reset();

            stopwatch.Start();

            for (long i = 0; i < Capacity; i++)
            {
                long value = Dict[i];
//                Console.WriteLine(value + " : " + RandomNumbers[i]);
            }

            stopwatch.Stop();

            Console.WriteLine("Search Dictionary:   " + stopwatch.ElapsedMilliseconds);

            stopwatch.Reset();

            stopwatch.Start();

            for (long i = 0; i < Capacity; i++)
            {
                long value = BinarySearch(Arr, 0, Capacity, i);
//                Console.WriteLine(value + " : " + RandomNumbers[i]);
            }

            stopwatch.Stop();

            Console.WriteLine("Search Array:        " + stopwatch.ElapsedMilliseconds);

            Console.ReadLine();
        }

        private static long BinarySearch(long[] arr, long low, long hi, long value)
        {
            while (low <= hi)
            {
                long median = low + ((hi - low) >> 1);

                if (arr[median] == value)
                {
                    return median;
                }

                if (arr[median] < value)
                {
                    low = median + 1;
                }
                else
                {
                    hi = median - 1;
                }
            }

            return ~low;
        }
    }
}

3

我强烈怀疑在大小约为1M的问题集中,散列会更快。

只为数字:

二进制搜索将需要〜20个比较(2 ^ 20 == 1M)

哈希查找将需要对搜索关键字进行1次哈希计算,之后可能需要进行少量比较以解决可能的冲突

编辑:数字:

    for (int i = 0; i < 1000 * 1000; i++) {
        c.GetHashCode();
    }
    for (int i = 0; i < 1000 * 1000; i++) {
        for (int j = 0; j < 20; j++)
            c.CompareTo(d);
    }

次:c =“ abcde”,d =“ rwerij”哈希码:0.0012秒。比较:2.4秒。

免责声明:实际上,将散列查找与二进制查找进行基准测试可能比不完全相关的测试更好。我什至不确定GetHashCode是否在后台被记忆


3
使用一个不错的优化程序,两者的结果都应该为0。
斯蒂芬·埃格蒙特

2

我会说这主要取决于hash和compare方法的性能。例如,当使用很长但随机的字符串键时,比较将始终产生非常快的结果,但是默认的哈希函数将处理整个字符串。

但是在大多数情况下,哈希映射应该更快。


1
没有任何理由哈希函数必须使用整个字符串。
哈维尔

只是非常实用的一个,您不希望将字符串的所有扩展名都放在同一个存储桶中(除非您将其用作一种基数,并从存储桶元素中删除前缀,然后将其转换为类似于trie的字符串)结构)
斯蒂芬·埃格蒙特

2

我不知道为什么没有人提到完美的哈希

仅当您的数据集固定很长时间时才有意义,但是它所做的是分析数据并构造一个完美的哈希函数,以确保不发生冲突。

如果您的数据集是恒定的,并且与应用程序运行时间相比,计算该函数的时间很小,则非常简洁。


1

这取决于您如何处理哈希表的重复项(如果有的话)。如果您确实希望允许散列键重复(没有完美的散列函数),则对于主键查找,它仍然为O(1),但是在后面搜索“正确”值可能会很昂贵。从理论上讲,答案通常是散列更快。YMMV取决于您放入的数据...


1
“没有完美的哈希函数” –不,那是错误的。完美的散列之类的东西具有广泛的应用领域。最简单的情况当然是简并哈希函数h(x)= x。请注意,这一个有效的哈希函数,在很多情况下都使用了此哈希函数。
康拉德·鲁道夫

1
@Konrad-完美的哈希值仅在特定环境下才是完美的。实际上,“ perfect”是一个名称,而不是一个描述。没有万能的哈希表。话虽如此,使用某些众所周知的标准哈希函数的现实世界问题的机率极低,除非在恶意攻击者利用所使用的哈希函数的知识的特定情况下。
Steve314 2011年

1

这里描述了哈希的构建方式,并且由于键的Universe相当大,并且哈希函数被构建为“非常具有内射性”,因此很少发生冲突,哈希表的访问时间实际上不是O(1)……基于某些概率的东西。但是,可以说哈希的访问时间几乎总是小于时间O(log_2(n))


0

当然,对于如此大的数据集,哈希是最快的。

由于数据很少更改,因此可以进一步提高速度的一种方法是以编程方式生成临时代码,以将其作为巨型switch语句(如果编译器可以处理)进行第一层搜索,然后分支进行搜索产生的水桶。


特殊套管的第一层绝对是要尝试的东西。
斯蒂芬·埃格蒙特

我猜我对代码生成有一个好印象,只是因为没有一种流行的主要“方法”可以告诉您什么时候获胜。
Mike Dunlavey,

2
我有一个代码生成器,可为决策树生成嵌套的switch语句。有时,它会生成gotos(因为严格来说,这是一个决策非循环有向图)。但是“切换”不是算法。编译器可能会使用硬编码的二进制搜索或查找表(以几种方式之一构造-可能是简单数组,可能是哈希表,可能是二进制搜索的数组)或其他任何东西。在这里,我可能会超出范围-硬编码的二进制搜索和简单数组都确实存在于实际的编译器中,但是除此之外-编译器做得很好,这就足够了。
Steve314 2011年

@ Steve314:您正在按照我的方式去做。如果情况适当连续,“ switch”将创建一个跳转表,这就是一种算法。我从未听说过编译器会为开关生成if-tree,但是如果这样做的话,那将是非常了不起的,这是另一种算法。无论如何,代码生成可以是一个巨大的胜利。这取决于您要搜索的“表格”是否相对静态。
Mike Dunlavey

@Mike-我现在不确定是GCC还是VC ++(很可能是GCC),但是我已经在分解生成的代码中看到了if-tree。至于相对静态的,我的代码生成器正在执行多个分派,并且多态函数的可能实现的集合在运行时当然是完全静态的。但是,这对于单独的编译不是很好,因为您需要了解所有情况来构建决策树。有一些语言通过单独的编译来执行此操作,但是它们在运行时(例如,在首次调用时)构建决策树/表。
Steve314 2011年

0

答案取决于。让我们考虑元素“ n”的数量非常大。如果您擅长编写更好的散列函数(冲突较少),那么散列是最好的。 请注意, 哈希函数仅在搜索时执行一次,并且直接指向相应的存储桶。因此,如果n高,则开销并不大。 哈希表中的问题
但是哈希表中的问题是,如果哈希函数不好(发生更多冲突),则搜索不是O(1)。它倾向于O(n),因为在存储桶中进行搜索是线性搜索。比二叉树还差。 因此, 如果可以看到一个好的哈希方法,请使用哈希表。否则,最好使用二叉树。二叉树中的问题: 在二叉树中,如果树不平衡,则也趋向于O(n)。例如,如果您将1,2,3,4,5插入到更可能是列表的二叉树中。


0

这更多是对比尔的回答的评论,因为即使他的回答有错,他的回答也是如此多。所以我不得不发布这个。

我看到很多讨论,涉及哈希表中查找的最坏情况的复杂性是什么,什么才算是摊销分析,什么不是。请检查下面的链接

哈希表运行时复杂度(插入,搜索和删除)

与Bill所说的相反,最坏的情况是O(n)而不是O(1)。因此,他的O(1)复杂度不会摊销,因为该分析只能用于最坏的情况(他自己的Wikipedia链接也是如此)

https://zh.wikipedia.org/wiki/哈希表

https://zh.wikipedia.org/wiki/Amortized_analysis


0

这个问题比纯算法性能的范围还复杂。如果除去二进制搜索算法对缓存更友好的因素,则从一般意义上讲,哈希查找将更快。找出的最佳方法是构建程序并禁用编译器优化选项,并且由于一般意义上的算法时间效率为O(1),我们可以发现哈希查找更快。

但是,当启用编译器优化并尝试使用较少数量的样本(例如少于10,000个)进行相同的测试时,二进制搜索将利用其缓存友好的数据结构胜过哈希查找。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.