Як здійснити зважене перетасовку

Нещодавно я написав деякий код, який, на мій погляд, був дуже неефективним, але оскільки він містив лише кілька значень, я прийняв його. Однак мене все-таки цікавить кращий алгоритм для наступного:

зважене

  1. Список X об'єктів, кожному з них присвоєна "вага"
  2. Підсумуйте ваги
  3. Створіть випадкове число від 0 до суми
  4. Перебирайте предмети, віднімаючи їх вагу від суми, поки сума не буде додатною
  5. Видаліть об’єкт зі списку, а потім додайте його в кінець нового списку

Елементи 2, 4 і 5 займають n часу, і тому це алгоритм O (n ^ 2).

Чи можна його вдосконалити?

Як приклад зваженого перемішування, елемент, швидше за все, буде спереду з більшою вагою.

Приклад (я згенерую випадкові числа, щоб зробити це реальним):

6 предметів вагою 6,5,4,3,2,1; Сума дорівнює 21

Я вибрав 19: 19-6-5-4-3-2 = -1, тому 2 виходить на перше місце, зараз вага становить 6,5,4,3,1; Сума дорівнює 19

Я вибрав 16 16-6-5-4-3 = -2:, отже, 3 переходить до другої позиції, вага зараз 6,5,4,1; Сума дорівнює 16

Я вибрав 3: 3-6 = -3, тож 6 переходить на третю позицію, вага зараз становить 5,4,1; Сума дорівнює 10

Я вибрав 8:, 8-5-4 = -1, таким чином 4 переходить до четвертої позиції, зараз ваги 5.1; Сума дорівнює 6

Я вибрав 5:, 5-5 = 0, тож 5 переходить на п’яту позицію, вага зараз дорівнює 1; Сума дорівнює 1

Я вибрав 1:, 1-1 = 0, тому 1 переходить до останньої позиції, я більше не маю ваги, закінчую

Це можна реалізувати в O (n log (n)) за допомогою дерева.

Спочатку створіть деревоподібну структуру, зберігаючи в кожному вузлі сукупну суму всіх нащадкових вузлів праворуч і ліворуч від кожного вузла.

Для вибірки елемента рекурсивно виконайте вибірку з кореневого вузла, використовуючи поточні суми, щоб вирішити, чи повертаєте ви поточний вузол, лівий вузол чи правий вузол. Кожного разу, коли ви берете вибірку вузла, встановлюйте його вагу рівним нулю, а також оновлюйте батьківські вузли.

Ось моя реалізація в Python:

weigthed_shuffle - це генератор, тому ви можете ефективно відібрати k найкращих елементів. Якщо ви хочете змішати весь масив, просто перебирайте генератор до вичерпання (за допомогою функції списку).

ОНОВЛЕННЯ:

Зважена випадкова вибірка (2005; Efraimidis, Spirakis) забезпечує дуже елегантний алгоритм для цього. Реалізація надзвичайно проста, а також працює в O (n log (n)):

РЕДАГУВАТИ: Ця відповідь не інтерпретує ваги, як очікувалося. Іншими словами, предмет ваги 2 не вдвічі частіше є простим, ніж предмет ваги 1.

Один із способів перемішати список - це присвоїти випадкові числа кожному елементу у списку та відсортувати їх за цими номерами. Ми можемо продовжити цю ідею, просто виберіть зважені випадкові числа. Наприклад, ви можете використовувати випадкову () * вагу. Різні варіанти спричинять різні розподіли.

У чомусь на зразок Python це має бути настільки просто, як:

Будьте обережні, щоб не оцінювати ключі більше одного разу, оскільки вони матимуть різні значення.

Перш за все, давайте відштовхуємося від того, що вага даного елемента у списку, який потрібно відсортувати, є постійною. Це не зміниться між ітераціями. Якщо так, то. ну це більша проблема.

Наприклад, давайте використаємо колоду карт, де ми хочемо зважити карти обличчям вперед. вага (картка) = картка. Підсумовуючи, якщо ми не знаємо, розподіл ваги дійсно дорівнює O (n) один раз.

Ці елементи зберігаються у відсортованій структурі, такій як модифікація в індексованому списку переходів, так що всі індекси рівня доступні з даного вузла:

Однак у цьому випадку кожен вузол також займає стільки місця, скільки його вага.

Тепер, коли ви шукаєте карту в цьому списку, ви можете перейти до її позиції у списку за час O (log n) і видалити її зі пов’язаних списків за час O (1). Гаразд, можливо, це не O (1), це може бути O (log log n) (мені слід подумати про це набагато більше). Видалення 6-го вузла у наведеному вище прикладі вимагало б оновлення всіх чотирьох рівнів - і ці чотири рівні не залежать від кількості елементів у списку (залежно від того, як ви реалізуєте рівні).

Вага елемента постійний, ми можемо просто обійтися без sum - = вага (видалений), який знову перетинає структуру.

Отже, у вас є одна вартість O (n) і пошукове значення O (log n) і вартість видалення з O (1). Це стає O (n) + n * O (log n) + n * O (1), що дає загальну ефективність O (n log n).

Давайте розглянемо це з картами, тому що це те, що я використав вище.

Це дуже маленька колода, на якій всього 4 карти. Повинно бути легко зрозуміти, як це можна продовжити. З 52 картами ідеальна структура мала б 6 рівнів (журнал 2 (52)

= 6), хоча, якщо заглибитися в списки стрибків, це може бути зменшено до меншої кількості.

Сума всіх ваг дорівнює 10. Отже, ви отримуєте випадкове число [1 . 10) і його 4. Ви переглядаєте список стрибків, щоб знайти предмет, який знаходиться на стелі (4). Оскільки 4 менше 10, ви переходите з вищого рівня на другий рівень. Четверо - це більше 3, отже, зараз у нас два алмази. 4 менше 3 + 7, тому ми опускаємось на нижній рівень, а 4 менше 3 + 3, отже, у нас є 3 алмази.

Після вилучення 3-х діамантів із конструкції структура тепер виглядає так:

Ви помітите, що вузли займають кількість "простору", пропорційного їх вазі в конструкції. Це дозволяє зважений вибір.

Оскільки це наближається до збалансованого двійкового дерева, пошук у ньому не потребує сканування нижнього шару (який би мав значення O (n)), а замість цього підйом дозволяє швидко пропустити структуру, щоб знайти те, що ви шукаєте.

Багато чого з цього можна зробити за допомогою якогось збалансованого дерева. Проблема полягає в тому, що перебалансування структури при видаленні вузла стає заплутаним, оскільки це не є типовою деревною структурою, і домогосподарству доводиться пам’ятати, що 4 алмази тепер переміщено з позицій [6 7 8 9] в [3 4 5 6 ] може коштувати дорожче, ніж переваги деревної структури.

Однак, незважаючи на те, що список переходів наближається до бінарного дерева, оскільки він здатний переходити до списку за час O (log n), він має просту роботу зі зв'язаним списком.

Це не означає, що легко зробити все це (завжди слідкуйте за будь-якими посиланнями, які потрібно редагувати під час видалення елемента), але це означає лише оновлення кількості рівнів, які у вас є., А також їх посилань ніж направо на відповідній структурі дерева.