【问题标题】:Find largest and second largest element in a range查找范围内的最大和第二大元素
【发布时间】:2010-11-27 14:50:38
【问题描述】:

如何在不删除最大元素并再次搜索的情况下找到上述内容?有没有更有效的方法来做到这一点?这些元素是否重复并不重要。

【问题讨论】:

    标签: c++ sorting stl ranking visual-c++-2005


    【解决方案1】:

    从 n..m 创建一个子列表,按降序排序。然后抓住前两个元素。从原始列表中删除这些元素。

    【讨论】:

      【解决方案2】:

      使用partial_sort ?

      std::partial_sort(aTest.begin(), aTest.begin() + 2, aTest.end(), Functor);
      

      一个例子:

      std::vector<int> aTest;
      
          aTest.push_back(3);
          aTest.push_back(2);
          aTest.push_back(4);
          aTest.push_back(1);
      
      
          std::partial_sort(aTest.begin(), aTest.begin()+2,aTest.end(), std::greater<int>());
      
          int Max = aTest[0];
      int SecMax = aTest[1];
      

      【讨论】:

      • +1 我不知道 partial_Sort。但是如果他想保持原始列表的顺序,则不起作用。
      • 你可以使用 partial_sort_copy
      • 如果你正在流式传输大量值,比如来自 input_iterator 并且不想存储整个范围,只存储顶部元素,那么堆操作 -- make_heap、push_heap和 sort_heap -- 是要走的路。
      • partial_sort 具有在只需要对前 n 个元素进行排序时停止排序的特殊能力。 (第二个论点)
      • 来自 Josuttis --- Partial_sort() 在任何情况下都保证 n * log(n) 复杂性。通常在线性和 n-log-n 之间(大约 numberOfElements*log(numberOfSortedElements) 比较)。
      【解决方案3】:

      假设您的意思是在列表中找到两个最大的唯一值。

      如果列表已经排序,则只需查看倒数第二个元素(或者更确切地说,从末尾迭代以查找倒数第二个值)。

      如果列表未排序,则不必费心对其进行排序。排序最多为 O(n lg n)。简单的线性迭代是 O(n),所以只需循环跟踪跟踪的元素:

      v::value_type second_best = 0, best = 0;
      for(v::const_iterator i=v.begin(); i!=v.end(); ++i)
         if(*i > best) {
           second_best = best;
           best = *i;
         } else if(*i > second_best) {
           second_best = *i;
         }
      

      当然还有其他的标准,这些都可以放在循环内的测试中。但是,如果您的意思是应该找到两个具有相同最大值的元素,您必须考虑如果三个或更多元素都具有该最大值,或者如果两个或更多元素具有第二大值,会发生什么情况。

      【讨论】:

      • 除了这个不处理列表中的重复项
      • 如果 second_best 需要一个 else 块
      • 我不需要找到唯一值,重复就可以了 - 我已经在问题中更新了这个。
      【解决方案4】:
      for (e: all elements) {
       if (e > largest) {
         second = largest;
         largest = e;
       } else if (e > second) {
         second = e;
       }
      }
      

      您可以将largest 和second 初始化为适当的下限,或者初始化列表中的前两项(检查哪一项更大,不要忘记检查列表是否至少有两项项目)

      【讨论】:

      • 简单的代码......你还能要求什么?诚然,搜索不那么“复杂”,但由于内存访问的乐趣,几乎可以肯定会更慢。
      • 按定义排序比较慢,因为你必须检查元素至少 O(n log n) 次。这保证只检查元素 O(n) 次(其中 n 当然是#elements)。只有在同一个列表上重复该操作时,排序才会变得更有效率。
      • 这是我最终实现的(应该可能在 OP 中提到过),但我正在寻找不同的东西(可能更快)。
      • 排序可以用更少的行完成,并且在已经排序的列表上检索最大/最小元素非常有效,但是您必须预先支付更大的成本才能对其进行排序。但是,如果您只搜索一组(在本例中为 2)数量最大的项目,这可能是最有效的解决方案。
      【解决方案5】:

      您可以一次扫描列表并保存第一个和第二个值,其效率为 O(n),而排序为 O(n log n)。

      编辑:
      我认为部分排序是 O(n log k)

      【讨论】:

        【解决方案6】:

        答案取决于您是只需要值,还是需要指向值的迭代器。

        @will 回答的细微修改。

        v::value_type second_best = 0, best = 0;
        for(v::const_iterator i=v.begin(); i!=v.end(); ++i)
        {
           if(*i > best)
           {
             second_best = best;
             best = *i;
           }
           else if (*i > second_best)
           {
             second_best = *i;
           }
        }
        

        【讨论】:

          【解决方案7】:

          nth_element(begin, begin+n,end,Compare) 如果[begin, end) 的范围在begin+n 处排序,则放置第n 个元素(其中“first”是“0th”),并确保[begin,begin+n) 中的所有内容都出现在第n 个之前排序列表中的元素。所以你想要的代码是:

          nth_element(container.begin(),
                      container.begin()+1,
                      container.end(),
                      appropriateCompare);
          

          这在您的情况下效果很好,因为您只寻找最大的两个。假设您的适当Compare 从最大到最小对事物进行排序,则位于位置 1 的第二大元素将位于位置 0。

          【讨论】:

            【解决方案8】:

            最佳算法不需要超过 1.5 * N - 2 次比较。 (一旦我们确定它是 O(n),N 前面的系数是多少?2 * N 次比较不是最优的)。

            因此,首先确定每对中的“赢家”和“输家” - 这是 0.5 * N 次比较。

            然后通过比较获胜者来确定最大的元素 - 这又是 0.5 * N - 1 次比较。

            然后通过将最大元素来自的配对的失败者与所有其他配对的获胜者进行比较来确定第二大元素 - 再进行 0.5 * N - 1 次比较。

            总比较次数 = 1.5 N - 2。

            【讨论】:

              【解决方案9】:

              未经测试但很有趣:

              template <typename T, int n>
              class top_n_functor : public unary_function<T, void>
              {
              
                void operator() (const T& x) {
                   auto f = lower_bound(values_.begin(), values_.end(), x);
              
                   if(values_.size() < n) {
                       values_.insert(f, x);
                       return;
                   }
              
                   if(values_.begin() == f)
                        return;
              
                   auto removed = values_.begin();
                   values_.splice(removed, values_, removed+1, f);
              
                   *removed = x;
                }
              
                std::list<T> values() {
                   return values_;
                }
              private:
                 std::list<T> values_;
              };
              
              int main()
              {
                int A[] = {1, 4, 2, 8, 5, 7};
                const int N = sizeof(A) / sizeof(int);
              
                auto vals = for_each(A, A + N, top_n_functor<int,2>()).values();
              
                cout << "The top is " << vals.front()
                     << " with second place being " << *(vals.begin()+1) << endl;
              }
              

              【讨论】:

                【解决方案10】:

                如果最大的是第一个元素,则在 [largest+1,end) 中搜索第二大的元素。否则在 [begin,largest) 和 [largest+1,end) 中搜索并取两者中的最大值。当然,这有 O(2n),所以它不是最优的。

                如果你有随机访问迭代器,你可以像快速排序一样使用优雅的递归:

                template< typename T >
                std::pair<T,T> find_two_largest(const std::pair<T,T>& lhs, const std::pair<T,T>& rhs)
                {
                  // implementation finding the two largest of the four values left as an exercise :) 
                }
                
                template< typename RAIter >
                std::pair< typename std::iterator_traits<RAIter>::value_type
                         , typename std::iterator_traits<RAIter>::value_type > 
                find_two_largest(RAIter begin, RAIter end)
                {
                  const ptr_diff_t diff = end-begin;
                  if( diff < 2 )
                    return std::make_pair(*begin, *begin);
                  if( diff < 3 )
                    return std::make_pair(*begin, *begin+1);
                  const RAIter middle = begin + (diff)/2;
                  typedef std::pair< typename std::iterator_traits<RAIter>::value_type
                                   , typename std::iterator_traits<RAIter>::value_type > 
                    result_t;
                  const result_t left = find_two_largest(begin,middle);
                  const result_t right = find_two_largest(middle,end);
                
                  return find_two_largest(left,right);
                }
                

                这有 O(n) 并且不应该比 NomeN's implementation 进行更多的比较。

                【讨论】:

                  【解决方案11】:

                  top k 通常比 n(log k) 好一点

                   template <class t,class ordering>
                   class TopK {
                   public:
                      typedef std::multiset<t,ordering,special_allocator> BEST_t;
                      BEST_t best;
                      const size_t K;
                      TopK(const size_t k)
                          : K(k){
                      } 
                      const BEST_t& insert(const t& item){
                          if(best.size()<k){
                              best.insert(item);
                              return best;
                          }
                          //k items in multiset now
                          //and here is why its better - because if the distribution is random then
                          //this and comparison above are usually the comparisons that is done; 
                          if(compare(*best.begin(),item){//item better than worst
                             erase(begin());//the worst
                             best.insert(item); //log k-1 average as only k-1 items in best
                          } 
                          return best;
                      } 
                      template <class it>
                      const BEST_t& insert(it i,const it last){
                          for(;i!=last;++i){
                              insert(*i);    
                          }
                          return best;
                      }
                    };
                  

                  当然,special_allocator 本质上可以只是一个 k 多集 value_types 的数组和这些节点的列表(通常没有任何内容,因为其他 k 在多集中使用,直到需要放置一个新的一个进去,我们擦除然后立即重用它。很高兴有这个,否则 std::multiset 中的内存分配/释放和缓存行废话会杀死你。它是一个(非常)微小的工作来给它静态状态不违反 STL 分配器规则。

                  不如正好 2 的专用算法好,但对于固定的 k&lt;&lt;n,我会 GUESS (2n+delta*n),其中 delta 很小 - 我的 DEK ACP vol3 S&S 已打包,对 delta 的估计是我想做更多的工作。

                  平均最糟糕的是我猜 n(log(k-1) + 2) 当顺序相反并且完全不同时。

                  最好是 2n + k(log k),因为 k 最好是第一个

                  【讨论】:

                    猜你喜欢
                    • 2022-01-16
                    • 2021-11-13
                    • 1970-01-01
                    • 2016-03-19
                    • 1970-01-01
                    • 2019-08-28
                    • 1970-01-01
                    • 1970-01-01
                    • 1970-01-01
                    相关资源
                    最近更新 更多