解法:假定有個數組,含有n個元素,相似以下:html
[1][2][3][4][5]java
利用簡單構造法,咱們不妨先問本身,假定有個方法shuffle(...)對n-1個元素有效,咱們能夠用它來打亂n個元素的次序嗎?固然能夠,並且很是容易實現。咱們會先打亂前n-1個元素的次序,而後,取出第n個元素,將它和數組中的元素隨機交換。就這麼簡單!遞歸解法的算法以下:算法
//lower和highter(含)之間的隨機數 int rand(int lower,int highter) { return lower+(int)(random()*(highter-lower+1)); } void shuffleArrayRecursively(int cards[],int i) { if(i==0) return; shuffleArrayRecursively(cards,i-1); int k=rand(0,i); int temp=cards[i]; cards[i]=cards[k]; cards[k]=temp; return; }
以迭代方式實現的話,這個算法又會是什麼樣?讓咱們先考慮,咱們要作的是遍歷整個數組,對每一個元素i,將array[i]與0到i(含)之間的隨機數交換。數組
void suffleArrayInteratively(int cards[],n) { for(int i=0;i<n;i++) { int k=rand(0,i); int tmp=cards[k]; cards[k]=cards[i]; cards[i]=tmp; } }
洗牌問題(shuffle)就如隨機取樣(random sample)問題,在《計算機程序設計藝術》(volume 2 chapter 3)中獲得了詳細的講解,關於該問題的詳細探討能夠翻閱該書相應章節。dom
洗牌問題,顧名思義,就是給你一把牌,讓你把它徹底打亂,這能夠歸結成一個數組問題:函數
給你一個長度爲n的數組,要求你將其徹底打亂,數組中元素交換跟下標是一一對應的,因此也就能夠表述爲給你一個有序序列0—n-1,要你將其徹底打亂,要求每一個元素在任何一個位置出現的機率均爲1/n。ui
洗牌問題是打亂一個有序序列(好比下標有序)的算法與隨機取樣很有淵源,算法也與隨機取樣問題十分相近,以下:this
void shuffle(T* arr, int len)spa
{.net
for(int i=0; i<len; i++)
{
int idx=rand()%(i+1);
swap(arr[idx], arr[i]);
}
}
算法正確性證實也能夠用數學概括法證實:
待證實問題:對於一個長度爲n的數組,通過上述算法處理後,會獲得一個隨機數組,原數組中每個元素在任何一個位置的機率均爲1/n
證實:算法能夠分爲兩部分:前n-1次執行+最後一次執行
一、當n=1時,idx必爲0,因此元素arr[0]在任何一個位置的機率爲1/1,命題成立。
二、假設當n=k時,命題成立,即n=k時,原數組中任何一個元素在任何一個位置的機率爲1/k。
當n=k+1時,當算法執行完k次時,前k個元素在前k個位置的機率均爲1/k,執行最後一步時,前k個元素中任何一個元素被替換到第k+1位置的機率:
(1-(1/k)*(k/k+1)) * (1/k) = 1/k+1
因此,對於前k個元素,它們在k+1的位置上機率爲1/k+1,在前面k個位置任何一個位置上的機率爲(1-1/(k+1)) * (1/k)=1/(k+1),對於前k個元素,其在整個數組前k+1個位置上的機率均爲1/k+1,
對於第k+1個元素,其在原位置的機率爲1-(k/k+1)=1/k+1,在前k個位置任何一個位置的機率爲:(k/k+1) * (1/k)=1/k+1,因此對於第k+1個元素,其在整個數組前k+1個位置上的機率也均爲1/k+1。
命題得證。
能讓我理解的隨機洗牌問題。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。
問題描述:假設有一個數組,包含n個元素。如今要從新排列這些元素,要求每一個元素被放到任何一個位置的機率都相等(即1/n),而且直接在數組上重排(in place),不要生成新的數組。用O(n) 時間、O(1)輔助空間。
算法是很是簡單了,固然在給出算法的同時,咱們也要證實機率知足題目要求。
先想一想若是能夠開闢另一塊長度爲n的輔助空間時該怎麼處理,顯然只要對n個元素作n次(不放回的)隨機抽取就能夠了。先從n個元素中任選一個,放入新空間的第一個位置,而後再從剩下的n-1個元素中任選一個,放入第二個位置,依此類推。
按照一樣的方法,但此次不開闢新的存儲空間。第一次被選中的元素就要放入這個數組的第一個位置,但這個位置原來已經有別的(也可能就是這個)元素了,這時候只要把原來的元素跟被選中的元素互換一下就能夠了。很容易就避免了輔助空間。
咱們先假設一個5維數組:1,2,3,4,5。若是第1次隨機取到的數是4, 那麼咱們但願參與第2次隨機選取的只有1,2,3,5。既然4已經不用, 咱們能夠把它和1交換,第2次就只須要從後面4位(2,3,1,5)中隨機選取便可。同理, 第2次隨機選取的元素和數組中第2個元素交換,而後再從後面3個元素中隨機選取元素, 依次類推。
C++實現:
void RandomShuffle(int a[], int n){ for(int i=0; i<n; ++i){ int j = rand() % (n-i) + i;// 產生i到n-1間的隨機數,依次從n個元素,n-1個元素,n-2個元素中取得一個元素。 Swap(a[i], a[j]); }
思路:咱們有n張牌,不妨先假設有一個洗牌函數shuffle(....),能完美的洗出n-1張牌 。拿第n張牌來打亂前面n-1的洗牌順序,從而獲得n張牌的最終結果。
舉例說明:若是1,2,3三張牌,想完美洗牌,那麼先讓1,2洗牌洗好,再把3與其中之一(隨機選取)進行交換,因此是遞歸思想,而非循環思想,差異是遞歸是等洗出n-1張牌再拿第n張牌去交換,若是要循環作,就是第n張牌去換一下n-1張牌其中之一
遞歸代碼:
package main import ( "fmt" "math/rand" "time" ) func randNum(low, high int) int { r := rand.New(rand.NewSource(time.Now().UnixNano())) return low + r.Intn(high-low+1) } func shuffle(arr []int, n int) { if n <= 0 { return } shuffle(arr, n-1) rand := randNum(0, n) arr[n], arr[rand] = arr[rand], arr[n] } func main() { cards := []int{ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52} cardslen := len(cards) cardslen1 := cardslen / 4 for i := 1; i <= 10; i++ { fmt.Printf("\n") shuffle(cards, cardslen-1) for j := 1; j <= cardslen; j++ { fmt.Printf("%d ", cards[j-1]) if j%cardslen1 == 0 { fmt.Printf("\n") } } } }
import java.util.Random; class Card { public String num; public String suit; Card(String n,String s) { this.num=n; this.suit=s; } public String toString() { String ss=suit+":"+num+" "; return ss; } } class DeskOfCard { Card card[]; public void initcard()//初始化 { String num[]={"A","2","3","4","5","6","7","8","9","10","J","Q","K"}; String suit[]={"方塊","梅花","紅桃","黑桃"}; card = new Card[52]; for(int i=0;i<52;i++) { card[i] = new Card(num[i%13],suit[i/13]); } } public void shufflecard()//洗牌 { Random rd = new Random(); for(int i=0;i<52;i++) { int j = rd.nextInt(52);//生成隨機數 Card temp = card[i];//交換 card[i]=card[j]; card[j]=temp; } } public void dealcard()//發牌 { for(int i=0;i<52;i++) { if(i%4==0) System.out.println("\n"); System.out.print(card[i]); } } } public class TestCard { public static void main(String[] args) { DeskOfCard cc = new DeskOfCard(); cc.initcard(); cc.shufflecard(); cc.dealcard(); } }
這個算法的要求是這樣的:將N個數亂序後輸出.因爲和撲克牌的洗牌過程比較類似因此我也就稱爲洗牌算法了.不少地方都不自覺的須要這個算法的支持.也能夠將這個算法擴展爲從N個數中取出M個不重複的數(0
思路:
有n個數據的數據列,從第一個元素開始,隨機取出數據列中元素與之交換,依次進行n次交換,便可獲得一個隨機排列的數據列
代碼實現:
public class ShuffleSortTest {
public static void main(String[] args) {
int[] data = new int[] { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 };
print(data);
shuffleSort(data);
System.out.println("排序後的數組:");
print(data);
}
public static void swap(int[] data, int i, int j) {
if (i == j) {
return;
}
data[i] = data[i] + data[j];
data[j] = data[i] - data[j];
data[i] = data[i] - data[j];
}
public static void shuffleSort(int[] data) {
for (int i = 0; i < data.length - 1; i++) {
int j = (int) (data.length * Math.random());
swap(data, i, j);
}
}
public static void print(int[] data) {
for (int i = 0; i < data.length; i++) {
System.out.print(data[i] + "\t");
}
System.out.println();
}
}
運行結果
0 1 2 3 4 5 6 7 8 9
排序後的數組:
0 7 4 1 8 6 5 2 9 3
每次的運行結果都是隨機的
參考:http://blog.csdn.net/sunnyyoona/article/details/43795243