Зачем нужны коллекции?алгоритм shuffle() работает лучше, чем моя реализация [дубликат]
этот вопрос уже есть ответ здесь:
Collections.shuffle()
проходит через каждый индекс Collection
назад, а затем меняет его на случайный индекс в том числе и перед. Мне было интересно, Почему, поэтому я попытался сделать то же самое, но поменялся с любой индекс random в Collection
.
вот перетасовка части коллекций.перетасовать() код:
for (int i=size; i>1; i--)
swap(arr, i-1, rnd.nextInt(i));
и вот мой алгоритм:
Random r = new Random();
for (int i = 0; i < a.size(); i++) {
int index = r.nextInt(a.size());
int temp = a.get(i);
a.set(i, a.get(index));
a.set(index, temp);
}
Я нашел это Collections.shuffle()
был гораздо более равномерно распределен, чем мой код, когда я запускал оба на одном и том же ArrayList
миллион раз. Кроме того, при запуске моего кода на:
[0, 1, 2, 3, 4]
кажется, что последовательно происходят следующие перестановки чаще всего:
[1, 0, 3, 4, 2]
[1, 2, 3, 4, 0]
[1, 2, 0, 4, 3]
[0, 2, 3, 4, 1]
[1, 2, 3, 0, 4]
может кто-нибудь объяснить, почему?
2 ответов
Collections.Shuffle()
тут Фишер-Йейтс перемешать. Это более равномерно распределенная форма перетасовки и не перетасовывает то, что ранее уже было перетасовано, в отличие от вашего алгоритма.
что делает ваш алгоритм (также известный как наивная реализация) заключается в том, что он будет случайным образом выбирать любой индекс массива и перетасовывать его, что означает высокую вероятность того, что он выберет тот же индекс, который ранее был перетасован уже.
Фишер-Йейтс перемешать (также известный как Donald Knuth Shuffle) - это беспристрастный алгоритм, который перетасовывает элементы в массиве с одинаковой вероятностью. Он избегает шанса, если он "перемещает" одни и те же объекты дважды.
вот хорошее объяснение Фишера Йейтса Shuffle наш собственный Джефф Этвуд в кодировании ужаса, он обсуждает наивную реализацию случайного перетасовки против Фишера Йейтса перетасовки.
см. Также этот вопрос SO о реализации Java. в нем упоминается то, что вы спросили. вы также можете посмотреть исходный код, если хотите, как упоминалось там. Я нашел его в Googling Collections.shuffle()
в топ-5 ссылок.
чтобы обсудить это в будущем, всегда полезно использовать перетасовку Фишера-Йейтса по сравнению с наивными реализациями, особенно в реализациях, которые требуют более высокого уровня случайности (например, перетасовка покерных карт), чтобы избежать введение коэффициентов и несправедливой игры. Было бы нехорошо, если бы азартные игры, где реализованы на основе нашей наивной реализации,как перекос приводит к тому, что вы наблюдали, где одна и та же перестановка, кажется, появляется чаще, чем другие.
и наконец, как упоминал пользователь @jmruc, вот очень хороший учебник по визуализации алгоритмов, он содержит Fisher-Yates shuffle, а также другие алгоритмы, все красиво представлены. Могут помочь вам оберните голову вокруг концепций, если вы больше визуализатор:визуализация алгоритмов Майка Бостока
это еще одно объяснение Фишера-Йейтса.
рассмотрим следующий способ:
- есть два списка, A и B. первоначально все элементы находятся на список A, поэтому список B пуст.
-
на каждом шагу:
Выберите с равномерной вероятностью из элементов в настоящее время в списке A.
перестановка списка A, чтобы сделать выбранный элемент последним элементом.
удалить последний элемент из списка и добавить его в список Б.
- когда список a пуст, верните список B.
я считаю, что этот алгоритм легко понять и визуализировать.
вероятность того, что данный элемент будет выбран на первом шаге 1/n
. Вероятность того, что данный элемент будет выбран на втором шаге, - это вероятность того, что он не будет выбран на первом шаге,(n-1)/n
, умножает вероятность его выбора на втором шаге, учитывая, что он все еще находится в списке A,1/(n-1)
. Что продукт 1/n
.
аналогично, он имеет вероятность ((n-1)/n)*((n-2)/(n-1)) = (n-2)/n
по-прежнему находится в списке A после того, как два элемента были перемещены, и, следовательно, a 1/n
вероятность быть третьим выбранным элементом.
В общем, вероятность все еще быть в списке a после k
элементы были выбраны это ((n-1)/n)*((n-2)/(n-1))*...*((n-k)/(n-k+1)) = (n-k)/n
. Вероятность выбора на следующем шаге, учитывая, что элемент все еще находится в списке A, равна 1/(n-k)
, поэтому безусловная вероятность выбирается на шаге, когда список A имеет (n-k)
элементов ((n-k)/n)*(1/(n-k)) = 1/n
.
Fisher-Yates - это только этот алгоритм с двумя списками, общая длина которых всегда n
, объединенный в один массив. На каждом шаге он выбирает элемент из списка A с равномерной вероятностью, переставляет список A, чтобы поместить этот элемент рядом со списком B, а затем перемещает границу так, чтобы она изменилась с элемента списка A на последний добавленный элемент списка B.