Я знаю, что для статических весов рекомендуется использовать метод псевдонима Уокера, поскольку его сложность выборки равна O (1).
Однако у меня есть случай, когда мне нужно иметь возможность временно динамически регулировать вероятность определенных элементов.
Скажем, у меня есть карта ключей с соответствующими весами:
A = 5, B = 10, C = 15, D = 20
Я пытаюсь найти наиболее эффективный способ увеличить относительную вероятность того, что произойдет А, на 20 %, а С — на 15 %. Итак, сейчас шансы A и C составляют 10% и 30%. Они должны стать 12% и 34,5%.
Самый очевидный способ выглядит следующим образом:
Примените изменения к весам:
a = 5
c = 15
newA = a * 1.2 = 6
newC = c * 1.15 = 17.25
difference = (newA - a) + (newC - c) = (6 - 5) + (17.5 - 15) = 1 + 2.25 = 3.25
Используйте разницу, чтобы пропорционально уменьшить другие веса (b и d), чтобы общий вес остался прежним:
b = 10
d = 20
bAndD = b + d = 30
newB = b - (b / bAndD) * difference = 10 - 1.083333333333333 = 8.916666666666667
newD = d - (d / bAndD) * difference = 20 - 2.166666666666667 = 17.83333333333333
Итоговые веса:
a = 6
b = 8.916666666666667
c = 17.25
d = 17.83333333333333
totalWeight = remains 50
Итак, шансы сейчас:
chanceA = 12% (20% increase)
chanceB = ~17.83% (~10.83% decrease)
chanceC = 34.5% (15% increase)
chanceD = ~35.66% (~10.83% decrease)
Это здорово, но в идеале мне бы хотелось объединить все это в какую-нибудь эффективную структуру, если это возможно. Динамический псевдоним Уокера, дерево Фенвика или дерево сегментов. И в этом случае, если я правильно понимаю, я, вероятно, не хочу обновлять КАЖДЫЙ вес каждый раз, когда мне нужно временно корректировать шансы.
Есть ли лучшие альтернативы, в которых я мог бы изменить только веса A и C? Или это лучший способ? Если да, означает ли это, что все эффективные структуры и деревья исчезнут из окна, и мне придется использовать стандартный метод «кумулятивный вес / сумма весов», чтобы после этого выбрать запись?
const totalWeight = weights.reduce(
(sum, weight) => sum + weight,
0,
);
const randomWeight = Math.random() * totalWeight;
let cumulativeWeight = 0;
for (let index = 0; i < weights.length; index++) {
cumulativeWeight += weights[index];
if (randomWeight < cumulativeWeight) {
return index;
}
}
UPD: удалось найти решение, которое не изменяет веса B и D. Возможно, это позволит обновить дерево Фенвика/псевдоним динамического ходока с меньшими затратами (поскольку мы обновляем только определенные элементы, а не все).
Wa + Wb + Wc + Wd = Wtotal (new total weight)
desired probability:
Wa / Wtotal = 0.12
Wc / Wtotal = 0.345
Wtotal = Wa + Wc + 30
Wa = Wtotal * 0.12
Wс = Wtotal * 0.345
Wtotal * 0.12 + Wtotal * 0.345 + 30 = Wtotal
0.535 * Wtotal = 30
Wtotal = 30 / 0.535
Wtotal = 56.07476635514019
Wa = 6.728971962616822 = 12% chance
Wc = 19.34579439252337 = 34.5% chance
Весь «общий вес должен оставаться прежним» не является обязательным требованием. Я попытался объяснить свою логику увеличения относительных шансов. 20 + 80 = 100. Увеличьте 20 на 50% = 30. Но для того, чтобы этот вес 30 стал шансом 30%, нам нужно будет уменьшить 80 на 10. Я попытался придумать формулу, которая позволяет только увеличивать A и C, но все стало грязно
UPD: Кажется, мне удалось найти способ обновлять только веса A и C, что уже значительно лучше. Дополнительные отзывы по-прежнему приветствуются.
Это может быть случаем преждевременной оптимизации. Хотя O(1) — это хорошо, O(n) и даже O(n log n) масштабируются довольно хорошо. O(n^2) и O(n^n) создают проблемы. Однако вам не нужно превышать сложность O(n).
Создание таблицы, необходимой для метода Walker Alias, занимает время O(n), где n — количество элементов. У вас есть миллионы или миллиарды предметов? Вы можете не заметить никаких проблем с производительностью, пока не достигнете триллионов или квадриллионов элементов. Либо платформа очень маломощная.
Далее, как часто вам нужно временно корректировать вероятность определенных предметов? Если меньше, чем несколько раз в секунду, вам все равно может не потребоваться что-либо оптимизировать.
Во-первых, я бы попробовал настроить веса, как вы описали в своем вопросе. Я думаю, что эта операция займет не более O(n) времени. Возможно, просто изменить веса A и C, но это, вероятно, не приведет к существенному увеличению производительности (если только у вас нет большого количества элементов и/или вы не постоянно меняете веса). Даже если вы просто измените вес одного элемента, псевдоним Уокера все равно должен предварительно вычислить всю таблицу. (Я не уверен, будет ли оптимизация для повторного использования предыдущей таблицы...)
Затем подключите новые веса к выбранному вами алгоритму, например Walker Alias. Я предполагаю, что поиск - это более часто используемая операция, которая по-прежнему занимает постоянное время O (1).
Даже если вам придется обновлять таблицу при каждом поиске, сложность времени выполнения по-прежнему не превышает O(n).
Вы можете кэшировать исходные веса, если новые веса должны быть временными.
Вы совершенно правы. Это случай преждевременной оптимизации) Я только что узнал о Walker Alias, и мне стало интересно, смогу ли я сохранить его преимущества, имея более сложную логику. «как часто вам нужно временно корректировать вероятность определенных элементов» — потенциально каждый раз, когда я извлекаю запись из таблицы. Итак, по сути, вы говорите, что мне следует использовать оптимистический подход, а затем, в худшем случае, мне придется заново генерировать материал, и он упадет до O (n). Хорошо понял
«Есть ли альтернативы, в которых я мог бы изменить только веса A и C?» - конечно, если вы снимете ограничение, общий вес должен остаться прежним. Вместо того, чтобы уменьшать вероятность B+D, вы можете еще больше увеличить A+C, чтобы B и D остались прежними.