Fisher–Yates隨機置亂算法也被稱做高納德置亂算法,通俗說就是生成一個有限集合的隨機排列。Fisher-Yates隨機置亂算法是無偏的,所以每個排列都是等可能的,當前使用的Fisher-Yates隨機置亂算法是相當有效的,需要的時間正比於要隨機置亂的數,不需要額爲的存儲空間開銷。
一、算法流程:
需要隨機置亂的n個元素的數組a:
for i 從n-1到1
j <—隨機整數(0 =< j <= i)
交換a[i]和a[j]
end
二、實例
各列含義:範圍、當前數組隨機交換的位置、剩餘沒有被選擇的數、已經隨機排列的數
第一輪:從1到8中隨機選擇一個數,得到6,則交換當前數組中第8和第6個數
第二論:從1到7中隨機選擇一個數,得到2,則交換當前數組中第7和第2個數
下一個隨機數從1到6中搖出,剛好是6,這意味着只需把當前線性表中的第6個數留在原位置,接着進行下一步;以此類推,直到整個排列完成。
截至目前,所有需要的置亂已經完成,所以最終的結果是:7 5 4 3 1 8 2 6
三、Java源代碼
package simpleGa;
import java.util.Arrays;
import java.util.Random;
public class Test {
public static void main(String[] args) {
int[] arr = new int[10];
int i;
//初始的有序數組
System.out.println("初始有序數組:");
for (i = 0; i < 10; i++) {
arr[i] = i + 1;
System.out.print(" " + arr[i]);
}
//費雪耶茲置亂算法
System.out.println("\n" + "每次生成的隨機交換位置:");
for (i = arr.length - 1; i > 0; i--) {
//隨機數生成器,範圍[0, i]
int rand = (new Random()).nextInt(i+1);
System.out.print(" " + rand);
int temp = arr[i];
arr[i] = arr[rand];
arr[rand] = temp;
}
//置換之後的數組
System.out.println("\n" + "置換後的數組:");
for (int k: arr)
System.out.print(" " + k);
}
}
分析:從運行結果可以看到隨着算法的進行,可供選擇的隨機數範圍在減小,與此同時此時數組裏的元素更加趨於無序。
四、潛在的偏差
在實現Fisher-Yates費雪耶茲隨機置亂算法時,可能會出現偏差,儘管這種偏差是非常不明顯的。原因:一是實現算法本身出現問題;二是算法基於的隨機數生成器。
1.實現上每一種排列非等概率的出現
在算法流程裏 j 的選擇範圍是從0...i-1;這樣Fisher-Yates算法就變成了Sattolo算法,共有(n-1)!種不同的排列,而非n!種排列。
j在所有0...n的範圍內選擇,則一些序列必須通過n^n種排列纔可能生成。
2.Fisher-Yates費雪耶茲算法使用的隨機數生成器是PRNG僞隨機數生成器
這樣的一個僞隨機數生成器生成的序列,完全由序列開始的內部狀態所確定,由這樣的一個僞隨機生成器驅動的算法生成的不同置亂不可能多於生成器的不同狀態數,甚至當可能的狀態數超過了排列,不正常的從狀態數到排列的映射會使一些排列出現的頻率超過其他的。所以狀態數需要比排列數高几個量級。
很多語言或者庫函數內建的僞隨機數生成器只有32位的內部狀態,意味着可以生成2^32種不同的序列數。如果這樣一個隨機器用於置亂一副52張的撲克牌,只能產生52! = 2^225.6種可能的排列中的一小部分。對於少於226位的內部狀態的隨機數生成器不可能產生52張卡片的所有的排列。
僞隨機數生成器的內部狀態數和基於此生成器的每種排列都可以生成的最大線性表長度之間的關係: