因為別人問了我一個問題當時一兩分鍾沒想上來,后面搜索了下,找到了一個文章寫的很全。搬過來記一下。原問題是想設計一個算法在一個集合中隨便選一個數,但是選出來這個數的概率要和這個數的大小成正比。也就是說希望越大的數被大概率的選出來。
這個問題更清晰點兒描述是,有一組數字,他們都帶有不同的權重,現在要從中“隨機”抽一個數字,但是抽到某個數字的概率要正比於他的權重。假設這個集合中的元素和其對應權重為{‘A’:50,‘B’:10,‘C’:100,‘D’:3,‘E’:60,‘F’:25}。
方法一
如果隨機選的話,在一個list中出現的次數多被選中的概率就會大。所以可以按照權重組合一個list,在這個list中A有50個,B有10個,C有100個,等等。有了list之后,從中隨機選一個。這樣選取的時間負責度是O(1)。但是如果數字很多,權重又很大的話,那么空間負責度會很高。
import random data = {'A': 50, 'B': 10, 'C': 100, 'D': 3, 'E': 60, 'F': 25} value_list = [] for key, value in data.items(): value_list += value*[key] pick_value = random.choice(value_list)
進行100萬次實驗,計數每個字母被選中的次數。結果如下圖所示
可以看出多次實驗的結果,每個字符選中的次數確實與權重成正比。
方法二(常用)
對每個元素的權重進行加和記為sum,在1到sum之間隨機選一個數。之后遍歷整個集合,統計遍歷到的項的權重之和,如果大於sum,則選擇這個數。該方法需要遍歷集合,時間復雜度為O(n)
import random data = {'A': 50, 'B': 10, 'C': 100, 'D': 3, 'E': 60, 'F': 25} value_sum = sum(data.values()) for i in range(1000000): t = random.randint(0, value_sum - 1) for key, value in data.items(): t -= value if t < 0: pick_value = key break
多次實驗統計被選中的元素的次數如下圖
方法三
上面方法最壞的情況是遍歷完所有的元素,但是如果權重大的元素能夠先被遍歷的話可以減少遍歷次數。基於此,可以先對元素按照權重排序。但是這樣排序的話也會比較花時間。可以計算一個前項和序列,記錄到每一項時的權重是多少。然后隨機生成一個數,可以用二分查找找這個數對應的索引,也就可以找到對應的元素。但是這樣就要求數據不能存成字典,因為字典的遍歷是無序的。
import bisect import random data = {'A': 50, 'B': 10, 'C': 100, 'D': 3, 'E': 60, 'F': 25} key_list = list(data.keys()) value_list = [data[key] for key in key_list] prefix_sum = [] tmp_sum = 0 for value in value_list: tmp_sum += value prefix_sum.append(tmp_sum) t = random.randint(0, tmp_sum - 1) pick_value = key_list[bisect.bisect_right(prefix_sum, t)]
同樣統計結果如下
參考:
【1】http://www.cnblogs.com/zywscq/p/5469661.html