演算法--20181111--陣列分成長度相等兩個陣列,使兩部分和最接近
給定一個數組,長度為偶數,將陣列分成長度相等兩部分,使兩部分和最接近
先介紹01揹包,然後解決長度可以不相等的情況,最後解決該問題
問題0: 01揹包問題---k件商品,每件都有重量wi以及價格vi,給定一個袋子容量為W,求袋子中存放商品價值最大的取貨方案
令f(k,w)為袋裡可用容量為w時,取前k件商品的最大總價值
若已知f(k-1,w),求出f(k,w):第k件商品重量為wk,價格vk
若wk>w,則袋子容量不夠,不能取第k件 f(k,w) = f(k-1, w)
若wk<w,則袋子容量足夠,若取第k件,有總價值f(k-1,w-wk)+vk ,若不取則f(k-1, w) 有: f(k,w) = max{f(k-1,w-wk)+vk,f(k-1,w)}
遞迴方式解決:
def bag(k, w, v, W):
if k==-1:
return 0
if w[k]>W:
return bag(k-1, w, v, W)
else:
return max(bag(k-1,w,v,W), bag(k-1,w,v,W-w[k])+v[k])
臨界條件k==-1時,不論揹包可用容量如何,沒有商品可以取,直接返回0;當k=0時第一件商品,若w[0]>W,則返回bag(-1,W)=0
若w[0]<w則返回max(bag(-1,W),bag(-1,W-w0)+v[0])
非遞迴方式:
動態規劃方法,利用輔助陣列dp[k+1][W+1],其中dp[0][:]表示沒有商品時總價值 dp[:][0]表示揹包容量為0總價值,對於每一種商品,對於每個容量,利用遞推式來進行遞推求解。
def bag2(k, w, v, W): #k為商品總數 #dp = [[0]*(W+1)]*(k+1) #dp = [([0]*(W+1)) for i in range(k+1)] dp = [[0 for i in range(W+1)] for i in range(k+1)] for i in range(W+1): dp[0][i] = 0 for i in range(k+1): dp[i][0] = 0 print (mat(dp)) for i in range(1,k+1): for j in range(1,W+1): if j<w[i-1]: dp[i][j] = dp[i-1][j] else: dp[i][j] = max(dp[i-1][j],dp[i-1][j-w[i-1]]+v[i-1]) print (i,j,w[i-1],dp[i][j],dp[i-1][j],dp[i-1][j-w[i-1]]+v[i-1]) print (mat(dp)) #print (dp) return dp[k][W]
python中二維陣列的宣告方式:
dp = [[0]*(W+1)]*(k+1) 不要輕易使用list*n 後面均是第一個元素的淺拷貝,太坑,這種方式 是錯誤的。
dp = [([0]*(W+1)) for i in range(k+1)]
dp = [[0 for i in range(W+1)] for i in range(k+1)]
初始化陣列,並將第一行以及第一列置為0:
從第二行(第一件商品)開始遍歷,每次增大容量值,計算最大價值,當計算到dp[1][2]時,此時容量=2,而第一件商品重量w[0]=2,即可以放入揹包中,此時dp[1][2]=3 同理對於第一件商品而言,之後繼續增大容量有:
最終的陣列:
問題1:將陣列分成兩部分(個數可以不相等),使其兩部分和最接近
假定陣列數字之和為sum, 可以轉化為在一個數組中挑選一部分數使其最接近sum/2。
利用01揹包來解決:
令dp[i][j]表示從前i個數中取任意個數,且這些數之和為j的取法是否存在
dp[:][0]第一列表示從前幾個數中取數使得和為0,易知 dp[:][0]=True
dp[0][1:]第一行除去第一個 表示不取數,使得和從1到目標值的取法,易有 dp[0][1:] = False
def splitArray(num):
n = len(num)
sum = 0
for i in range(n):
sum = sum + num[i]
target = int(sum/2)
#dp[i][j]從前i個數中隨意挑選一些數 其和是否可以等於j
dp = [[False for i in range(target+1)] for i in range(n+1)]
for i in range(n+1):
dp[i][0] = True
for i in range(1, target+1):
dp[0][i] = False
print (mat(dp))
for i in range(1, n+1):
for j in range(0, target+1):
if j>=num[i-1]:
dp[i][j] = dp[i-1][j-num[i-1]] or dp[i-1][j]
else:
dp[i][j] = dp[i-1][j]
print (mat(dp))
print (mat(dp))
for j in range(target, 0, -1):
if dp[n][j]:
print(sum - 2*j)
return sum - 2*j
return None
接下來對於每一行,依次測試目標值從1逐漸增大是否可以得到
最終結果是最後一行中,最接近目標值的數
輸入陣列 num=[2,3,4,5,9]
初始化:
計算第一個數可以得到的值: 第一個數為2 從前1個數中可以得到的和只有2,即第三列=true
最終結果:
解法2:
dp[k][s] 表示從前k個數中取任意個數,且這些數之和為s的取法是否存在
外階段:在前k1個數中進行選擇,k1=1,2...n。
內階段:從這k1個數中任意選出k2個數,k2=1,2...k1。
狀態:這k2個數的和為s,s=1,2...sum/2。
決策:決定這k2個數的和有兩種決策,一個是這k2個數中包含第k1個數,另一個是不包含第k1個數。
range函式(a,b,index)從[a,b)以步長index遞進 注意不包含b
def splitArray2(num):
n = len(num)
sum = 0
for i in range(n):
sum = sum + num[i]
target = int(sum/2)
#dp[i][j]從前i個數中隨意挑選一些數 其和是否可以等於j
dp = [[False for i in range(target+1)] for i in range(n+1)]
for i in range(n+1):
dp[i][0] = True
for i in range(1, target+1):
dp[0][i] = False
print (mat(dp))
#從前i個數中取i個數,可以實現的和s有哪些
for i in range(1,n+1):
#print (i)
for j in range(i, 0, -1):
for s in range(1, target+1):
print (i,j,s)
if s>=num[i-1] and dp[j-1][s-num[i-1]]:
dp[j][s] = True
print (mat(dp))
#從前i個數中取任意個數,可以實現的和s有哪些
for i in range(2, n+1):
for s in range(1, target+1):
if dp[i-1][s]:
dp[i][s] = True
for s in range(target, 1, -1):
if dp[n][s]:
print(sum - 2*s)
return sum - 2*s
return None
基於動態規劃陣列,第一種解法,在初始化第一行以及第一列之後,然後逐行填充陣列,最後返回結果;
解法二也是基於陣列,初始化陣列之後,首先遍歷前i個數中計算取前i個可以的和有哪些,i表示前i個數,j表示遍歷這i個數,觀察目標值1--target計算哪些值可以計算得到
然後計算從前i個數中取任意個數,可以的和有哪些,對應於矩陣的列,若dp[i][j]==true則dp[i+1][j]也可以得到
初始化:
計算前k個數可以得到的和有哪些:
前k個數去任意個數:
問題2:給定一個數組,長度為偶數,將陣列分成長度相等兩部分,使兩部分和最接近
選出的物體數必須為n/2
dp[k][s]表示從前k個數中取k個數,且k不超過n/2,且這些數之和為s的取法是否存在
def splitArray3(num):
n = len(num)
l = int(n/2)
sum = 0
for i in range(n):
sum = sum + num[i]
target = int(sum/2)
#dp[i][j]從前i個數中隨意挑選一些數 其和是否可以等於j
dp = [[False for i in range(target+1)] for i in range(n+1)]
for i in range(n+1):
dp[i][0] = True
for i in range(1, target+1):
dp[0][i] = False
print (mat(dp))
#從前i個數中取i個數(i<n),可以實現的和s有哪些
for i in range(1,n+1):
#print (i)
for j in range(min(l,i), 0, -1):
for s in range(1, target+1):
print (i,j,s)
if s>=num[i-1] and dp[j-1][s-num[i-1]]:
dp[j][s] = True
print (mat(dp))
for s in range(target, 0, -1):
if dp[l][s]:
print(sum - 2*s)
return sum - 2*s
return None
注意最後尋找要從n/2行尋找,因為每部分的個數為n/2,即必須從陣列中取出n/2個數來不斷逼近sum/2
輸入num=[2,3,0,5]
初始化
最終dp陣列:
dp[k][s]代表從前k個數中取k個數,且k不超過n/2,且這些數之和為s的取法是否存在。