Ağırlıklı shuffle nasıl uygulanır


22

Geçenlerde çok verimsiz olduğunu düşündüğüm bazı kodlar yazdım, ancak yalnızca birkaç değer içerdiğinden kabul ettim. Ancak, aşağıdakiler için daha iyi bir algoritmaya hala ilgi duyuyorum:

  1. Her birine bir "ağırlık" atanan X nesnelerinin bir listesi
  2. Ağırlıkları topla
  3. Toplamdan 0 dan rasgele bir sayı oluşturun
  4. Nesneler arasında yineleme, ağırlıklarını toplamdan pozitif olmayana kadar toplamdan çıkarma
  5. Nesneyi listeden kaldırın ve ardından yeni listenin sonuna ekleyin.

2,4 ve 5 numaralı maddeler nzaman alır ve bu nedenle bir O(n^2)algoritmadır.

Bu geliştirilebilir mi?

Ağırlıklandırılmış bir karıştırmaya örnek olarak, bir elemanın ön tarafında daha yüksek ağırlıkta olma şansı daha yüksektir.

Örnek (gerçek kılmak için rastgele sayılar üreteceğim):

Ağırlığa sahip 6 nesne 6,5,4,3,2,1; Toplam 21

19'u seçtim: 19-6-5-4-3-2 = -12 böylece birinci konuma girdim, ağırlıklar şimdi 6,5,4,3,1; Toplam 19

16'yı seçtim: 16-6-5-4-3 = -23 böylece ikinci konuma geçiyor, ağırlıklar şimdi 6,5,4,1; Toplam 16

3'ü seçtim: 3-6 = -3Böylece 6 üçüncü pozisyona giriyor, ağırlıklar şimdi 5,4,1; Toplam 10

8 seçtim: 8-5-4 = -1Böylece 4 dördüncü sıraya giriyor, ağırlıklar şimdi 5,1; Toplam 6

5 seçtim: 5 5-5=0böylece beşinci sırada yer alıyor, ağırlıklar 1; Toplam 1

1 seçtim: 1 1-1=0böylece son konuma geçiyorum, daha fazla ağırlığım yok, bitiriyorum


6
Tam olarak ağırlıklı bir karışıklık nedir? Bu, ağırlık arttıkça, nesnenin destenin en üstünde olması anlamına mı geliyor?
Doval

Meraktan, (5) adımının amacı nedir. Liste statikse bunu iyileştirmenin yolları vardır.
Robot

Evet Doval. Öğeyi listeden kaldırdım, böylece karıştırılmış listede bir kereden fazla görünmüyor.
Nathan Merrill

Listedeki bir öğenin ağırlığı sabit mi?

Bir öğe diğerinden daha büyük bir ağırlığa sahip olacaktır, ancak X maddesi her zaman aynı ağırlığa sahip olacaktır. (Belli ki, eğer eşyalarını kaldırırsanız, daha büyük ağırlık orantılı olarak artar)
Nathan Merrill

Yanıtlar:


14

Bu O(n log(n))bir ağaç kullanımında uygulanabilir .

İlk olarak, her düğümde sağ ve soldaki her inen düğümün kümülatif toplamını koruyarak ağacı oluşturun.

Bir öğeyi örneklemek için, geçerli düğümü, soldan bir düğümü veya sağdan bir düğümü sağa döndürüp döndürmemenize karar vermek için kümülatif toplamları kullanarak tekrar tekrar kök düğümünden örnek alın. Bir düğümü her örneklediğinizde ağırlığını sıfıra ayarlayın ve ana düğümleri de güncelleyin.

Bu benim Python'daki uygulamam:

import random

