Как эффективно выбрать случайный элемент из 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 (или просто скопируйте набор в вектор по необходимости) и случайным образом выберите из этого.

другой подход, как видно из комментария, состоит в том, чтобы сохранить вектор итераторов в наборе (они недействительны только при удалении элемента для setS) и случайным образом выберите итератор.

наконец, если вам не нужен набор на основе дерева, вы можете использовать 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).