Проблемы временной сложности с Multimap

Я создал программу, которая находит медиану списка чисел. Список чисел является динамическим в том смысле, что числа можно удалять и вставлять (можно вводить повторяющиеся числа), и в течение этого времени новая медиана повторно оценивается и распечатывается.

Я создал эту программу, используя мультикарту, потому что

1) преимущество в том, что он уже отсортирован,
2) простота вставки, удаления, поиска (поскольку multimap реализует бинарный поиск)
3) разрешены повторяющиеся записи.

Ограничения на количество записей + удалений (представленных как N): 0 ‹N‹ = 100 000.

Программа, которую я написал, работает и распечатывает правильную медиану, но не достаточно быстро. Я знаю, что unsorted_multimap быстрее, чем multimap, но тогда проблема с unsorted_multimap в том, что мне придется его отсортировать. Мне нужно отсортировать его, потому что для нахождения медианы вам нужен отсортированный список. Итак, мой вопрос: будет ли практично использовать unsorted_multimap, а затем быстро отсортировать записи, или это будет просто смешно? Не было бы быстрее просто использовать вектор, быстро отсортировать вектор и использовать двоичный поиск? Или, может быть, я забываю какое-то потрясающее решение, о котором даже не думал.

Хотя я не новичок в C ++, я должен признать, что мои навыки работы с временной сложностью несколько полезны.


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


person user1066524    schedule 14.03.2013    source источник
comment
Я не думаю, что карта является хорошей структурой данных для этой проблемы. Лучшая производительность, вероятно, будет исходить от вектора, хотя я не сравнивал их.   -  person evanmcdonnal    schedule 14.03.2013
comment
@evanmcdonnal, думаю, ты прав. Я думаю, что вектор может быть таким же быстрым с быстрой сортировкой и двоичным поиском.   -  person user1066524    schedule 14.03.2013
comment
Я думаю, что этот вопрос дублирует: stackoverflow.com/questions/1387497/   -  person Jose Luis Blanco    schedule 15.03.2013
comment
@JoseLuisBlanco: Это не совсем так, потому что набор может сжиматься.   -  person ipc    schedule 15.03.2013
comment
ну, вектор был быстрее, чем мульти-карта, но все равно недостаточно быстро ... что странно. возможно я замедляю это каким-то другим способом.   -  person user1066524    schedule 15.03.2013


Ответы (4)


Если ваша цель - отслеживать медианное значение на лету, когда элементы вставляются / удаляются, вы должны использовать min-heap и max-heap. Каждый из них будет содержать половину элементов ... Пару дней назад был связанный с этим вопрос: Как реализовать медианную кучу

Хотя, если вам нужно искать определенные значения, чтобы удалить элементы, вам все равно понадобится какая-то карта.

Вы сказали, что это медленно. Вы выполняете итерацию от начала карты до элемента (N / 2) каждый раз, когда вам нужна медиана? Тебе это не нужно. Вы можете отслеживать медиану, постоянно указывая на него итератор, а также счетчик количества элементов, меньших этого. Каждый раз, когда вы вставляете / удаляете, сравнивайте новый / старый элемент со средним значением и обновляйте итератор и счетчик.

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

Если вам нужна медиана всего несколько раз, вы можете использовать алгоритм «выбора». Это описано в книге Седжвика. В среднем это занимает O (n) времени. Это похоже на быструю сортировку, но не выполняет сортировку полностью. Он просто разбивает массив на части со случайными поворотами, пока, в конце концов, он не «выберет» с одной стороны m меньших элементов (m = (n + 1) / 2). Затем вы ищете наибольший из этих m элементов, и это медиана.

person comocomocomocomo    schedule 14.03.2013

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

Если у вас мало обновлений - используйте несортированный std :: vector + std :: nth_element алгоритм, который является O (N). Вам не нужна полная сортировка, которая составляет O (N * ln (N)).

живая демонстрация nth_element:

#include <algorithm>
#include <iterator>
#include <iostream>
#include <ostream>
#include <vector>

using namespace std;

template<typename RandomAccessIterator>
RandomAccessIterator median(RandomAccessIterator first,RandomAccessIterator last)
{
   RandomAccessIterator m = first + distance(first,last)/2; // handle even middle if needed
   nth_element(first,m,last);
   return m;
}

int main()
{
   vector<int> values = {5,1,2,4,3};
   cout << *median(begin(values),end(values)) << endl;
}

Выход:

3

Если у вас много обновлений и вы удаляете их только из середины, используйте две кучи, как comocomocomocomo предлагает. Если бы вы использовали fibonacci_heap - тогда вы также получите удаление O (N) из произвольной позиции (если у вас нет дескриптора).

Если у вас много обновлений и вам нужно удалить O (ln (N)) из произвольных мест - тогда используйте два мультимножества как ipc предлагает .

person Evgeny Panasyuk    schedule 14.03.2013

Вот как это можно реализовать в O(log N) за обновление:

template <typename T>
class median_set {
public:
  std::multiset<T> below, above;

  // O(log N)
  void rebalance()
  {
    int diff = above.size() - below.size();
    if (diff > 0) {
      below.insert(*above.begin());
      above.erase(above.begin());
    } else if (diff < -1) {
      above.insert(*below.rbegin());
      below.erase(below.find(*below.rbegin()));
    }
  }

public:
  // O(1)
  bool empty() const { return below.empty() && above.empty(); }

  // O(1)
  T const& median() const
  {
    assert(!empty());
    return *below.rbegin();
  }

  // O(log N)
  void insert(T const& value)
  {
    if (!empty() && value > median())
      above.insert(value);
    else
      below.insert(value);
    rebalance();
  }

  // O(log N)
  void erase(T const& value)
  {
    if (value > median())
      above.erase(above.find(value));
    else
      below.erase(below.find(value));
    rebalance();
  }
};

(Работа с тестами в действии)

Идея такая:

  • Следите за значениями выше и ниже медианы в двух наборах
  • Если добавлено новое значение, добавьте его в соответствующий набор. Всегда следите за тем, чтобы в нижеследующем наборе ровно на 0 или 1 больше, чем в другом.
  • Если значение удалено, удалите его из набора и убедитесь, что условие все еще выполняется.

Вы не можете использовать priority_queues, потому что они не позволят вам удалить один элемент.

person ipc    schedule 14.03.2013
comment
Вы не можете использовать priority_queues, потому что они не позволят вам удалить один элемент. - fibonacci_heap поддерживает стирание элементов. Это операции: верх - O (1), нажатие - O (1), стирание - O (ln (N)). Но поиск элемента, который нужно стереть (если у него нет дескриптора), составляет O (N), поэтому стирание будет O (N). boost.org/doc/libs/1_53_0/doc/ html / heap / data_structures.html - person Evgeny Panasyuk; 15.03.2013

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

person Steve    schedule 14.03.2013
comment
вектор был быстрее, чем мульти-карта, но все еще недостаточно быстр. Я использую быструю сортировку и бинарный поиск. - person user1066524; 15.03.2013