【问题标题】:Is adding to a set O(n)?是否添加到集合 O(n)?
【发布时间】:2014-04-03 14:54:28
【问题描述】:

由于集合只能具有唯一值,这是否意味着每次将元素添加到集合时,它必须检查它是否等于那里的每个元素,因此是 O(n)?

如果在这种情况下,这会使它们比 arrayLists 慢得多,那么您真正应该使用它们的唯一时间是确保您的元素都是唯一的,或者它们还有其他优势吗?

【问题讨论】:

  • 哪种语言和哪种容器? std::set 有一个插入方法,它需要 O(log(size()))。
  • 这取决于set 的实现。 C++ 中的std::set 通常实现为红黑树,并保证插入复杂度为 O(log(n)) (source)。
  • 这取决于Set的实现。在 Java 中的每个示例中,将元素添加到 HashSet 是O(1)(假设您的哈希函数均匀地分布您的元素)。但是在 TreeSet 中它是 O(log(n))
  • 你可以在这里查看实现类:docs.oracle.com/javase/7/docs/api/java/util/Set.html

标签: java c++ data-structures set


【解决方案1】:

这取决于set 的实现。

C++

C++ 中的std::set 通常实现为红黑树,并保证插入复杂度为 O(log(n)) (source)。

std::set 是一个关联容器,它包含一组有序的 Key 类型的唯一对象。排序是使用键比较功能比较完成的。搜索、删除和插入操作具有对数复杂度。

C++11 的 std::unordered_set 的插入复杂度为 O(1) (source)。

无序集是一个关联容器,其中包含一组 Key 类型的唯一对象。搜索、插入和删除具有平均恒定时间复杂度。

JAVA

在 Java 中,将元素添加到 HashSet 是 O(1)。来自documentation

这个类为基本操作(添加、删除、包含和大小)提供恒定的时间性能,假设哈希函数将元素正确地分散在桶中

将元素插入TreeSet 是 O(log(n))。

此实现为基本操作(添加、删除和包含)提供有保证的 log(n) 时间成本。

所有实现Set 的类都可以在documentation 中找到。

结论

在大多数情况下,添加到 set 并不比添加到 ArrayListstd::vector 慢。但是,set 不一定按照插入的顺序保留项目。此外,访问集合的某些 Nth 元素的复杂性比对 ArrayListstd::vector 的相同操作更糟糕。各有优缺点,应相应使用。

【讨论】:

  • 添加到 java HashSet 是 amortized O(1)
【解决方案2】:

你标记了这个 Java 和 C++,所以我会回答这两个:

在 C++ 中,std::set 是一个有序容器,很可能实现为一棵树。无论实现如何添加到集合并检查集合中的元素是否保证为 O(log n)。对于 std::unordered_set,这是 C++11 中的新功能,这些操作是 O(1)(给定一个适当的散列函数)。

在 Java 中,java.util.Set 是一个接口,可以有许多不同的类来实现它们。操作的复杂性取决于这些类。最常用的集合是TreeSetHashSet。前者的运算是 O(log n),后者的运算是 O(1)(同样,给出一个适当的散列函数)。

【讨论】:

    【解决方案3】:

    C++ std::set 通常实现为黑红树。这意味着添加将是 O(log n)。

    C++ std::unordered_set 插入被实现为哈希表,因此插入是 O(1)。

    【讨论】:

      【解决方案4】:

      您忘记了集合可能不是大量元素列表;它可以被安排(确实如此)搜索速度比O(N) 快得多。

      http://en.wikipedia.org/wiki/Set_(abstract_data_type)#Implementations

      【讨论】:

        【解决方案5】:

        这取决于。不同的语言提供不同的实现。甚至 java 也有 2 个不同的集合:TreeSet 和 HashSet。添加一个 TreeSet 是 O(logn),因为元素已经按顺序排列。

        【讨论】:

          【解决方案6】:

          在 cpp 中,集合通常实现为二叉搜索树。话虽如此,插入将需要 O(log(N)) 时间复杂度。对于唯一键,你可以试试cpp中的hash_map,插入时的时间复杂度是恒定的。

          【讨论】:

            【解决方案7】:

            时间复杂度:包装类与基元。

            • 当值更改时,尤其是多次更改时,基元会提供更好的时间。

            例子:

               int counter = 0; 
                 while (x>y){ 
                 counter++; 
                 }
            

            比:

            快得多
            Integer counter = 0; 
             while (x>y){ 
             counter++; 
             }
            
            • 当值保持不变时,包装类提供更好的时间,因为只有一个指向包装类的指针被传递给算法。它在定义不改变其值的方法的参数时非常方便。

            例子:

             public int sum (Integer one, Integer two, Integer three){ 
             int sum = one+two+three; 
             return sum; 
             }
            

            public int sum (int one, int two, int three){ 
             int sum = one+two+three;
             return sum; 
             }
            

            传递给方法的值可以是原始的,重要的是方法本身的参数定义,也就是说:

             public int sum (Integer one, Integer two, Integer three){ 
             int sum = one+two+three;                                               
             return sum; 
             } 
            
             int a = 1; int b = 2; int c = 3;                                       
            
             public int sum (a, b, c){ 
             int sum = a+b+c; 
             return sum; 
             }
            

            使用上述包装类的累积效应可以显着提高程序的性能。

            【讨论】:

              【解决方案8】:

              正如其他人所说,集合通常是树 O(logn) 或哈希表 O(1)。但是,您可以确定一件事:任何合理的地图实现都不会有 O(n) 行为。

              【讨论】:

              猜你喜欢
              • 1970-01-01
              • 1970-01-01
              • 2020-08-05
              • 1970-01-01
              • 2019-06-17
              • 2011-01-25
              • 2021-07-03
              • 1970-01-01
              • 1970-01-01
              相关资源
              最近更新 更多