def weigthed_shuffle(items, weights):
    if len(items) != len(weights):
        raise ValueError("Unequal lengths")

    n = len(items)
    nodes = [None for _ in range(n)]

    def left_index(i):
        return 2 * i + 1

    def right_index(i):
        return 2 * i + 2

    def total_weight(i=0):
        if i >= n:
            return 0
        this_weigth = weights[i]
        if this_weigth <= 0:
            raise ValueError("Weigth can't be zero or negative")
        left_weigth = total_weight(left_index(i))
        right_weigth = total_weight(right_index(i))
        nodes[i] = [this_weigth, left_weigth, right_weigth]
        return this_weigth + left_weigth + right_weigth

    def sample(i=0):
        this_w, left_w, right_w = nodes[i]
        total = this_w + left_w + right_w
        r = total * random.random()
        if r < this_w:
            nodes[i][0] = 0
            return i
        elif r < this_w + left_w:
            chosen = sample(left_index(i))
            nodes[i][1] -= weights[chosen]
            return chosen
        else:
            chosen = sample(right_index(i))
            nodes[i][2] -= weights[chosen]
            return chosen

    total_weight() # build nodes tree

    return (items[sample()] for _ in range(n - 1))

Kullanımı:

In [2]: items = list(range(10))
   ...: weights = list(range(10, 0, -1))
   ...:

In [3]: for _ in range(10):
   ...:     print(list(weigthed_shuffle(items, weights)))
   ...:
[5, 0, 8, 6, 7, 2, 3, 1, 4]
[1, 2, 5, 7, 3, 6, 9, 0, 4]
[1, 0, 2, 6, 8, 3, 7, 5, 4]
[4, 6, 8, 1, 2, 0, 3, 9, 7]
[3, 5, 1, 0, 4, 7, 2, 6, 8]
[3, 7, 1, 2, 0, 5, 6, 4, 8]
[1, 4, 8, 2, 6, 3, 0, 9, 5]
[3, 5, 0, 4, 2, 6, 1, 8, 9]
[6, 3, 5, 0, 1, 2, 4, 8, 7]
[4, 1, 2, 0, 3, 8, 6, 5, 7]

weigthed_shufflebir jeneratör olduğundan, üstteki kparçaları verimli bir şekilde örnekleyebilirsiniz . Tüm diziyi karıştırmak istiyorsanız, yalnızca tükenene kadar ( listişlevi kullanarak ) jeneratörün üzerinde yineleyin .

GÜNCELLEŞTİRME:

Ağırlıklı Rastgele Örnekleme (2005; Efraimidis, Spirakis) bunun için çok şık bir algoritma sağlar. Uygulama süper basittir ve ayrıca O(n log(n)):

def weigthed_shuffle(items, weights):
    order = sorted(range(len(items)), key=lambda i: -random.random() ** (1.0 / weights[i]))
    return [items[i] for i in order]

Son güncelleme , yanlış bir liner çözümüne şiddetle benziyor . Bunun doğru olduğuna emin misin?
Giacomo Alzetta

19

EDIT: Bu cevap ağırlıkları beklendiği gibi yorumlamaz. Yani, ağırlık 2 olan bir ürün, ağırlık 1 olan ilk üründen iki kat daha fazla değildir.

Bir listeyi karıştırmanın bir yolu, listedeki her bir öğeye rasgele sayılar atamak ve bu numaralara göre sıralamaktır. Bu fikri uzatabiliriz, sadece ağırlıklı rastgele sayılar seçmeliyiz. Örneğin, kullanabilirsiniz random() * weight. Farklı seçenekler farklı dağıtımlar üretecektir.

Python gibi bir şey, bu kadar basit olmalıdır:

items.sort(key = lambda item: random.random() * item.weight)

Anahtarları farklı değerlere sahip olacağından, bir kereden fazla değerlendirmemeye dikkat edin.


2
Bu sadeliği nedeniyle dürüst bir şekilde dahi. Bir nlogn sıralama algoritması kullandığınızı varsayarsak, bu iyi sonuç vermelidir.
Nathan Merrill

Ağırlıkların ağırlığı nedir? Yükseklerse, nesneler basitçe ağırlıklarına göre sıralanır. Eğer düşüklerse, nesneler ağırlığa göre sadece hafif çarpıklıklarla neredeyse rastgeledir. Her iki durumda da, bu yöntemi her zaman kullandım, ancak sıralama konumunun hesaplanması muhtemelen biraz ince ayar gerektirecektir.
david.pfx

