1. 程式人生 > >演算法--20181111--陣列分成長度相等兩個陣列,使兩部分和最接近

演算法--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的取法是否存在。