Эффективный способ получить среднее (среднее) значение std:: set?
std::set
- отсортированное дерево. Он предоставляет методы begin
и end
, поэтому я могу получить минимальный и максимальный значения и lower_bound
и upper_bound
для двоичного поиска. Но что, если я хочу, чтобы итератор указывал на средний элемент (или один из них, если там есть четное количество элементов)?
Существует ли эффективный способ (O(log(size))
not O(size)
)?
{1} => 1
{1,2} => 1 or 2
{1,2,3} => 2
{1,2,3,4} => 2 or 3 (but in the same direction from middle as for {1,2})
{1,312,10000,14000,152333} => 10000
PS: Тот же вопрос на русском языке.
Ответы
Ответ 1
В зависимости от того, как часто вы вставляете/удаляете элементы или просматриваете среднюю/медианную, возможно более эффективное решение, чем очевидное, - поддерживать постоянный итератор в среднем элементе и обновлять его всякий раз, когда вы вставляете/удаляете элементы из задавать. Есть куча краевых случаев, которые потребуют обработки (нечетное число против четного количества элементов, удаление среднего элемента, пустой набор и т.д.), Но основная идея заключалась бы в том, что когда вы вставляете элемент, который меньше, чем текущий средний элемент, ваш средний итератор может нуждаться в декрементах, тогда как если вы вставляете большую, вам нужно увеличивать. Это наоборот для удаления.
При времени поиска это, конечно, O (1), но также имеет существенно O (1) стоимость при каждой вставке/делеции, то есть O (N) после N вставок, которая должна быть амортизирована на достаточном количество поисковых запросов, чтобы сделать его более эффективным, чем принудительное принуждение.
Ответ 2
Это будет O (размер), чтобы получить середину двоичного дерева поиска. Вы можете получить его с помощью std::advance()
следующим образом:
std::set<int>::iterator it = s.begin();
std::advance(it, s.size() / 2);
Ответ 3
Имейте в виду, что std::set
НЕ хранит повторяющиеся значения. Если вы вставите следующие значения {1, 2, 3, 3, 3, 3, 3, 3, 3}
, медиана, которую вы получите, будет 2
.
std::set<int>::iterator it = s.begin();
std::advance(it, s.size() / 2);
int median = *it;
Если вы хотите включить дубликаты при рассмотрении медианы, вы можете использовать std::multiset
(медиана {1, 2, 3, 3, 3, 3, 3, 3, 3}
будет равна 3
):
std::multiset<int>::iterator it = s.begin();
std::advance(it, s.size() / 2);
int median = *it;
Если единственная причина, по которой вы хотите отсортировать данные, это получить медиану, std::sort
на мой взгляд, вам лучше использовать простой старый std::vector
+ std::sort
.
С большим тестовым образцом и несколькими итерациями я выполнил тест за 5 секунд с помощью std::vector
и std::sort
и с 13 по 15 с использованием std::set
или std::multiset
. Ваш пробег может варьироваться в зависимости от размера и количества повторяющихся значений.
Ответ 4
Это предложение является чистой магией и потерпит неудачу, если будут какие-то дубликаты
В зависимости от того, как часто вы вставляете/удаляете элементы по сравнению с поиском среднего/медианы, возможно, более эффективным решением, чем очевидное, является сохранение постоянного итератора для среднего элемента и его обновление всякий раз, когда вы вставляете/удаляете элементы из набора. Существует множество крайних случаев, которые необходимо обработать (нечетное или четное количество элементов, удаление среднего элемента, пустой набор и т.д.), Но основная идея заключается в том, что при вставке элемента, который меньше текущего среднего элемента ваш средний итератор может нуждаться в уменьшении, тогда как если вы вставляете больший итератор, вам нужно увеличивать его. Это наоборот для переездов.
Предложения
- Первое предложение заключается в использовании std :: multiset вместо std :: set, чтобы он мог хорошо работать, когда элементы могут быть продублированы.
- Я предлагаю использовать 2 мультимножества, чтобы отслеживать меньшее зелье и большее зелье и балансировать между ними
Алгоритм
1. сохранить наборы сбалансированными, чтобы size_of_small == size_of_big или size_of_small + 1 == size_of_big
void balance(multiset<int> &small, multiset<int> &big)
{
while (true)
{
int ssmall = small.size();
int sbig = big.size();
if (ssmall == sbig || ssmall + 1 == sbig) break; // OK
if (ssmall < sbig)
{
// big to small
auto v = big.begin();
small.emplace(*v);
big.erase(v);
}
else
{
// small to big
auto v = small.end();
--v;
big.emplace(*v);
small.erase(v);
}
}
}
2. если наборы сбалансированы, средний элемент всегда является первым элементом в большом наборе
auto medium = big.begin();
cout << *medium << endl;
3. будьте осторожны при добавлении нового элемента
auto v = big.begin();
if (v != big.end() && new_item > *v)
big.emplace(new_item );
else
small.emplace(new_item );
balance(small, big);
сложность объяснила
- это O (1), чтобы найти среднее значение
- добавить новый предмет занимает O (n)
- вы все еще можете искать предмет в O (log n), но вам нужно искать 2 комплекта
Ответ 5
Если ваши данные являются статическими, то вы можете его заранее определить и не вставлять новые элементы - проще использовать вектор, сортировать его и получать доступ к медианному только по индексу в O (1)
vector<int> data;
// fill data
std::sort(data.begin(), data.end());
auto median = data[data.size() / 2];