动态规划: Subset Sum

这次我想给大家分享的内容是动态规划Subset Sum (子集之和)问题 。其实挺枯燥的,涉及到的是数学和编程方面的内容,如果不感兴趣请直接退出阅读。

本文概要:

  • Subset Sum 问题描述
  • 问题求解思路
  • 递归法求解
  • 重点:动态规划法求解

Subset Sum 问题


假设有一串 { 1,2, 4,5,6} , 是否存在任意数字之和为 7 , 55 。
通过目测 { 1, 2,4} {5, 2} 之和为7,所以答案为存在,因为所有元素之和小于55,所以答案为不存在。

问题:

假设有一个非负整数的集合 Set ,求是否存在一个组合使得其中任意元素之和为一个给定的整数 sum ?

A = { X1,X2,....Xn}, sum = Y
subset(A,Y) = True or False ?

求解思路


  1. 我们立马能想到第一个方法是先求解集合A 所有的排列组合,一共有2的5次方种可能,然后逐一对组合求和判断是否等于6。这种思路属于暴利求解法,当集合元素非常多的时候,计算时间会指数级增长,该算法的时间复杂度为O(2^n)。

  2. 第二种求解方法的思想是 把大事化小,小事化无的

如: A = { 1,2, 4,5,6} 可以简化为 { 2, 4,5,6 } 这个子集合元素是否有sum = 6 的解,或者{ 2, 4,5,6 } 是否存在相加之和为5 的解 。依次类推可以将问题一层一层缩小,利用上篇文章提到的递归思想。

递归求解法


arr  = [1,2 ,4,5,6]

def  subset(arr , i , s):
    if i == 0:
        return s == arr[0]
    elif s == 0:
        return True
    elif arr[i] > s :
        return rec_subset(arr, i-1 , s)
    else:
        A =  subset(arr , i-1 ,s - arr[i])
        B =  subset(arr, i-1 , s)
        return A or B
    
print( subset(arr ,len(arr )-1, 7))
out: True

通过递归方式来求解必须遍历所有的子问题,但其中子问题有重叠的部分,也就是说存在重复计算的情况,算法时间复杂度为O(2^n)。对于规模比较大的问题这是无法容忍的。接下来通过动态规划的方法 ,来避免重复计算来优化程序的性能。

动态规划求解法


动态规划是从下往上(bottom-up)的求解方法,并且把中间结果缓存起来避免重复计算。
例如:把{ 1,2, 4,5,6} 分解成 空集 0 { } ,1 { 1 } ,2 { 1,2 } ,3 { 1,2, 4 }, ,4 { 1,2, 4,5 } ,5 { 1,2, 4,5,6} 分别来计算,具体流程如下。

  1. 构建一个二维数组Subset[i,j] 来存放子集合相加和的所有可能情况,True(以下记为T)表示存在解,False(以下记为F)表示不存在解。
动态规划: Subset Sum_第1张图片
step1

如图1: 行为子集相加为0-7所有的情况,列为0-5个元素的所有情况。
第一行表示: 除了第一列为0存在解标记为T 之外,其余1-7都为 F 。

第一列表示:各个子集相加之和为0的情况。例如 {1 } 的情况显然如果 不取元素1 空集{}之和存在0的解,类似{1,2} 也有存在0的解,因此第一列都为T。

第二行第二列:表示{1} 之和为1,显然为T 。

第二行其余列:因为最大为1,所以余下都为F。

动态规划: Subset Sum_第2张图片
step2

中间任意一个格子计算方法,分两种情况。

如果格子上方为T,当前格子也为T。实际意义为:当前集合的子集如果有解那么,当前集合也有解,如{1,2,4} 之和3的情况,因为 {1,2} 存在之和为3的解。
因此得出结论,只要T下方的格子都可以设置为T。

如果格子上方为F, 则求 sum - A 余下子集合的解。例如上图绿色标记的位置 Subset[2,4] , 可以简化为求解 { 1 } 是否有 1的解,查表值为T,当前表格值则 为T。

用此算法,把表格填满。

动态规划: Subset Sum_第3张图片
step3

表格最后一个元素就是 { 1,2, 4,5,6} 是否存在子集合之和为7 这个问题的解。

具体的代码实现:


import numpy as np

def dp_subset(arr,S):
    
    subset = np.zeros((len(arr),S+1),dtype=bool) #构造二维数组
    subset[:,0] = True # 第一列 设为True
    subset[0,: ] = False #第一列 设为 False
    subset[0,arr[0]] = True
    
    for i in range(1,len(arr)):
        for s in range(1, S+1):
            if arr[i] > s:
                subset[i , s] = subset[i-1 , s]
            else:
                A = subset[i-1 , s ]
                B = subset[i-1 , s - arr[i]]
                subset[i,s] = A or B
                
                
    row ,cell = subset.shape
    return subset[row-1,cell-1]
            
arr  = [1,2, 4,5,6]            
dp_subset(arr,7) 
out:True

参考


  1. 视频讲解动态规划

  2. Dynamic Programming – Subset Sum Problem

  1. Algorithm/Insights

你可能感兴趣的:(动态规划: Subset Sum)