青青草原综合久久大伊人导航_色综合久久天天综合_日日噜噜夜夜狠狠久久丁香五月_热久久这里只有精品

隨筆 - 224  文章 - 41  trackbacks - 0
<2010年5月>
2526272829301
2345678
9101112131415
16171819202122
23242526272829
303112345

享受編程

常用鏈接

留言簿(11)

隨筆分類(159)

隨筆檔案(224)

文章分類(2)

文章檔案(4)

經典c++博客

搜索

  •  

最新評論

閱讀排行榜

評論排行榜

原文地址:http://www.cnblogs.com/huaping-audio/archive/2008/09/09/1287985.html

shuffle算法,我把他叫做洗牌算法,它的目標正好與各種的sort算法相反,即把一個有序(或者無序)的一系列元素打亂,以滿足需求。

舉個兩例子,大家都知道撲克牌,我們每次都需要在摸牌之前把牌洗掉,用來讓每個人摸到每張牌的概率盡量相等,增加游戲的隨機性和樂趣;還有音頻播放器,有一些人不喜歡順序播放,而喜歡使用隨機播放(其實隨機播放分為兩種,random和shuffle,后文會介紹到),比如iPod Shuffle的賣點之一就是“你永遠不知道你將要聽到的下一首歌曲是什么”。至少,如果要模擬撲克牌游戲,或者做音頻播放器,都要使用shuffle算法,而二者的shuffle算法卻有一些區別,一個是一次性的洗牌,另一個則是每次取一首歌。那么怎么實現他們呢?

撲克牌的shuffle算法:

下面為了方便和容易讀懂,我都用撲克牌來作例子:桌上有n張牌,并且對桌子上的牌進行標號,從0直到n-1。我們的目的是洗這些牌。

一個比較容易想到的方法是,桌子上有n張撲克牌,我第i次從桌子上等概率隨機取一張撲克牌,作為洗牌后牌堆的第i張撲克牌,那么這個算法實現起來應該是這樣的:

偽代碼:
for i <- 0 to n - 1
do d <- Random mod (n - i)
   shuffle[i] <- deck[d]
   deck[d] <- deck[n - i]

其中,deck是洗牌前的序列(0~n-1),shuffle是洗牌后的序列(0~n-1),第i次(從0開始數)在剩下的n-i張牌里等概率的取一張牌,把它放到shuffle里。而deck[d] = deck[n - i]這句達到的效果是刪除取過的牌。

這個方法的時間復雜度是O(n),已經可以接受了,但這個方法還不夠好,因為我們需要兩個長度為n數組。其實可以很容易得得到下面的方法,解決空間的問題:
偽代碼:
for i <- 0 to n - 1
do d <- Random mod (n - i)
   swap(deck[d], deck[n - i])

這樣,這個算法的道理就有些像選擇排序了,第i次(從0開始數)確定第n-i個元素的原位置,并且交換兩個位置上的元素。它的復雜讀仍然是O(n),而只需要1個額外的空間來儲存交換用的臨時變量。
這個方法已經是一個比較好的解決方法了(自己認為),如果你還能寫出更好的shuffle算法,請告訴我。

我相信對洗牌這種東西有了解的人都不會用這樣的方法來洗牌:另外對每張牌做一個標記,即是否抽過這張牌:然后第i次在n張牌里隨機抽一個,如果這張牌曾經被抽過,那么把它放回去,重復抽取,直到抽到一張沒被抽過的牌,將這張牌標記為抽取過的牌,然后在紙上的第i個地方記下這張牌。在計算機里這樣實現:

偽代碼:
for i <- 0 to n - 1
do d <- Random mod n
   while did[d] = 1
   do d = Random mod n
   did[d] <- 1
   shuffle[i] <- deck[d]


看了描述,你一定就會覺得這種方法實在是遭透了,不僅麻煩,而且會有一個陷阱,那就是在某次取牌的時候,也許會運氣差永遠也取不到沒有被取過的那張牌,導致程序運行的不確定性。然而,在初學者當中,卻有不少是用這種方法實現的shuffle的。個人認為,在設計算法的時候,越簡單、越接近生活的模型,就越容易設計出好的算法,而且算法的描述也更接近實際生活。因此,設計算法的時候,如果能往平時生活的方面想, 總是事半功倍的。

附上我自己實現的一個類qsort的shuffle算法

// element_Size is the size of each element
 
