Как эффективно выбрать случайный элемент из std:: set
как я могу эффективно выбрать случайный элемент из std::set
?
A std::set::iterator
is не итератор произвольного доступа. Поэтому я не могу напрямую индексировать случайно выбранный элемент, как я мог бы для std::deque
или std::vector
Я мог бы взять iterator, возвращенный из std::set::begin()
и наоборот 0
to std::set::size()-1
раз, но это, кажется, делает много ненужной работы. Для "индекса", близкого к размеру набора, я бы в конечном итоге пересек вся первая половина дерева, хотя уже известно, что элемент там не будет найден.
есть ли лучший подход?
во имя эффективности, я готов определить "случайный" как меньше случайных чем любой подход, который я мог бы использовать для выбора случайного индекса в векторе. Назовите это "разумно случайным".
правка...
многие проницательные ответы ниже.
короткий версия заключается в том, что, хотя вы можете найти конкретные элемент log (n) времени, вы не можете найти произвольные элемент в то время через std::set
интерфейс.
6 ответов
использовать boost::container::flat_set
вместо:
boost::container::flat_set<int> set;
// ...
auto it = set.begin() + rand() % set.size();
вставки и удаления становятся O(N), хотя я не знаю, если это проблема. У вас все еще есть o(log N) lookups, и тот факт, что контейнер смежен, дает общее улучшение, которое часто перевешивает потерю o(log N) вставок и удалений.
как насчет предиката для find
(или lower_bound
) что вызывает случайный обход дерева? Вы должны были бы сказать ему размер набора, чтобы он мог оценить высоту дерева и иногда заканчиваться перед узлами листа.
Edit: я понял, что проблема в том, что std::lower_bound
принимает предикат, но не имеет никакого древовидного поведения (внутренне он использует std::advance
, которая обсуждается в комментариях другого ответа). std::set<>::lower_bound
использует предикат, который не может быть случайным, и все-таки установили, как поведение.
Аха, вы не можете использовать другой предикат, но вы можете использовать изменяемый предикат. С std::set
передает объект предиката по значению, которое вы должны использовать predicate &
как предикат, так что вы можете достичь и изменить его (установив его в режим "выборочно").
вот квази-рабочий пример. К сожалению, я не могу обернуть свой мозг вокруг правильного случайного предиката, поэтому моя случайность не превосходна, но Я уверен, что кто-то может понять это:
#include <iostream>
#include <set>
#include <stdlib.h>
#include <time.h>
using namespace std;
template <typename T>
struct RandomPredicate {
RandomPredicate() : size(0), randomize(false) { }
bool operator () (const T& a, const T& b) {
if (!randomize)
return a < b;
int r = rand();
if (size == 0)
return false;
else if (r % size == 0) {
size = 0;
return false;
} else {
size /= 2;
return r & 1;
}
}
size_t size;
bool randomize;
};
int main()
{
srand(time(0));
RandomPredicate<int> pred;
set<int, RandomPredicate<int> & > s(pred);
for (int i = 0; i < 100; ++i)
s.insert(i);
pred.randomize = true;
for (int i = 0; i < 100; ++i) {
pred.size = s.size();
set<int, RandomPredicate<int> >::iterator it = s.lower_bound(0);
cout << *it << endl;
}
}
мой наполовину испеченный тест случайности ./demo | sort -u | wc -l
чтобы увидеть, сколько уникальных целых чисел я получаю. С большим набором образцов попробуйте ./demo | sort | uniq -c | sort -n
искать ненужные шаблоны.
Если вы можете получить доступ к базовому красно-черному дереву (при условии, что оно существует), тогда вы можете получить доступ к случайному узлу в O(log n), выбрав L/R как последовательные биты a ceil(log2(n))
-битное случайное число. Однако вы не можете, поскольку базовая структура данных не предоставляется стандартом.
решение Xeo о размещении итераторов в векторе-это O (n) время и пространство для настройки, но амортизированная константа в целом. Это выгодно отличается от std::next
, что равно O(n) время.
можно использовать std::advance
способ:
set <int> myset;
//insert some elements into myset
int rnd = rand() % myset.size();
set <int> :: const_iterator it(myset.begin());
advance(it, rnd);
//now 'it' points to your random element
другой способ сделать это, вероятно, менее случайные:
int mini = *myset().begin(), maxi = *myset().rbegin();
int rnd = rand() % (maxi - mini + 1) + mini;
int rndresult = *myset.lower_bound(rnd);
если набор не обновляется часто или вам не нужно часто запускать этот алгоритм, сохраните зеркальную копию данных в vector
(или просто скопируйте набор в вектор по необходимости) и случайным образом выберите из этого.
другой подход, как видно из комментария, состоит в том, чтобы сохранить вектор итераторов в наборе (они недействительны только при удалении элемента для set
S) и случайным образом выберите итератор.
наконец, если вам не нужен набор на основе дерева, вы можете использовать vector
или deque
как ваш базовый контейнер и сортировка / уникальный-ify при необходимости.
вы можете сделать это, поддерживая нормальный массив значений; когда вы вставляете в набор, вы добавляете элемент в конец массива (O (1)), затем, когда вы хотите сгенерировать случайное число, вы можете захватить его из массива в O (1) как хорошо.
проблема возникает, когда вы хотите удалить элементы из массива. Самый наивный метод взял бы O (n), что может быть достаточно эффективным для ваших нужд. Однако, это можно улучшить к O (log n) использовать следующий метод;
Keep, для каждого индекса i
в массиве prfx[i]
, который представляет количество не удаленных элементов в диапазоне 0...i
в массиве. Держите дерево сегментов, где вы держите максимум prfx[i]
содержится в каждом диапазоне.
обновление дерева сегментов можно сделать в O (log n) за удаление. Теперь, когда вы хотите получить доступ к случайному числу, вы запрашиваете дерево сегментов, чтобы найти "реальный" индекс число (путем нахождения самого раннего диапазона, в котором максимум prfx
равно случайному индексу). Это делает генерацию случайных чисел сложности O (log n).