@ david.pfx Ağırlıkların aralığı rastgele sayıların aralığı olmalıdır. Bu şekilde max*min = min*maxve dolayısıyla herhangi bir permütasyon mümkündür, ancak bazıları çok daha muhtemeldir (özellikle ağırlıklar eşit dağılmadığında)
Nathan Merrill

2
Aslında, bu yaklaşım yanlış! 75 ve 25 ağırlıkları düşünün. 75 durumda, zamanın 2 / 3'ü> 25 seçecektir. Kalan 1 / 3'ünde, zamanın% 50'sini "% 25" yenecektir. 75 zamanın ilk 2/3 + (1/3 * 1/2) olacak:% 83. Henüz bir çözüm bulamadım.
Adam Rabung

1
Bu çözüm rastgele örneklemenin düzgün dağılımını üssel bir dağılımla değiştirerek çalışmalıdır.
P-Gn

5

Birincisi, sıralanacak listedeki belirli bir öğenin ağırlığının sabittir. Yinelemeler arasında değişmeyecek. Eğer öyleyse, o zaman ... şey, bu daha büyük bir problem.

Örnek olarak, yüz kartlarını öne doğru ağırlıklandırmak istediğimiz bir kart destesini kullanalım. weight(card) = card.rank. Bunları toplayarak, eğer ağırlık dağılımını bilmiyorsak, gerçekten bir kez O (n) 'dir.

Bu elemanlar, bir indekslenebilir atlama listesindeki bir modifikasyon gibi sınıflandırılmış bir yapıda depolanır , böylece seviyelerin tüm indekslerine verilen bir düğümden erişilebilir:

   1 10
 o ---> o ------------------------------------------------ -------------> o Üst seviye
   1 3 2 5
 o ---> o ---------------> o ---------> o ---------------- -----------> o Seviye 3
   1 2 1 2 5
 o ---> o ---------> o ---> o ---------> o ----------------- ----------> o Seviye 2
   1 1 1 1 1 1 1 1 1 1 1 1 
 o ---> o ---> o ---> o ---> o ---> o ---> o ---> o ---> o ---> o ---> o ---> o Alt seviye

Kafa 1. 2. 3. 4. 5. 6. 7. 8. 8. 9. 10. NIL
      Düğüm Düğüm Düğüm Düğüm Düğüm Düğüm Düğüm Düğüm Düğüm Düğüm

Bununla birlikte, bu örnekte, her düğüm aynı zamanda ağırlığı kadar yer kaplar.

Şimdi, bu listedeki bir kartı ararken, listedeki konumuna O (log n) zamanında erişebilir ve ilgili listeden O (1) sürede çıkarabilirsiniz. Tamam, O (1) olmayabilir, O (log log n) zamanı olabilir (bunu daha fazla düşünmek zorunda kalırdım). Yukarıdaki örnekte 6. düğümü kaldırmak, dört seviyenin de güncellenmesini içerir - ve bu dört seviye listedeki kaç öğenin bulunduğundan bağımsızdır (düzeyleri nasıl uyguladığınıza bağlı olarak).

Bir elemanın ağırlığı sabit sum -= weight(removed)olduğu için, yapıyı tekrar geçmek zorunda kalmadan basit bir işlem yapılabilir .

Böylece, bir kerelik O (n) maliyetine ve O (log n) arama değerine ve O (1) liste maliyetinden kaldırmaya sahip olursunuz. Bu O (n) + n * O (log n) + n * O (1) olur ve bu da size genel bir O (n log n) performansı verir.


Buna kartlarla bakalım, çünkü yukarıda kullandığım şey buydu.

      10
ilk 3 -----------------------> 4d
                                .
       3 7.
    2 ---------> 2d ---------> 4d
                  . .
       1 2. 3 4.
bot 1 -> İlan -> 2d -> 3d -> 4d

