編寫一個方法,洗一副牌。要求作到完美洗牌,換言之,這幅牌52!種排列組合出現的機率相同。假設給定一個完美的隨機發生器

解法:假定有個數組,含有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")
			}
		}
	}
}

java實現隨機洗牌算法

 

複製代碼

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

相關文章
相關標籤/搜索