भारित फेरबदल को कैसे लागू किया जाए


22

मैंने हाल ही में कुछ कोड लिखे थे जो मुझे बहुत ही अयोग्य लगे, लेकिन चूंकि इसमें केवल कुछ मूल्य शामिल थे, इसलिए मैंने इसे स्वीकार कर लिया। हालाँकि, मैं अभी भी निम्नलिखित के लिए एक बेहतर एल्गोरिथ्म में दिलचस्पी रखता हूं:

  1. एक्स ऑब्जेक्ट्स की एक सूची, उनमें से प्रत्येक को एक "वजन" सौंपा गया है
  2. वज़न बढ़ाओ
  3. 0 से योग तक एक यादृच्छिक संख्या उत्पन्न करें
  4. वस्तुओं के माध्यम से Iterate, उनके वजन को योग से घटाकर जब तक कि राशि गैर-सकारात्मक न हो
  5. ऑब्जेक्ट को सूची से निकालें, और फिर उसे नई सूची के अंत में जोड़ें

आइटम 2,4, और 5 सभी nसमय लेते हैं , और इसलिए यह एक O(n^2)एल्गोरिथ्म है।

क्या इसमें सुधार किया जा सकता है?

एक भारित फेरबदल के उदाहरण के रूप में, एक तत्व के सामने एक उच्च वजन के साथ होने का एक बड़ा मौका है।

उदाहरण (मैं इसे वास्तविक बनाने के लिए यादृच्छिक संख्या उत्पन्न करूँगा):

6 वज़न वाले 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=0इस प्रकार ५ वें स्थान पर जाता है, वज़न अब १ है; योग 1 है

मैंने 1 उठाया: 1-1=0इस प्रकार 1 अंतिम स्थिति में जाता है, मेरे पास अधिक भार नहीं है, मैं समाप्त करता हूं


6
क्या वास्तव में एक भारित फेरबदल है? क्या इसका मतलब है कि वजन जितना अधिक होगा, उतनी ही अधिक संभावना डेक के शीर्ष पर होगी?
डोभाल

जिज्ञासा से बाहर, चरण (5) का उद्देश्य क्या है। यदि सूची स्थिर है, तो इसे सुधारने के तरीके हैं।
रोबोट

हाँ, डोभाल। मैं सूची से आइटम को हटाता हूं ताकि यह एक से अधिक बार फेरबदल सूची में प्रकट न हो।
नाथन मेरिल

क्या सूची में किसी वस्तु का वजन स्थिर है?

एक आइटम का वजन दूसरे की तुलना में बड़ा होगा, लेकिन आइटम X का वजन हमेशा उतना ही होगा। (जाहिर है, यदि आप वस्तुओं को हटाते हैं, तो बड़ा वजन अनुपात में बड़ा हो जाएगा)
नाथन मेरिल

जवाबों:


14

यह O(n log(n))एक पेड़ का उपयोग करने में लागू किया जा सकता है ।

सबसे पहले, प्रत्येक नोड के दाईं ओर और सभी नोड के बाईं ओर प्रत्येक नोड में संचयी राशि रखते हुए, ट्री बनाएं।

किसी आइटम का नमूना करने के लिए, रूट नोड से पुनरावर्ती नमूना, संचयी रकम का उपयोग करके यह तय करने के लिए कि क्या आप वर्तमान नोड वापस करते हैं, बाईं ओर से नोड या दाईं ओर से नोड। हर बार जब आप एक नोड का नमूना लेते हैं, तो उसका वजन शून्य पर सेट करें और मूल नोड्स को भी अपडेट करें।

यह पायथन में मेरा कार्यान्वयन है:

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))

उपयोग:

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_shuffleएक जनरेटर है, ताकि आप शीर्ष kवस्तुओं को कुशलता से नमूना कर सकें । यदि आप संपूर्ण सरणी को फेरबदल करना चाहते हैं, तो थकावट ( listफ़ंक्शन का उपयोग करके ) तक जनरेटर पर पुनरावृति करें ।

अद्यतन करें:

