美文网首页
彩票的注数算法(Swift)

彩票的注数算法(Swift)

作者: Hanfank | 来源:发表于2019-08-27 11:37 被阅读0次

    验证计算结果可以用这个排列组合计算器
    计算注数前先要了解清楚什么是排列,什么是组合。推荐文章:5分钟彻底了解排列组合

    排列组合计算公式简介

    排列组合公式:排列组合公式大全

    排列数计算公式 组合数计算公式

    排列组合公式例子:排列组合算法,排列组合例题,排列组合计算

    排列计算公式例子 组合计算公式例子

    Factorial 阶乘

    在公式中我们需要先计算阶乘,然后再计算排列组合算法,这里再阐述阶乘的公式:

    n! // 表达式
    5! = 5 * 4 * 3 * 2 * 1 = 120 //例子
    

    很简单不是吗,n每次减1,再相乘。
    根据公式我们可以写出阶乘的算法:

    // 阶乘算法
    func factorial(_ n: Int) -> Int {
        var n = n
        var result = 1
        while n > 1 {
            result *= n
            n -= 1
        }
        return result
    }
    

    尝试打印下结果

    factorial(5) // return 120
    factorial(6) // return 720
    factorial(7) // return 5040
    factorial(20) // return 2432902008176640000 溢出
    

    请注意:使用此函数不能计算超过20的阶乘数,否则会得到整数溢出的结果。

    Permutations 排列

    接下来我们来编写排列算法,回顾一下公式:


    排列数计算公式

    排列算法如下:

    //排列算法
    func permutations(_ n: Int, _ r: Int) -> Int{
        return factorial(n) / factorial(n-r) 
    }
    

    尝试计算结果:

    permutations(5, 3)   // return 60
    permutations(6, 3)  // return 120
    permutations(7, 3)   // return 210
    

    在这个算法中有一个问题,由于计算时使用了阶乘算法,阶乘数过大时会导致计算结果整型数溢出,最终导致计算不准确,例如p(21,3)是计算不出结果的。可以通过swift在线运行测试

    优化排列算法
    实际上,计算排列数的可能性不需要按照计算全部的阶乘。
    例如计算p(5,3)=60,仅需要计算5 * 4 * 3 = 60。这样可以大大加快计算速度。看算法:

    //排列数计算
    func permutations(_ n: Int, _ r: Int) -> Int {
      var n = n
      var answer = n
      for _ in 1..<r { //注意r
        n -= 1
        answer *= n
      }
      return answer
    }
    

    试运行结果

    permutations(5, 3)   // returns 60
    permutations(50, 6)  // returns 11441304000
    permutations(9, 4)   // returns 3024
    

    其中计算permutations(9, 4)是这么计算的。

              9 * 8 * 7 * 6 * 5 * 4 * 3 * 2 * 1
    P(9, 4) = --------------------------------- = 9 * 8 * 7 * 6 = 3024
                              5 * 4 * 3 * 2 
    

    解释:计算结果时只需要计算遍历4次即可计算出结果,也就是9 * 8 * 7 * 6,次数正好来源于选择数的r值。每次让n递减,并相乘。

    但事实上,算法仍然不能计算特别大的值,例如P(30,15),这个数值已经超出了swift 语言的最大整数,同样会导致整型数溢出。

    打印排列数
    前面已经介绍了如何计算排列数,那么我们如何打印出每个排列数呢?

    来自Niklaus Wirth的递归算法:

    func permuteWirth<T>(_ a: [T], _ n: Int) {
        if n == 0 {
            print(a)   // display the current permutation
        } else {
            var a = a
            permuteWirth(a, n - 1)
            for i in 0..<n {
                a.swapAt(i, n)
                permuteWirth(a, n - 1)
                a.swapAt(i, n)
            }
        }
    }
    

    使用方法如下:

    let letters = ["a", "b", "c", "d", "e"]
    permuteWirth(letters, letters.count - 1)
    

    结果输出:

    ["a", "b", "c", "d", "e"]
    ["b", "a", "c", "d", "e"]
    ["c", "b", "a", "d", "e"]
    ["b", "c", "a", "d", "e"]
    ["a", "c", "b", "d", "e"]
    ...
    

    这和我们之前看到的结果是一样的,会有120中不同的排列方式。

    Combinationes 组合数

    根据阶乘的算法写出组合的算法,回顾一下组合数计算公式:


    组合数计算公式
    // 组合数算法
    func combinations(_ n: Int, choose r: Int) -> Int {
      return permutations(n, r) / factorial(r)
    }
    let combinations = combinations(28, choose: 5)    // prints 98280
    

    这个方法同样受限于阶乘的限制,一旦阶乘超过20就无法结算准确数值了。
    有一种更快的计算方式可以避免这个情况,这种方法仍然来源于C(n,r)公式:

                    n!                      n * (n - 1) * ... * 1
    C(n, r) = ------------- = ------------------------------------------
              (n - r)! * r!      (n - r) * (n - r - 1) * ... * 1 * r!
    

    通过变形可以得到以下计算方式:

                   n * (n - 1) * ... * (n - r + 1)         (n - 0) * (n - 1) * ... * (n - r + 1)
    C(n, r) = --------------------------------------- = -----------------------------------------
                               r!                          (0 + 1) * (1 + 1) * ... * (r - 1 + 1)
    

    写出算法如下:

    func quickBinomialCoefficient(_ n: Int, choose r: Int) -> Int {
      var result = 1
      for i in 0..<r {
        result *= (n - i)
        result /= (i + 1)
      }
      return result
    }
    

    使用方法:

    quickBinomialCoefficient(8, choose: 2)     // prints 28
    quickBinomialCoefficient(30, choose: 15)   // prints 155117520
    

    除了以上计算组合数的方案之外,还有另外一种速度更快的方案,二项式系数算法:
    这种新方法速度非常快,但仍有限制数量的大小。毫无问题C(30,15)计算是没问题的,但像C(66,33)这个大小仍然会导致分子中的整数溢出。这是一种使用动态编程来克服计算阶乘和分割的算法。它基于Pascal的三角形:

    0:               1
    1:             1   1
    2:           1   2   1
    3:         1   3   3   1
    4:       1   4   6   4   1
    5:     1   5  10   10  5   1
    6:   1   6  15  20   15  6   1
    

    下一行中的每个数字都是通过添加前一行中的两个数字来组成的。例如,在行6中,通过从行5添加5和10来产生数字15.这些数字被称为二项式系数,并且当它们发生时它们与C(n,k)相同。

    // 组合数算法4 二项式系数
    func binomialCoefficient(_ n: Int, choose r: Int) -> Int {
        var bc = Array(repeating: Array(repeating: 0, count: n + 1), count: n + 1)
        for i in 0...n {
            bc[i][0] = 1
            bc[i][i] = 1
        }
        if n > 0 {
            for i in 1...n {
                for j in 1..<i {
                    bc[i][j] = bc[i - 1][j - 1] + bc[i - 1][j]
                }
            }
        }
        return bc[n][r]
    }
    
    

    使用方法

    binomialCoefficient(66, choose: 33)   // 打印一个大数
    

    注数计算

    完成上面的排列组合算法,接下来处理注数计算的问题。
    从上面的算法中,我们可以获得两个算法:
    排列算法

    //排列数算法
    func permutations(_ n: Int, _ r: Int) -> Int {
      var n = n
      var answer = n
      for _ in 1..<r { //注意r
        n -= 1
        answer *= n
      }
      return answer
    }
    

    和组合数算法

    // 组合数算法4 二项式系数
    func binomialCoefficient(_ n: Int, choose r: Int) -> Int {
        var bc = Array(repeating: Array(repeating: 0, count: n + 1), count: n + 1)
        for i in 0...n {
            bc[i][0] = 1
            bc[i][i] = 1
        }
        if n > 0 {
            for i in 1...n {
                for j in 1..<i {
                    bc[i][j] = bc[i - 1][j - 1] + bc[i - 1][j]
                }
            }
        }
        return bc[n][r]
    }
    

    在彩票游戏中,最经典的彩种是时时彩,通过计算时时彩的玩法简单介绍如何计算注数:
    时时彩组选120:号码一共10个数,选中5个,在5个中取1个进行组合,一共有多少组合?
    通过组合算法得到结果:

    let c = binomialCoefficient(5, choose: 5)    // prints 1
    let c = binomialCoefficient(10, choose: 5)    // prints 252
    

    五星直选复式

    //五星直选复式
    let an = permutations(5, 1)
    let an1 = permutations(2, 1)
    let an2 = permutations(1, 1)
    let an3 = permutations(1, 1)
    let an4 = permutations(1, 1)
    let ans = an * an1 * an2 * an3 * an4
    

    简单介绍两种玩法,查看更多玩法
    喜欢的话给个Star。

    相关文章

      网友评论

          本文标题:彩票的注数算法(Swift)

          本文链接:https://www.haomeiwen.com/subject/phtjectx.html