P問題, NP問題, NPC問題, NP-hard問題
- 基本概念
- 復雜度級別: 1)多項式級別O(nk);2)非多項式級別,如,指數級O(an)和階乘級別O(n!)。后者的復雜度無論如何都大於前者。
- 歸約(約化):如果能找到這樣一個多項式變換法則,對任意一個程序A的輸入,都能按這個法則變換為程序B的輸入,使兩程序的輸出相同,那么我們說,問題A可歸約為問題B。
- 通俗解釋:一個問題A可以歸約為問題B指,可以用問題B的解法解決問題A,或者說,問題A可以“變成”問題B。
- 特點:“問題A可歸約為問題B”有一個直觀意義,B的時間復雜度高於或者等於A的時間復雜度,既,問題A不比問題B難。
- 性質:傳遞性。如果問題A可以歸約為問題B,問題B可以歸約為問題C,則問題A一定可以歸約為問題C。
- P問題, NP問題, NPC問題, NP-hard問題的定義和相互關系
- P問題(polynomial):求解一個問題的時間復雜度是多項式級別
- NP問題(nondeterministic polynomial):可以在多項式時間里驗證解是否正確的問題。定義NP問題的意義在於,如果一個問題不能在多項式時間驗證,則這個問題一定沒有多項式時間的算法。
- 圖中某條路是否是Hamilton回路,可以在多項式時間驗證,是NP問題圖中
- 是否不存在Hamilton回路,不可以在多項式時間驗證。
- NPC問題(nondeterministic polynomial complete):
- 定義:一個問題1)它是NP問題;2)所有的問題都可以約化到它,這樣的問題稱為NPC問題。
- 證明:1)先證明它是NP問題;2)再證明其中一個已知的NPC問題能約化到它(由約化的傳遞性,如果A能約化到B,則B的時間復雜度不低於A)。
- 特點:NPC問題目前沒有多項式的有效解法,只能有指數級或階乘級復雜度的算法搜索
- NP-hard問題(nondeterministic polynomial - hard):滿足NPC問題的第2條但是不一定滿足第1條。即使NPC問題獲得了多項式級別的求解算法,NP-hard問題可能仍然找不到多項式級的算法。
- 他們之間的關系:
- P問題一定是NP問題,當前無法證明NP問題是否是P問題。但普遍認為P≠NP。於是NP問題包含P問題。
- NP問題可以歸約為NPC問題,所以NP問題包含NPC問題
參考資料:
動態規划解背包問題
一維背包問題
def dynamicAlgorithm_Knapscak(w, v, b):
"""用動態規划方法求解背包問題
輸入:
w: 物品的重量 dtype int
v: 物品的價值 dtype int
b: 背包的重量
輸出:
x:最優解,各個物品是否裝入背包
max_value:裝入背包的物品的重量
"""
# 判斷異常
if len(w)!=len(v):
print('請檢查輸入')
return -1,0
# 邊界條件
n = len(w)
# F[i,j] 當背包重量為j,可取前i個物品時,可裝入物品的最大重量
F = np.zeros((n+1, b+1), dtype=int)
# info[i,j],當背包重量為j,可取前i個物品時, 裝入的物品的最大標號
info = np.zeros((n+1, b+1), dtype=int)
F[:,0] = 0
F[0,:] = 0
# 遞推 轉移方程:F_k(y) = F_k(y-x) + v_k
for y in range(1,b+1):
for k in range(1,n+1):
F[k, y] = F[k-1, y]
info[k, y] = info[k-1, y]
if (y-w[k-1] >= 0) & ( F[k-1, y-w[k-1]]+v[k-1] > F[k, y]):
F[k, y] = F[k-1,y-w[k-1]] + v[k-1]
info[k, y] = k
# 追蹤結果
x = np.zeros(n, dtype=int)
max_value = F[-1,-1]
k = info[-1,-1]
leftb = b
while k > 0:
x[k-1] = 1
leftb = leftb - w[k-1]
k = info[k-1, leftb]
# 輸出
return x, max_value
- 測試用例
v = np.random.randint(1,100,1000,dtype=int)
w = np.random.randint(1,100,1000,dtype=int)
b = int(w.sum()*0.4)
x, max_value = dynamicAlgorithm_Knapscak(w, v, b)
print(x.sum(), max_value)
解二維背包問題
def dynamicAlgorithm_Knapscak(w, v, b):
"""
w: 物品的重量和體積 多維數組,dtype int
v: 物品的價值 dtype int
b: 最大體積和最大重量
F_k(y) = F_k(y-x) + v_k
"""
if len(w[0])!=len(v):
print('請檢查輸入')
return -1,0
# 邊界條件
n = len(v)
F = np.zeros((n+1, b[0]+1, b[1]+1), dtype=int)
info = np.zeros((n+1, b[0]+1, b[1]+1), dtype=int) # 裝入的物品的最大標號
# 遞推
for k in range(1,n+1):
for y1 in range(1,b[0]+1):
if (y1-w[0,k-1] >= 0):
for y2 in range(1,b[1]+1):
F[k, y1, y2] = F[k-1, y1, y2]
info[k, y1, y2] = info[k-1, y1, y2]
if (y2-w[1,k-1] >= 0):
if F[k-1, y1-w[0,k-1], y2-w[1,k-1]] + v[k-1] > F[k, y1, y2]:
F[k, y1, y2] = F[k-1, y1-w[0,k-1], y2-w[1,k-1]]+v[k-1]
info[k, y1, y2] = k
else:
F[k, y1, :] = F[k-1, y1, :]
info[k, y1, :] = info[k-1, y1, :]
# 追蹤結果
x = np.zeros(n, dtype=int)
max_value = F[-1, -1, -1]
k = info[-1, -1, -1]
leftw = b[0]
leftc = b[1]
while k > 0:
x[k-1] = 1
leftw = leftw - w[0, k-1]
leftc = leftc - w[1, k-1]
k = info[k-1, leftw, leftc]
# 輸出
return x, max_value
- 測試用例
w = np.random.randint(1,60,(2,1000),dtype=int)
b = [120,120]
v = np.random.randint(2,5000,1000,dtype=int)
x, max_value = dynamicAlgorithm_Knapscak(w, v, b)
print('x = :',x, '\n max_value=', max_value)
- 偽多項式變換時間算法近似求解背包問題(屈婉玲: 算法設計與分析p262)
- 對偶 + 讓所有的物品的重量縮小一定倍數,並取整
參考資料:
- 屈婉玲: 算法設計與分析
求一個向量中的第k小的元素
def partition(nums,l,r):
k = random.randint(l+1,r)
nums[l], nums[k] = nums[k], nums[l]
i = l+1 # [l+1, i) <= nums[l]
j = r # (j, r] > nums[l]
while True:
while (i <= r) and (nums[i] < nums[l]):
i +=1
while (j >= l+1) and nums[j] > nums[l]:
j -=1
if i > j:
break
nums[i], nums[j] = nums[j], nums[i]
i +=1
j -=1
nums[l],nums[j] = nums[j], nums[l]
return j
def findKthMinest(nums, kth):
l = 0; r = len(nums)-1
while True:
mid = partition(nums, l, r)
if mid+1 == kth:
return nums[mid]
elif mid+1 < kth:
l = mid+1
kth = kth-(mid+1)
else:
r = mid
參考資料:
整數規划的求解方法總結
精確解法中遺漏了分支定價算法,入門列生成法和分支定價算法可以閱讀羅納德《運籌學》(肖永波譯)第二版第13章
參考資料: