你真的懂隨機嗎——shuffle一個簡單實用的隨機演算法
正經學徒,佛系記錄,不搞事情
隨機數的定義:
其實隨機數是分為真隨機數和偽隨機數的
所謂真隨機數,根據百科的解釋,真隨機數必須依賴於物理現象,例如大樂透,拋硬幣,公路上第五輛過來的車是單號還是雙號
而偽隨機數又區分為強偽隨機和弱偽隨機
弱隨機數:首先要滿足是隨機的,但容易找出隨機的規律性
強隨機數:首先要滿足是隨機的,且無法預測規律
綜上所述,平時專案中用純演算法實現的隨機其實都屬於偽隨機數,跟是否使用種子等因素無關。而真隨機數的生成,聽聞有人使用滑鼠移動的軌跡,排風扇的噪音大小等物理現象來控制隨機數的生成。
實戰:
這裡以qq音樂為例(誰讓網易雲下架了杰倫的歌),分析qq音樂的隨機播放功能
假設有ABCDEF六首歌,隨機播放功能有以下規律:
- 從播放第一首音樂開始,點選下一首,接下來的五首歌都不會重複,即隨機播放在一輪播放內音樂是不會重複的
- 從播放第一首音樂開始,點選上一首,接下來的五首歌都不會重複,即隨機播放在一輪播放內音樂是不會重複的
- 通過上下切換音樂,在六首歌的範圍內,已經播放過的音樂,順序是固定的,當再進行一次切換時,歌曲是隨機的,且回到規律1、2
通過上述規律,可以推測出一種隨機的過程:
當播放第一首音樂開始,後臺將會把所有歌曲的順序打亂,並儲存歌曲的順序,如當前歌單順序是ABCDEF,此時播放音樂C,後臺會打亂音樂的順序:CDABFE,
且C和E收尾相連,形成一個環狀,此時點選下一首的順序將會是DABFE,點選上一首
當播放的音樂最後一首是B時,此時點選下一首將不會是C,而是重新排序的一個數組,同理當播放的音樂最後一首是D時,此時點選上一首將不會是C,而是重新排序的一個數組。
知道隨機的過程後,問題就落在了怎麼去隨機歌單,這裡就涉及到了shuffle洗牌演算法
java的Collections類已經內建提供了shuffle演算法,程式碼如下
private static Random r; public static void shuffle(List<?> list) { Random rnd = r; if (rnd == null) r = rnd = new Random(); // harmless race. shuffle(list, rnd); } public static void shuffle(List<?> list, Random rnd) { int size = list.size(); if (size < SHUFFLE_THRESHOLD || list instanceof RandomAccess) { for (int i=size; i>1; i--) swap(list, i-1, rnd.nextInt(i)); } else { Object arr[] = list.toArray(); for (int i=size; i>1; i--) swap(arr, i-1, rnd.nextInt(i)); ListIterator it = list.listIterator(); for (int i=0; i<arr.length; i++) { it.next(); it.set(arr[i]); } } } public static void swap(List<?> list, int i, int j) { final List l = list; l.set(i, l.set(j, l.get(i))); } private static void swap(Object[] arr, int i, int j) { Object tmp = arr[i]; arr[i] = arr[j]; arr[j] = tmp; }
可見這裡過載了兩個shuffle方法,如果不傳遞Random引數,則預設使用當前時間作為種子
接下來對集合進行替換,這裡的洗牌演算法是直接對集合本身進行替換,如ABCDEF,首先從F開始,隨機與六位中的某一位替換位置,之後是E,與五位中的某一位替換位置,具體與哪一位通過Random的隨機數來決定。
最後就是實現播放器隨機播放功能的java程式:
建立一個實體類 MusicOrder,用於模擬儲存歌單順序等資訊,實際專案應持久化到資料庫中,如redis等。
//歌單
List<String> order = new ArrayList<>(Arrays.asList("NO1.安靜", "NO2.手寫的從前", "NO3.以父之名", "NO4.一路向北", "NO5.你好嗎"));
//當前播放下標
private int currentPlayIndex = 0;
//最上一首的下標
private int prevIndex = 0;
//最下一首的下標
private int nextIndex = 0;
建立一個工具類MusicShuffleUtil,用於打亂歌單順序,切歌,播放。
public static MusicOrder musicOrder = new MusicOrder();
public static void startMusic(String name){
//獲取歌單
List<String> order = musicOrder.getOrder();
if(name != null && name.length()>0){
int index = order.indexOf(name);
order.remove(index);
//洗牌演算法打亂除當前音樂的其它音樂順序
Collections.shuffle(order);
//將當前播放的音樂置為第一位
order.add(0, name);
}else{
//洗牌演算法打亂全部順序
Collections.shuffle(order);
name = order.get(0);
}
//清零
musicOrder.setCurrentPlayIndex(0);
musicOrder.setNextIndex(0);
musicOrder.setPrevIndex(0);
System.out.println("洗牌後的歌曲順序:"+musicOrder.getOrder());
//播放
playMusic(name);
}
public static void nextMusic(){
int currentPlayIndex = musicOrder.getCurrentPlayIndex()+1;
if(currentPlayIndex==musicOrder.getOrder().size()){
currentPlayIndex -= musicOrder.getOrder().size();
}
//當前下標大於下一首的最大下標則修改最大下一首下標
if(currentPlayIndex>musicOrder.getNextIndex()){
musicOrder.setNextIndex(currentPlayIndex);
}
//歌單已播完,從新重新整理歌單順序
if(currentPlayIndex == musicOrder.getPrevIndex()){
startMusic("");
}else{
musicOrder.setCurrentPlayIndex(currentPlayIndex);
//播放
playMusic(musicOrder.getOrder().get(currentPlayIndex));
}
}
public static void prevMusic(){
int currentPlayIndex = musicOrder.getCurrentPlayIndex()-1;
if(currentPlayIndex<0){
currentPlayIndex += musicOrder.getOrder().size();
}
//當前下標小於上一首的最小下標則修改最小上一首下標
if(currentPlayIndex<musicOrder.getPrevIndex()){
musicOrder.setPrevIndex(currentPlayIndex);
}
//歌單已播完,從新重新整理歌單順序
if(currentPlayIndex == musicOrder.getNextIndex()){
startMusic("");
}else{
musicOrder.setCurrentPlayIndex(currentPlayIndex);
//播放
playMusic(musicOrder.getOrder().get(currentPlayIndex));
}
}
public static void playMusic(String name){
//播放音樂操作
System.out.println("播放:"+name);
}
測試:以NO3音樂開始隨機播放,控制輸入1切換上一首,其它切換下一首
public static void main(String[] args) {
//開始播放
MusicShuffleUtil.startMusic("NO3.以父之名");
Scanner sc=new Scanner(System.in);
while(sc.hasNext())
{
if("1".equals(sc.next())){
MusicShuffleUtil.prevMusic();
}else{
MusicShuffleUtil.nextMusic();
}
}
}
結果:
專案地址:
https://pan.baidu.com/s/1cFrSBPZv4eh6NV8LpV1ZPg 提取碼: xbaq