void swap(void const *element1, void const *element2, size_t element_Size)
{
    char *temp = new char,
         *elem1, *elem2;
    elem1 = (char *)element1;
    elem2 = (char *)element2;
    for(int i = 0; i < element_Size; i++, elem1++, elem2++){
        *temp = *elem1;
        *elem1 = *elem2;
        *elem2 = *temp;
    }
    delete temp;
}
 
// array_Size is the size of array,
// element_Size is the size of each element in array
 
void shuffle(void const *array, size_t array_Size, size_t element_Size)
{
    void *element1, *element2;
    srand(time(0));
    for(int i = 0; i < array_Size / element_Size; i++){
        element1 = (char *)array + i * element_Size;
        element2 = (char *)array + rand(i * element_Size,
            array_Size - element_Size, element_Size);
        swap(element1, element2, element_Size);
    }
}

 

播放器的shuffle算法:

前面說過播放器的隨機播放有兩種,一種叫Random,一種叫Shuffle(我自己理解的......),下面解釋這兩種方法的不同。

學過概率的人都該知道有放回的抽取的概念。袋中有n個不同的小球,每次抽取一個小球,然后放回,每一次取的時候概率都是相同的。這正是播放器random算法的原理,這種算法實現起來很簡單,一首歌結束以后,只需要隨機選取下一首歌就行了。
但是這樣做有一些缺點:1,有一定的概率使得連續選取的兩首歌是同一首歌,我相信并不是所有人都希望在shuffle模式下連續聽同一首歌吧,當然也有解決辦法,那就是增加層循環判斷,如果選上同一首歌,則重新選,而這樣又會重蹈那個很爛的洗牌算法的覆轍。2,當聽完一首歌的時候,覺得還想再聽一遍,怎么辦?按下“上一首”,你會發現這時聽到的歌曲已經不是剛才那一首想聽歌曲了,因為這種方法只知道當前的狀態,而不知道過去的播放狀態。怎么辦?一種辦法是增加一個隊列叫做“剛才播放列表”,把播放過的歌曲按照順序儲存在列表里。3,有一定概率在很長的一段時間內,播放器不停的在重復播放兩首歌曲A和B或者類似情況,就像這樣:...-A-B-A-B-A-B-...。這種情況也是很討厭的,可是如何避免呢?我能想到的辦法是增加判斷,看這首歌是不是在列表的最后幾項里,如果在就不選這首......

但是這些概率都小的可憐,對于一個播放器的random函數來說,能夠考慮到以上的幾點,已經能夠做到足夠random和人性化了。只要能夠合理的選擇參數,考慮到一些特殊情況(比如極小的播放列表),以及考慮用戶的心理,就能做出一個比較好的random函數。

下面講我設計的播放器shuffle算法,shuffle算法能夠很大程度上避免random算法的缺陷,在空間時間上都很節約,而且能夠達到比較理想的隨機化效果。它的大體思路是這樣的:

我們使用一個隱含的shuffle播放列表(一個循環隊列)來儲存歌曲的順序,并用一個指針表示正在播放的歌曲(記作"^"),比如當前的播放列表是這樣的:

ABCDEFGHIJKLMN
             ^

即現在有14首歌,將要播放位置1的歌曲(正在播放位置14的歌曲),我們認為隊列頭和尾是相連的,即N后面的元素是A,那么這樣夠成了一個循環隊列。
在播放之前,我們在前7(7=14*0.5,這個比例可以隨便選,當然越大隨機性越大,但能后退的次數越少)個位置中,隨機取一個一首歌,把它和將要播放的那個位置的歌曲交換。假設我們選的是E,則隊列變成這樣:

EBCDAFGHIJKLMN
^

然后播放E。E播放完了以后(或者選擇下一首時),重復剛才的動作,即在BCDAFGH中隨機選一個,交換,比如選到H,則隊列變成:
EHCDAFGBIJKLMN
 ^

然后播放H。這樣,一個shuffle算法初步完成了。

比如某一時刻播放器的狀態是這樣:
EHCDAFGBIJKLMN
          ^

則我們在LMNEHCD中選擇一個,比如選擇到H,那么交換并播放,成為:
ELCDAFGBIJKHMN
           ^

但是如果用戶選擇上一首怎么辦呢?我們可以再記錄一個指針指向最新shuffle選擇出來的那首歌曲(記作"*"),沒有選擇過前一首的時候,它與播放指針指向同一個位置。當選擇前一首的時候,僅移動指針^,而不移動*,比如上一個例子播放的時候按下前一首以后,成為:

ELCDAFGBIJKHMN
          ^*