भारित रैंडम सैम्पलिंग (2005; एफ़्रैमिडिस, स्पाइराकिस) इसके लिए एक बहुत ही सुंदर एल्गोरिथ्म प्रदान करता है। कार्यान्वयन सुपर सरल है, और इसमें भी चलता है 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]

अंतिम अद्यतन एक गलत एक लाइनर समाधान के समान है । क्या आप सुनिश्चित हैं कि सही है?
जियाको अल्जेटा

19

संपादित करें: यह उत्तर उस तरह से वजन की व्याख्या नहीं करता है जिस तरह की उम्मीद की जाएगी। वजन 2 के साथ एक आइटम दो बार वजन 1 के साथ पहले के रूप में होने की संभावना नहीं है।

सूची में फेरबदल करने का एक तरीका यह है कि सूची में प्रत्येक तत्व को यादृच्छिक संख्या निर्दिष्ट की जाए और उन संख्याओं के आधार पर छाँटें। हम उस विचार को बढ़ा सकते हैं, हमें केवल भारित यादृच्छिक संख्याएँ चुननी होंगी। उदाहरण के लिए, आप उपयोग कर सकते हैं random() * weight। विभिन्न विकल्प अलग-अलग वितरण का उत्पादन करेंगे।

पायथन जैसे कुछ में, यह उतना ही सरल होना चाहिए:

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

सावधान रहें कि आप कुंजियों का अधिक मूल्यांकन नहीं करते हैं, एक बार, क्योंकि वे विभिन्न मूल्यों के साथ समाप्त हो जाएंगे।


2
यह अपनी सादगी के कारण ईमानदारी से प्रतिभाशाली है। मान लें कि आप एक nlogn सॉर्टिंग एल्गोरिथ्म का उपयोग कर रहे हैं, यह अच्छी तरह से काम करना चाहिए।
नाथन मेरिल

वजन का वजन क्या है? यदि वे उच्च हैं, तो वस्तुओं को केवल वजन द्वारा क्रमबद्ध किया जाता है। यदि वे कम हैं, तो वजन के अनुसार केवल मामूली गड़बड़ी के साथ ऑब्जेक्ट लगभग यादृच्छिक हैं। किसी भी तरह से, इस पद्धति का मैंने हमेशा उपयोग किया है, लेकिन सॉर्ट स्थिति की गणना के लिए शायद कुछ ट्विकिंग की आवश्यकता होगी।
david.pfx

@ david.pfx वजन की सीमा यादृच्छिक संख्याओं की सीमा होनी चाहिए। इस तरह max*min = min*max, और इस तरह किसी भी क्रमपरिवर्तन संभव है, लेकिन कुछ अधिक होने की संभावना है (विशेषकर यदि वजन समान रूप से फैले हुए नहीं हैं)
नाथन मेरिल

2
दरअसल, यह दृष्टिकोण गलत है! कल्पना कीजिए कि वजन ५५ और २५ है। ,५ मामले के लिए, २/३ समय के लिए यह संख्या> २५ का चयन करेगा। शेष १/३ समय के लिए, यह समय के २५ ५०% को "हरा" कर देगा। 75 इस समय का पहला 2/3 + (1/3 * 1/2) होगा: 83%। अभी तक तय नहीं किया है।
एडम रबंग

1
इस समाधान को घातीय वितरण द्वारा यादृच्छिक नमूने के समान वितरण को प्रतिस्थापित करके काम करना चाहिए।
पी-जीएन

5

सबसे पहले, इससे काम करने देता है कि सूची में दिए गए तत्व का वजन क्रमबद्ध है। यह पुनरावृत्तियों के बीच बदलने वाला नहीं है। अगर ऐसा होता है, तो ... ठीक है, एक बड़ी समस्या है।

उदाहरण के लिए, हम कार्ड के एक डेक का उपयोग करते हैं जहाँ हम सामने के कार्डों का वजन करना चाहते हैं। weight(card) = card.rank। इनको समेटते हुए, यदि हम नहीं जानते कि वज़न का वितरण वास्तव में एक बार O (n) है।

