Swift學習——n個骰子的總和

參考自:http://www.cnblogs.com/python27/archive/2011/11/26/2263332.html
(作者原話)題目:把n個骰子仍在地上,所有骰子的點數和爲s。輸入n,打印s所有可能取值的概率。

分析1:容易知道,有n個骰子的話,s的最小取值爲n(全爲1),最大取值爲6n(全爲6)。

如果只有1個骰子,那麼很簡單,s取1,2,3,4,5,6的情況數均爲1,概率爲1/6;設想有n個骰子,出現和爲s,我們可以這樣考慮,如果第一個骰子有6中情況,取1,2,3,4,5,6;那麼剩下的n-1個骰子的和則分別取s-1,s-2, s-3,s-4,s-5, s-6,我們將所有這些情況相加,就可以得出總的情況數。看出了嗎?親,這是什麼問題?對了,還是遞歸問題,根據以上分析我們不難寫出如下的遞歸公式:
這裏寫圖片描述

(作者原話)對公式的說明:(1)f(s,n)表示,有n個骰子,出現和爲s的情況總數;(2)對於公式第二行的解釋;如果有一個骰子,那麼點數爲8或者0的情況數,我們記爲0,這樣是爲了在計算遞歸時更爲方便所作的處理;例如有公式可知f(8,2)=f(7,1)+f(6,1)+f(5,1)+f(4,1)+f(3,1)+f(2,1),如果我們規定了f(7,1)=0那麼計算會方便很多。

有了上面的分析,我們可以寫出如下代碼:

var diceMaxValue:Int = 6

//計算給定diceNumber個骰子,出現和爲diceTotalSum的所有可能情況的總數
//int fun(int diceTotalSum, int diceNumber)
func mainFun(diceTotalSum:Int, diceNumber:Int)->Int{
    //骰子數少於1,錯誤
    if diceNumber < 1 {
        return 0
    }
    //骰子數等於1,如果超出1~6範圍便錯誤,未超出則是1
    if diceNumber == 1 {
        if (diceTotalSum >= diceNumber && diceTotalSum <= diceMaxValue*diceNumber) {
            return 1
        }
        else {
            return 0
        }
    }
    //n>1,採用遞歸
    if diceNumber > 1 {
        var sum:Int = 0
        for var index:Int = 1; index <= diceMaxValue; ++index {
            sum += mainFun(diceTotalSum-index, diceNumber-1)
        }
        return sum
    }
    return 0
}

//給定number個骰子,打印出現各種情況的概率
func printSumProbabilityOfDice1(number:Int) {
    if number < 1 {
        return
    }

    var maxSum:Int = number * diceMaxValue
    var pProbability:Array<Float> = Array()
    for index in number...maxSum {
        pProbability.insert(Float(mainFun(index, number)), atIndex: index-number)
    }
    var total:Int = Int(pow(Float(diceMaxValue), Float(number)))

    for count in 0...maxSum-number {
        pProbability[count] /= Float(total)
        println("\(count+number)\t\(pProbability[count])")
    }
}

(作者原話)分析2:和算法2中求解斐波那契數列的方法類似,遞歸的效率太差,我們可以正向來求解,假設我們有一個數組表示k-1個骰子中各點數的情況,令第s個分量表示和爲s時情況總數,那麼當有k個骰子是,其和爲s時的情況總數,就是表示k-1骰子的數組中的s-1,s-2,s-3,s-4,s-5,s-6的和(分別令引入的第k個骰子的值分別取1,2,3,4,5,6即可,其實和分析1的思路差不太多)。根據這個思想,我們可以用兩個數組交替表示數組k-1和數組k,於是我們有如下的代碼:

func printSumProbabilityOfDice2(number:Int){
    //創建並初始化一個二維數組
    var pProbability:Array = Array(count: 2, repeatedValue: Array(count: number * diceMaxValue + 1, repeatedValue: Double(0)))
    var flag:Int = 0
    //將第一個數組下標爲1~6的賦值爲1
    for index in 1...diceMaxValue {
        pProbability[flag][index] = Double(1)
    }

    //骰子數k從2到n循環;對於每一k,s取值爲[k,6k],對於每一個s計算前一個數組
    //的s-1,s-2,s-3,s-4,s-5,s-6;因爲前一個數組的最小值爲k-1,因爲因而有s-j>=k-1;
    for k in 2...number {
        for s in k...diceMaxValue*k {
            pProbability[1-flag][s] = 0
            for var j = 1; j <= diceMaxValue && j <= s - k + 1; j++ {
                pProbability[1-flag][s] += pProbability[flag][s-j]
            }
        }
        flag = 1 - flag
    }

    var total:Double = pow(Double(diceMaxValue), Double(number))
    for ss in number...number*diceMaxValue {
        pProbability[flag][ss] /= total
        println("\t\(pProbability[flag][ss])")
    }
}

(作者原話)最後分析:我們看到和【算法02】中提到的一樣,雖然該算法的時間複雜度提高了很多,但是動態創建了兩個數組,而且每一個的數組長度都沒分析1中的長度多了一個n,因而還是以空間換時間的思想。好了,這個算法就到這,祝各位愉快!

參考文獻:

【1】何海濤博客:http://zhedahht.blog.163.com/blog/static/254111742009101524946359/

【2】Wikipedia:http://en.wikipedia.org/wiki/Dice#Probability

【3】http://www.helium.com/items/1538174-how-to-calculate-probability-using-multiple-dice

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章