這時候播放的K正好是剛才播放的那一首,當然這達到了我的目的,即可以選到剛才播放的曲目,當然如果再一次選擇上一首,就會變成:

ELCDAFGBIJKHMN
         ^ *

這時候如果按下一首,應該判斷^指向的是不是和*指向的相同,如果相同,就按照最早介紹的shuffle算法進行隨機選取,不相同就簡單的移動^,即成為:

ELCDAFGBIJKHMN
          ^*

偽代碼:
function keypress(key)
   if key = NEXT
      if p1 = p2
      do p1 <- p1 + 1
         p2 <- p2 + 1
         k = Random mod (length / 2)
         swap(p1, (p1 + k) mod length)
         play(p2)
      else
      do p2 <- (p2 + 1) mod length
         play(p2)
   if key = PREV
      do p2 <- (p2 + length - 1) mod length
         play(p2)

這個播放器的shuffle算法比較簡單實用,而且節約內存開銷(這對mp3 walkman之類的東西是十分重要的),當然也有個小缺點,就是當^前移多次回到*以后,再按下一首,則會重新開始shuffle,但是歌曲數目很多的情況下,這個缺點并不是那么重要。
這個算法在剛開始聽的時候,并不是很隨機,可是隨著聽的次數的增多,隊列會越來越亂,達到一個shuffle的效果。
當然,也可以在第一次對這個列表播放之前,使用撲克牌的shuffle算法(見本文第一部分)進行一次shuffle,這樣,剛開始播放的時候列表就是隨機的。
通過原理我們可以看到,對于剛聽過的那首歌來說,不經過length / 2次,是不會再一次聽到的,因此很大程度上避免了random算法的缺陷。這個length / 2的參數可以按照具體情況選擇,可以是常數,也可以是隨機數,也可以是和長度有關的一個數。 

