c# - 在 C# 中,为什么从 List 创建 HashSet 比从 HashSet 开始更快?

标签 c# .net hashset

我有一个方法接受上限,并返回一个不超过该上限的素数列表。

    public static List<int> AllPrimesUnder(int upperLimit)

我后来决定我真的只需要在列表中查找,通常只问“这是素数”这个问题。因为我处理的是小于一百万的所有质数,所以我意识到 HashSet 是我应该使用的结构。当然,使用该方法的结果进行查找更快,但方法本身较慢

我认为它变慢的原因是 HashSet 在添加之前检查重复项,而 List 只是将它推到最后。令我感到惊讶的是,以及产生问题和标题的原因是,为什么要从 List 开始并使用它来创建 HashSet,如下所示:

    hashSet = new HashSet<int>(Prime.AllPrimesUnder(1000000));

比使用方法内部的 Hashset 更快,启用这样的调用:

    hashSet = Prime.AllPrimesUnder_Hash(1000000);

如果减速是在重复检查中,无论如何都应该进行相同数量的检查,对吧?这可能是我理解失败的地方。

这是我得到的质数低于 100 万的次数。

  • 0.1136s 纯哈希
  • 0.0975s 纯列表(预计会更快)
  • 0.0998s 纯列表转换为哈希(未预期)

如果可以用简单的术语解释其原因,我很想听听。我想至少我正在寻找的是足够的理解,以了解如果最终结果将是一个大型项目的哈希集,我应该从列表还是哈希集开始。

我在下面添加了 prime 方法的主体,但请注意,两者之间与数据结构的所有交互都是相同的(代码方面)。我不相信我向结构添加数据的方式会影响异常。

    public static List<int> AllPrimesUnder(int upperLimit)
    {
        List<int> primeList = new List<int>();
        primeList.Add(2);
        int testNumber = 3;
        bool isPrime;

        while (testNumber <= upperLimit)
        {
            isPrime = true;

            foreach (int prime in primeList)
            {
                if (testNumber % prime == 0)
                {
                    isPrime = false;
                    break;
                }
                if (testNumber < prime*prime)
                    break;
            }

            if (isPrime)
                primeList.Add(testNumber);

            testNumber++;
        }

        return primeList;
    }

编辑:根据要求,我正在添加 Hash 方法的代码。如果它看起来几乎相同,那是因为它确实如此。

public static HashSet<int> AllPrimesUnder_Hash(int upperLimit)
{
    HashSet<int> primeHash = new HashSet<int>();
    primeHash.Add(2);
    int testNumber = 3;
    bool isPrime;

    while (testNumber <= upperLimit)
    {
        isPrime = true;

        foreach (int prime in primeHash)
        {
            if (testNumber % prime == 0)
            {
                isPrime = false;
                break;
            }
            if (testNumber < prime*prime)
                break;
        }

        if (isPrime)
            primeHash.Add(testNumber);

        testNumber++;
    }

    return primeList;
}

另外还有我用来测试执行时间的(丑陋的)代码:

        Stopwatch stopWatch = new Stopwatch();
        int iterations = 1;
        HashSet<int> hashSet = new HashSet<int>();
        List<int> list = new List<int>();

        stopWatch.Restart();
        for (int i = 0; i < iterations; i++)
        {
            hashSet = Prime.AllPrimesUnder_Hash(1000000);
        }
        stopWatch.Stop();

        Console.WriteLine("Hash: " + (stopWatch.Elapsed.TotalSeconds / iterations).ToString("#.###################"));

////////////////////////

        stopWatch.Restart();
        for (int i = 0; i < iterations; i++)
        {
            hashSet = new HashSet<int>(Prime.AllPrimesUnder(1000000));
        }
        stopWatch.Stop();


        Console.WriteLine("List converted: " + (stopWatch.Elapsed.TotalSeconds / iterations).ToString("#.###################"));

最佳答案

原因是当 HashSet 用集合初始化时,它可以使用集合的大小来设置容量。向空的 HashSet 添加值时,需要不时增加容量,这是一个 O(n) 操作。
出于某种原因,HashSet 不像List 那样将容量作为构造函数中的参数。

关于c# - 在 C# 中,为什么从 List 创建 HashSet 比从 HashSet 开始更快?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/18987841/

相关文章:

c# - 使用 REGEXP 在 C# 中使用 Sqlite

c# - 对象创建,如何解析 "class-owner"?

c# - 如何将 48bpp 图像的原始数据加载到位图中?

java - 从 .Net 使用 Java Web 服务

.net - 如何查看 JIT 生成的代码

java - JSTL <c :forEach items ="${......" what are the type restrictions

c# - 在 C# 中异步总是异步的吗?

c# - 如何在 Web API 2 Controller 中放置多个 GET 方法?

Java哈希集不删除它包含的节点

c# - HashSet.IsSuperSetOf 和 IsProperSuperSetOf 之间的区别?