Bu sadece 4 kart ile gerçekten küçük bir güverte. Bunun nasıl genişletilebileceğini görmek kolay olmalı. 52 kartla ideal bir yapı 6 seviyeye sahip olur (log 2 (52) ~ = 6), eğer atlama listelerine girseniz bile daha küçük bir sayıya düşürülebilir.

Tüm ağırlıkların toplamı 10'dur. Böylece [1 .. 10] 'dan rasgele bir sayı elde edersiniz ve 4 Tavanda (4) olan öğeyi bulmak için atlama listesini yürüdünüz. 4 10'dan küçük olduğundan, en üst seviyeden ikinci seviyeye geçersiniz. Dört, 3'ten büyük, yani şimdi elmasların 2'sindeyiz. 4, 3 + 7'den az, bu nedenle en alt seviyeye iniyoruz ve 4, 3 + 3'ten daha az, bu yüzden 3 elmasımız var.

3 elmasın yapıdan çıkarılmasından sonra, yapı şuna benzer:

       7
ilk 3 ----------------> 4d
                         .
       3 4.
    2 ---------> 2d -> 4d
                  . .
       1 2. 4.
bot 1 -> Reklam -> 2d -> 4d

Düğümlerin, yapıdaki ağırlıklarıyla orantılı bir miktar 'yer' aldığını not edersiniz. Bu, ağırlıklı seçim için izin verir.

Bu , dengeli bir ikili ağaç yaklaştığından , bu aramaya bakmak alt katmanı (O (n) olacaktır) yürümek zorunda kalmaz ve bunun yerine üstten gitmek aradığınızı bulmak için yapıyı hızla atlamanızı sağlar için.

Bunun çoğu bir çeşit dengeli ağaçla yapılabilir. Buradaki problem, bir düğüm çıkarıldığında yapının yeniden dengelenmesidir, çünkü bu klasik bir ağaç yapısı değildir ve elmasların 4'ün şimdi konumlarından [3 7] 5 6], ağaç yapısının faydalarından daha pahalı olabilir.

Bununla birlikte, atlama listesi, O (log n) zamanında listeyi atlama kabiliyetinde bir ikili ağaca yaklaşırken, bunun yerine bağlantılı bir liste ile çalışmanın basitliğine sahiptir.

Bu, tüm bunları yapmanın kolay olduğunu söylemek değildir (bir öğeyi kaldırırken değiştirmeniz gereken tüm bağlantıları takip etmeye devam etmeniz gerekir), ancak bu yalnızca sahip olduğunuz seviyeyi ve bağlantılarını güncellemek anlamına gelir. doğru ağaç yapısındaki sağdaki her şeyden.


Neyi tanımladığınızın bir Atlama listesiyle eşleştiğinden emin değilim (ancak o zaman sadece atlama listelerine baktım ). Wikipedia'da anladığım kadarıyla, yüksek ağırlıklı daha düşük ağırlıklardan daha sağa doğru olacaktır. Ancak, atlamaların genişliğinin ağırlık olması gerektiğini açıklıyorsunuz. Başka bir soru ... bu yapıyı kullanarak rastgele bir elemanı nasıl seçersiniz?
Nathan Merrill

1
@ MrTi böylece bir endekslenebilir atlama listesi fikri üzerinde değişiklik . Anahtar, önceki öğelerin ağırlığının, O (n) zamanından ziyade O (log n) zaman içinde <23'e toplandığı noktada elemana erişebilmektir. Yine de rastgele öğeyi tarif ettiğiniz şekilde seçiyorsunuz, [0, toplam (ağırlıklar)] arasından rastgele bir sayı seçiyor ve ardından ilgili öğeyi listeden alıyorsunuz. Düğümlerin / kartların atlama listesinde hangi sırayla olduğu önemli değildir - çünkü daha ağır ağırlıklı nesneler tarafından alınan daha büyük 'alan' anahtardır.

Ah, anlıyorum. Bunu sevdim.
Nathan Merrill
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.