इन तत्वों को एक क्रमबद्ध संरचना में संग्रहीत किया जाता है, जैसे कि एक अनुक्रमित छोड़ें सूची में संशोधन जैसे कि सभी स्तरों के सूचकांक को दिए गए नोड से एक्सेस किया जा सकता है:

   १ १०
 ओ ---> ओ -------------------------------------------- -------------> o शीर्ष स्तर
   १ ३ २ ५
 ओ ---> ओ ---------------> ओ ---------> ओ ---------------- -----------> ओ लेवल 3
   1 2 1 2 5
 ओ ---> ओ ---------> ओ ---> ओ ---------> ओ ----------------- ----------> ओ लेवल २
   1 1 1 1 1 1 1 1 1 1 1 
 ओ ---> ओ ---> ओ ---> ओ ---> ओ ---> ओ ---> ओ ---> ओ ---> ओ ---> ओ ---> o ---> o निचला स्तर

हेड 1 2 3 डी 4 डी 5 वीं 6 वीं 7 वीं 9 वीं 10 वीं एनआईएल
      नोड नोड नोड नोड नोड नोड नोड नोड नोड

हालांकि इस उदाहरण में, प्रत्येक नोड अपने वजन के रूप में 'कमरे' लेता है।

अब, जब इस सूची में एक कार्ड को देख रहे हैं, तो कोई व्यक्ति ओ (लॉग एन) समय में सूची में अपनी स्थिति तक पहुंच सकता है और इसे ओ (1) समय में संबंधित सूची से हटा सकता है। ठीक है, यह ओ (1) नहीं हो सकता है, यह ओ (लॉग लॉग एन) समय हो सकता है (मुझे इस बारे में अधिक सोचना होगा)। उपरोक्त उदाहरण में 6 वें नोड को हटाने से सभी चार स्तरों को अपडेट करना शामिल होगा - और वे चार स्तर स्वतंत्र हैं कि सूची में कितने तत्व हैं (आप स्तरों को कैसे लागू करते हैं इसके आधार पर)।

चूंकि एक तत्व का वजन स्थिर होता है, इसलिए कोई भी sum -= weight(removed)संरचना को फिर से खोले बिना बस कर सकता है।

और इस प्रकार, आपको ओ (एन) की एक बार की लागत और ओ का एक लुकअप मूल्य (लॉग एन) और ओ (1) की सूची लागत से एक निष्कासन मिला है। यह O (n) + n * O (लॉग एन) + n * O (1) बन जाता है जो आपको O (n लॉग एन) का समग्र प्रदर्शन देता है।


यह कार्ड के साथ देखो, क्योंकि मैं ऊपर क्या इस्तेमाल किया।

      10
शीर्ष 3 -----------------------> 4 डी
                                ।
       ३ 7।
    2 ---------> 2 डी ---------> 4 डी
                  । ।
       १ २। ३ ४।
बॉट 1 -> विज्ञापन -> 2 डी -> 3 डी -> 4 डी

यह एक बहुत छोटा डेक है जिसमें केवल 4 कार्ड हैं। यह देखना आसान होना चाहिए कि इसे कैसे बढ़ाया जा सकता है। 52 कार्डों के साथ एक आदर्श संरचना में 6 स्तर (लॉग 2 (52) ~ = 6) होंगे, हालांकि यदि आप स्किप सूचियों में खुदाई करते हैं, जो कि छोटी संख्या में भी कम हो सकते हैं।

सभी वज़न का योग 10. है। इसलिए आपको [1 .. 10) से एक यादृच्छिक संख्या मिलती है और इसके 4 आप सीलिंग (4) पर है कि आइटम को खोजने के लिए छोड़ें सूची पर चलते हैं। चूंकि 4 10 से कम है, आप शीर्ष स्तर से दूसरे स्तर पर जाते हैं। चार 3 से अधिक है, इसलिए अब हम हीरे के 2 पर हैं। 4 3 + 7 से कम है, इसलिए हम नीचे के स्तर पर चले जाते हैं और 4 3 + 3 से कम है, इसलिए हमें 3 हीरे मिले हैं।