posted on 2010-10-11 17:25 漂漂 閱讀(1160) 評論(0)  編輯 收藏 引用 所屬分類: 算法
青青草原综合久久大伊人导航_色综合久久天天综合_日日噜噜夜夜狠狠久久丁香五月_热久久这里只有精品
  • <ins id="pjuwb"></ins>
    <blockquote id="pjuwb"><pre id="pjuwb"></pre></blockquote>
    <noscript id="pjuwb"></noscript>
          <sup id="pjuwb"><pre id="pjuwb"></pre></sup>
            <dd id="pjuwb"></dd>
            <abbr id="pjuwb"></abbr>
            国产精品家庭影院| 99国产精品久久久| 久久精品官网| 国产精品尤物福利片在线观看| 99精品99久久久久久宅男| 欧美激情女人20p| 免费视频一区二区三区在线观看| 在线成人性视频| 亚洲理伦在线| 欧美系列精品| 蜜乳av另类精品一区二区| 久久综合激情| 亚洲一区在线免费| 欧美伊人影院| 亚洲一区免费在线观看| 久久久久天天天天| 亚洲欧美一区二区三区久久| 久久久久久9| 欧美一区1区三区3区公司| 欧美 日韩 国产精品免费观看| 久久av一区二区| 欧美精品一级| 欧美激情国产日韩| 国产精品看片资源| 亚洲电影一级黄| 国产一区二区久久精品| 午夜精品亚洲| 午夜国产不卡在线观看视频| 欧美高清视频一二三区| 免费在线日韩av| 亚洲国产一区二区三区青草影视| 欧美影院一区| 免费日韩av电影| 亚洲国产精品va在线看黑人 | 亚洲高清一区二| 亚洲激情亚洲| 欧美精品在线免费播放| 夜夜狂射影院欧美极品| 嫩草影视亚洲| 欧美国产视频日韩| 亚洲精品一区二区三| 欧美精品系列| 亚洲影院色无极综合| 欧美一级成年大片在线观看| 国产欧美亚洲日本| 玖玖综合伊人| 亚洲欧美成人一区二区三区| 久久久久国产一区二区三区四区| 亚洲电影激情视频网站| 欧美sm视频| 亚洲欧美日韩中文视频| 麻豆精品在线视频| 一本色道久久88综合亚洲精品ⅰ| 国产精品性做久久久久久| 久久精品视频在线| 最新69国产成人精品视频免费| 亚洲天堂av高清| 亚洲高清在线观看| 欧美三级午夜理伦三级中视频| 久久露脸国产精品| 亚洲欧美视频| 夜夜爽夜夜爽精品视频| 狂野欧美激情性xxxx欧美| 午夜亚洲福利| 小处雏高清一区二区三区| 亚洲日韩欧美一区二区在线| 一区二区在线观看视频在线观看| 欧美精品二区| 欧美理论电影在线播放| 久久久国产精彩视频美女艺术照福利 | 欧美体内谢she精2性欧美| 久久久精品网| 久久久久久久久久看片| 亚洲四色影视在线观看| 在线一区二区视频| 亚洲国产欧洲综合997久久| 国产精品一区=区| 国产精品午夜电影| 国产精品国产三级国产普通话蜜臀 | 免费不卡欧美自拍视频| 久久精品视频在线| 欧美aa国产视频| 欧美激情综合| 国产精品久久二区| 国产欧美va欧美不卡在线| 国产精品爱啪在线线免费观看| 国产精品美女久久久浪潮软件| 国产精品影视天天线| 国产亚洲精品aa| 亚洲精品欧美在线| 午夜性色一区二区三区免费视频| 午夜精品久久久久久久99樱桃 | 欧美一区二区三区免费观看| 亚洲欧美综合网| 美女精品在线| 一区二区三区高清| 欧美专区在线观看一区| 欧美女激情福利| 国内成+人亚洲+欧美+综合在线| 亚洲大片在线| 久久久久久黄| 亚洲一区二区三区在线视频| 久久色在线播放| 国产精品美女999| 亚洲欧洲午夜| 久久久欧美精品sm网站| 亚洲图片自拍偷拍| 欧美日韩美女在线| 亚洲精品国产精品国自产观看 | 国产精品综合久久久| 一区二区久久久久| 亚洲日本视频| 老司机成人网| 亚洲国产另类 国产精品国产免费| 亚洲制服欧美中文字幕中文字幕| 国产精品99免视看9| 亚洲一区二区黄| 亚洲一区二区三区四区视频| 国产精品欧美日韩久久| 欧美一级黄色录像| 亚洲特级片在线| 国产一区二区成人| 麻豆精品精华液| 欧美电影美腿模特1979在线看| 亚洲国产欧美在线| 日韩视频在线观看| 欧美久久久久久久久久| 欧美成人精品h版在线观看| 久久男人资源视频| 亚洲欧洲综合| 欧美成人一区二区三区| 你懂的视频欧美| 亚洲国产精品成人| 欧美成人69av| 午夜一区二区三区不卡视频| 久久在线免费观看视频| 亚洲一区二区视频| 美女网站久久| 久久伊人免费视频| 欧美日韩亚洲国产精品| 欧美成年人网站| 国产精品久久久久久av下载红粉| 欧美中文字幕在线观看| 欧美凹凸一区二区三区视频| 午夜精品一区二区三区电影天堂| 欧美专区日韩视频| 亚洲欧美一区二区三区极速播放| 女女同性女同一区二区三区91| 欧美亚洲在线| 国产精品乱码一区二三区小蝌蚪| 欧美顶级少妇做爰| 在线日韩中文字幕| 欧美伊人久久久久久久久影院| 每日更新成人在线视频| 狠狠综合久久av一区二区小说 | 国产午夜精品一区二区三区视频| 亚洲视频网在线直播| 99综合视频| 国产精品久久久久国产精品日日| 日韩网站在线| 翔田千里一区二区| 国产片一区二区| 久久精品免费| 亚洲精品少妇网址| 亚洲欧美亚洲| 国产一区二区三区最好精华液| 久久精品国产久精国产一老狼| 欧美激情亚洲精品| 午夜激情亚洲| 亚洲国产精品久久久久婷婷884 | 亚洲黄色精品| 国产欧美日韩另类一区| 久久一区二区三区av| 宅男噜噜噜66一区二区| 亚洲性图久久| 亚洲伊人久久综合| 亚洲欧洲日韩综合二区| 女生裸体视频一区二区三区| 久热精品在线视频| 毛片一区二区| 最新热久久免费视频| 亚洲三级性片| 亚洲日本va午夜在线影院| 免费在线视频一区| 免费成人高清在线视频| 免费黄网站欧美| 欧美成人一区二区三区| 久久国产精品免费一区| 亚洲女性裸体视频| 一区二区三区四区五区精品| 亚洲国产精品va| 亚洲第一黄网| 在线观看亚洲一区| 一区免费在线| 亚洲成人资源| 亚洲欧洲在线一区| 亚洲精品视频二区| 日韩一区二区免费高清| 日韩视频精品| 久久成人精品一区二区三区|