装球问题算法集锦

# -*- coding: utf-8 -*-
"""
Created on Thu Apr  4 09:19:06 2019

@author: Administrator
把m个同样的小球放在n个同样的盒子里,允许有的盒子空着不放,问共有多少种不同的分法?
其中5,1,1和1,5,1 是同一种分法。

算法分析:
回溯算法:有2种思路,一种是确保后面的盒子中球不比前面的少,
另一种是确保后面的盒子中球不比前面的多。  
第2种思路递归深度较少,但代码复杂些,特别要注意第n个盒子能放球的数量范围。   
基本上递归问题都可以转化为记忆化搜索,然后转换为动态规划问题。 
回溯算法相当于穷举,不但可以获得组合的数量,还可以存储具体的解空间。
记忆化搜索和动态规划算法均采用了确保后面的盒子中球不比前面的多的思路:
当没有球或者只剩1个盒子时,分法为1,即b[k][m] = 1;
当球比盒子少,则只需m个盒子,即b[k][m] = ms(m, m);
当球不少于盒子数量时,分成有空盒子和每个盒子至少装1个球情形,
即b[k][m] = ms(k-1, m) + ms(k, m-k)
动态规划的计算模型与记忆化搜索搜索相似,其中动态规划进行了降维优化。
"""
#确保后面的盒子中球不比前面的少
def fun_1(n, m):
    #k表示k号盒子,m表示共m个球
    def dfs(k, m):
        nonlocal s
        if k == n - 1:#最后一个盒子
            a[k] = m
            s += 1
            print(f'{s}:{a[:n]}')
        else:#至少还有2个盒子
            a[k] = a[k-1]
            while a[k] * 2 <= m:
                dfs(k+1, m-a[k])
                a[k] += 1
    s = 0
    a = [0] * n
    dfs(0, m)
    return s
 
#确保后面的盒子中球不比前面的多
def fun_2(n, m):
    #k表示k号盒子,m表示共m个球
    def dfs(k, m):
        nonlocal s
        #没有球或者已到最后一个盒子
        if m == 0 or k == n - 1:
            a[k] = m
            s += 1
            print(f'{s}:{a[:n]}')
        else:#至少还有2个盒子
            #确保后面的盒子中球不比前面的多
            a[k] = min(m, a[k-1])
            minNum = m//(n-k) + int(m%(n-k) != 0)
            while a[k] >= minNum:
                dfs(k+1, m-a[k])
                a[k] -= 1
    s = 0
    a = [0] * n
    for i in range(m, m//n + int(m%n != 0) - 1, -1):
        a[0] = i
        dfs(1, m-a[0])
    return s
    
#记忆化搜索
def fun_3(n, m):
    #计算给定k个盒子装m个球的方案总数
    def ms(k, m):
        if b[k][m] != 0:
            return b[k][m]
        if m == 0 or k == 1:
            b[k][m] = 1
        elif m < k:#球比盒子少,则只需m个盒子
            b[k][m] = ms(m, m)
        else:#分成有空盒子和每个盒子至少装1个球情形
            b[k][m] = ms(k-1, m) + ms(k, m-k)
        return b[k][m]
    
    #记录给定n个盒子装m个球的方案总数 
    b = [[0 for i in range(m+1)] for j in range(n+1)]
    b[0][0] = 1
    return ms(n, m)

#动态规划:使用二维数组 
def fun_4(n, m):
    #记录给定n个盒子装m个球的方案总数 
    b = [[0 for i in range(m+1)] for j in range(n+1)]
    #0个球放到i个盒子里 
    for i in range(n+1):
        b[i][0] = 1
    #j个球放到1个盒子里
    for j in range(m+1):
        b[1][j] = 1
    #计算给定i个盒子装j个球的方案总数
    for i in range(2, n+1):
        for j in range(1, i):
            b[i][j] = b[j][j]
        for j in range(i, m+1):
            b[i][j] = b[i-1][j] + b[i][j-i]
            
    return b[n][m]     

#动态规划优化1:使用2个一维数组 
def fun_5(n, m):
    #j个球放到1个盒子里 
    pre = [1] * (m + 1)
    cur = [0] * (m + 1)
    cur[0] = 1
    #计算给定i个盒子装j个球的方案总数
    for i in range(2, n+1):
        for j in range(1, i):
            cur[j] = pre[j]
        for j in range(i, m+1):
            cur[j] = pre[j] + cur[j-i]
        pre = cur[:]   
    return pre[m]    
  
#动态规划优化2:使用1个一维数组 
def fun_6(n, m):
    #j个球放到1个盒子里 
    f = [1] * (m + 1)
    #计算给定i个盒子装j个球的方案总数
    for i in range(2, n+1):
        for j in range(i, m+1):
            f[j] += f[j-i]
    return f[m]  
   
n, m = 3, 7
print(fun_1(n, m))
print(fun_2(n, m))
print(fun_3(n, m))
print(fun_4(n, m))
print(fun_5(n, m))
print(fun_6(n, m))

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值