संरचना से 3 हीरे निकालने के बाद, संरचना अब इस तरह दिखती है:

       7
शीर्ष 3 ----------------> 4 डी
                         ।
       ३ ४।
    2 ---------> 2 डी -> 4 डी
                  । ।
       १ २। ४।
बॉट 1 -> विज्ञापन -> 2 डी -> 4 डी

आप ध्यान देंगे कि नोड्स संरचना में उनके वजन के अनुपात में 'स्थान' की मात्रा लेते हैं। यह भारित चयन के लिए अनुमति देता है।

जैसा कि यह एक संतुलित द्विआधारी वृक्ष का अनुमान लगाता है, इसमें देखने के लिए नीचे की परत (जो कि O (n) होगी) चलने की आवश्यकता नहीं है और इसके बजाय ऊपर से जाने से आप जिस चीज को देख रहे हैं उसके बारे में जानने के लिए संरचना को तेजी से छोड़ सकते हैं। के लिये।

इसके बजाय संतुलित पेड़ के कुछ प्रकार के साथ किया जा सकता है। समस्या तब होती है जब संरचना का असंतुलन तब होता है जब एक नोड को हटा दिया जाता है क्योंकि यह क्लासिक ट्री संरचना नहीं है और हाउसकीपिंग को यह याद रखने के लिए कि हीरे के 4 को अब पदों से स्थानांतरित कर दिया गया है [6 7 8 9] [3 4] ५ ६] पेड़ की संरचना के लाभों से अधिक खर्च हो सकता है।

हालाँकि, जबकि स्किप सूची O (लॉग एन) समय में सूची को छोड़ने के लिए अपनी क्षमता में एक द्विआधारी वृक्ष का अनुमान लगाती है, इसके बजाय लिंक की गई सूची के साथ काम करने की सरलता है।

यह कहना आसान नहीं है कि यह सब करना आसान है (आपको किसी तत्व को हटाते समय आपको उन सभी लिंक पर नजर रखने की जरूरत है जो आपको संशोधित करने की आवश्यकता है), लेकिन इसका मतलब है कि आपके पास और उनके लिंक के बजाय केवल कई स्तरों को अपडेट करना है उचित पेड़ संरचना पर दाईं ओर सब कुछ की तुलना में।


मैं नहीं कर रहा हूँ यकीन है कि कैसे क्या आप मैचों में एक छोड़ें सूची का वर्णन कर रहे हैं (लेकिन फिर, मैं था बस छोड़ सूचियों देखो)। विकिपीडिया पर मुझे जो समझ में आया है, उससे कम वजन वाले की तुलना में अधिक वजन वाले व्यक्ति दाईं ओर अधिक होंगे। हालाँकि, आप वर्णन कर रहे हैं कि स्कीप की चौड़ाई वजन होनी चाहिए। एक अन्य प्रश्न ... इस संरचना का उपयोग करते हुए, आप एक यादृच्छिक तत्व कैसे उठाते हैं?
नाथन मेरिल

1
@MrTi इस प्रकार एक इंडेक्सेबल स्किप सूची के विचार पर संशोधन करता है। कुंजी उस तत्व तक पहुंचने में सक्षम है, जहां पिछले तत्वों का वजन O (n) समय के बजाय O (लॉग एन) समय में <23 पर निर्भर है। आप अभी भी जिस तरह से आप वर्णन कर रहे थे उस यादृच्छिक तत्व को चुनें, [0, सम (वेट)] से एक यादृच्छिक संख्या चुनें और फिर सूची से संबंधित तत्व प्राप्त करें। इससे कोई फर्क नहीं पड़ता कि स्किप सूची में नोड / कार्ड किस क्रम के हैं - क्योंकि भारी भार वाली वस्तुओं द्वारा लिया गया बड़ा 'स्पेस' कुंजी है।

आह! मुझे समझ आ गया। मुझें यह पसंद है।
नाथन मेरिल
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.