Найти самый большой и второй по величине элемент в диапазоне - PullRequest
6 голосов
/ 11 сентября 2009

Как мне найти вышеупомянутое без удаления самого большого элемента и повторного поиска? Есть ли более эффективный способ сделать это? Не имеет значения, являются ли эти элементы дубликатами.

Ответы [ 11 ]

21 голосов
/ 11 сентября 2009

с использованием partal_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];
20 голосов
/ 11 сентября 2009
for (e: all elements) {
 if (e > largest) {
   second = largest;
   largest = e;
 } else if (e > second) {
   second = e;
 }
}

Вы можете инициализировать largest и second соответствующей нижней границей или первыми двумя элементами в списке (проверьте, какой из них больше, и не забудьте проверить, есть ли в списке хотя бы два товар)

7 голосов
/ 11 сентября 2009

nth_element(begin, begin+n,end,Compare) помещает элемент, который будет nth (где «first» равно «0th»), если диапазон [begin, end) отсортирован в позиции begin+n, и гарантирует, что все из [begin,begin+n) появится до nth элемент в отсортированном списке. Итак, код, который вы хотите:

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

Это будет хорошо работать в вашем случае, так как вы ищете только два крупнейших. Предполагая, что ваш assignCompare сортирует вещи от наибольшего к наименьшему, второй по величине элемент будет с позицией 1, а самый большой будет с позицией 0.

2 голосов
/ 11 сентября 2009

Оптимальный алгоритм не должен требовать более 1,5 * N - 2 сравнений. (Как только мы решили, что это O (n), какой коэффициент перед N? 2 * N сравнениями меньше оптимального).

Итак, сначала определите «победителя» и «проигравшего» в каждой паре - это 0,5 * N сравнений.

Затем определите самый большой элемент, сравнивая победителей - это еще 0,5 * N - 1 сравнение.

Затем определите второй по величине элемент, сравнив проигравшего в паре, из которого был получен самый большой элемент, с победителями всех других пар - еще 0,5 * N - 1 сравнение.

Всего сравнений = 1,5 Н - 2.

2 голосов
/ 11 сентября 2009

Предположим, вы хотите найти два самых больших уникальных значения в списке.

Если список уже отсортирован, просто посмотрите на второй последний элемент (или, скорее, итерируйте с конца, ища второе последнее значение).

Если список не отсортирован, не пытайтесь его отсортировать. Сортировка в лучшем случае 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;
   }

Конечно, есть и другие критерии, и все они могут быть проверены внутри цикла. Однако, если вы имеете в виду, что должны быть найдены два элемента, оба из которых имеют одинаковое наибольшее значение, вы должны рассмотреть, что произойдет, если все три или более элемента имеют это наибольшее значение, или если два или более элемента имеют второе по величине значение.

1 голос
/ 11 сентября 2009

Ответ зависит от того, хотите ли вы только значения или итераторы, указывающие на значения.

Незначительная модификация ответа @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;
   }
}
0 голосов
/ 26 сентября 2009

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 multiset value_types и списком тех узлов (в которых обычно ничего нет, так как другие k используются в мультимножестве до тех пор, пока не наступит время для установки нового один в и мы стираем, а затем сразу же многократно используем его. Хорошо иметь эту или другую память, выделяемую / освобождаемую в std :: multiset и хрень строки кэша убивает тебя. Это (очень) крошечная работа, чтобы придать ей статическое состояние без нарушения правил распределителя STL.

Не так хорошо, как специализированный алгоритм для ровно 2, но для фиксированного k<<n, я бы УГОВОРИЛ (2n + дельта * n), где дельта мала - мой DEK ACP vol3 S & S упакован, а оценка дельты немного больше работы, которую я хочу сделать.

среднее худшее, я бы догадался, n (log (k-1) + 2), когда в противоположном порядке и все различно.

best - 2n + k (log k) для k, являющегося первым

0 голосов
/ 12 сентября 2009

Если наибольшим является первый элемент, найдите второй по величине в [наибольшее + 1, конец). В противном случае ищите в [begin, large) и [large + 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 .

0 голосов
/ 11 сентября 2009

Не проверено, но весело:

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;
}
0 голосов
/ 11 сентября 2009

Можно отсканировать список за один проход и сохранить 1-е и 2-е значения, которые имеют эффективность O (n), а сортировка - O (n log n).

EDIT:
Я думаю, что частичная сортировка O (n log k)

...