深搜算法之石子划分问题

#!/usr/bin/python3
#文件:深搜算法之石子划分问题
#作者:巧若拙
#日期:2019年1月4日
'''
给出n堆石子,以及每堆石子数。请将它们分为两堆,使得这两堆的总石子数差最小。
输入n,以及每堆石子数,输出分为两堆后的最小差值。
比如,n=4,四堆石子分别有13,6,8,14颗,则可以分为13+8和14+6的两堆,它们的最小差为1。
分堆算法为
(1)求得所有石子数total,以及它的一半half;
(2)在所有石子堆中作适当选择,对每种选择方案,求不超过half的己选中堆中的石子总数的最大值mx。
所求即为(total-max)-max。
(3)以a(j)表示第j堆石子数;以b(j)表示第j堆石子是否被选中,
如果b(j)=1,表示第j堆被选中,如果b(j)=0表示第j堆没有被选中。
(4)各种方案的表达及次序如下:
以00…00(均不选中),00…01(只选中第n堆石子),00…10(只选中第n-1堆石子),
00…11(选中第n-1堆和第n堆石子),00…100(选中第n-2堆石子),
00…101(选中第n-2堆和第n堆石子),11…11(选中所有n堆石子)。
算法分析:
本题算法思想与“广搜算法之翻转棋子游戏”如出一辙。
初看本题,最容易想到的是穷举法,用包含n个元素的列表b分别表示堆石子的选择状态,b[i]=1表示选择第i堆石子,b[i]=0表示不选择。
可以穷举从b=[0,0,...0,0]到b=[1,1,...1,1],即从都不选择到都选择。
若把列表b的值对应整数t的n位二进制数,则相当于穷举从t=0到t=2^n-1。
比较直白的算法是循环遍历每一个t,将t的n位二进制数存储到列表b;然后遍历b,若b[i]=1则选择第i堆石子;
完成本选择模式后,判断是否有解,若本选择模式能获得更好的解,则更新最优解。
上述算法思想简单明了,但把整数t转化成二进制数并存储到列表b的操作比较耗费时间。
我们可以使用位运算来处理整数t的二进制数,这样就无需引入列表b,可以直接操作整数t了,效率有所提升。
穷举法是容易想到的算法,但是效率实在太低,我们可以使用深度优先搜索(回溯加剪枝)来实现同样的功能。
这样我们就分别用穷举和深搜两种算法实现了所需功能。
回到最初的想法,我们使用列表b来表示整数t的二进制数的每一位,是很直观的想法,只不过每次都需要把整数转化成二进制数比较耗费时间;
其实我们可以直接修改列表b的值来模拟整数t递增的过程,这样就不需要引入整数t及其位运算了,数据结构更清晰。
我也用这种数据结构实现了穷举和深搜两种算法。
进一步思考:当石子堆数量n太大时,对应的n位二进制数太大了,用穷举法和回溯法都不适合;
因为我们对每个石子堆的操作都是选择或者不选择,所以本题可以当做一个0-1背包问题来解决(当然石子的总数不能太大,否则需要太大的空间),
我们可以把背包的容量用石子总量的一半half来代替,物品的质量和价值都用每堆石子的数量来代替,最大价值就是不超过half的石子数量,
这样就可以套用0-1背包问题的基本模型来解决本题。
'''

#将整数t的n位二进制数存储到列表d
def binary_number(t, n): 
    d =[0] * n #高位补零,凑足n位
    i = n -1
    while t > 0:
        d[i] = t & 1         #相当于t % 2
        i, t = i - 1, t >> 1 #相当于t // 2
    return d

#穷举法解石子划分问题:最直观的想法,把整数t的n位二进制数存储到列表b,并根据列表b的值来确定选择模式
def divide_stones_0(a):
    n, max_s = len(a), 0
    for t in range(1<<n): #遍历从[0,1<<len(a)]的所有选择模式
        b = binary_number(t, n) #将整数t的n位二进制数存储到列表b
        s = 0
        for i in range(n):
            s += a[i] * b[i] #累计被选中的石子堆的数量
        if s > max_s and s <= half: #更新最优解
            max_s = s
            d = b.copy()  
    s1, s2 = 0, 0
    for i in range(len(

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值