日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程语言 > python >内容正文

python

python 动态规划_DP动态规划(Python实现)

發布時間:2023/12/15 python 27 豆豆
生活随笔 收集整理的這篇文章主要介紹了 python 动态规划_DP动态规划(Python实现) 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

前言_

我們遇到的問題中,有很大一部分可以用動態規劃(簡稱DP)來解。 解決這類問題可以很大地提升你的能力與技巧,我會試著幫助你理解如何使用DP來解題。 這篇文章是基于實例展開來講的,因為干巴巴的理論實在不好理解。

注意:如果你對于其中某一節已經了解并且不想閱讀它,沒關系,直接跳過它即可。

簡介(入門)

什么是動態規劃,我們要如何描述它?

動態規劃算法通?;谝粋€遞推公式及一個或多個初始狀態。 當前子問題的解將由上一次子問題的解推出。使用動態規劃來解題只需要多項式時間復雜度, 因此它比回溯法、暴力法等要快許多。

現在讓我們通過一個例子來了解一下DP的基本原理。

首先,我們要找到某個狀態的最優解,然后在它的幫助下,找到下一個狀態的最優解。

“狀態”代表什么及如何找到它?

“狀態”用來描述該問題的子問題的解。原文中有兩段作者闡述得不太清楚,跳過直接上例子。

如果我們有面值為1元、3元和5元的硬幣若干枚,如何用最少的硬幣湊夠11元? (表面上這道題可以用貪心算法,但貪心算法無法保證可以求出解,比如1元換成2元的時候)

首先我們思考一個問題,如何用最少的硬幣湊夠i元(i<11)?為什么要這么問呢? 兩個原因:1.當我們遇到一個大問題時,總是習慣把問題的規模變小,這樣便于分析討論。 2.這個規模變小后的問題和原來的問題是同質的,除了規模變小,其它的都是一樣的, 本質上它還是同一個問題(規模變小后的問題其實是原問題的子問題)。

好了,讓我們從最小的i開始吧。當i=0,即我們需要多少個硬幣來湊夠0元。 由于1,3,5都大于0,即沒有比0小的幣值,因此湊夠0元我們最少需要0個硬幣。 (這個分析很傻是不是?別著急,這個思路有利于我們理清動態規劃究竟在做些什么。) 這時候我們發現用一個標記來表示這句“湊夠0元我們最少需要0個硬幣。”會比較方便, 如果一直用純文字來表述,不出一會兒你就會覺得很繞了。那么, 我們用d(i)=j來表示湊夠i元最少需要j個硬幣。于是我們已經得到了d(0)=0, 表示湊夠0元最小需要0個硬幣。當i=1時,只有面值為1元的硬幣可用, 因此我們拿起一個面值為1的硬幣,接下來只需要湊夠0元即可,而這個是已經知道答案的, 即d(0)=0。所以,d(1)=d(1-1)+1=d(0)+1=0+1=1。當i=2時, 仍然只有面值為1的硬幣可用,于是我拿起一個面值為1的硬幣, 接下來我只需要再湊夠2-1=1元即可(記得要用最小的硬幣數量),而這個答案也已經知道了。 所以d(2)=d(2-1)+1=d(1)+1=1+1=2。一直到這里,你都可能會覺得,好無聊, 感覺像做小學生的題目似的。因為我們一直都只能操作面值為1的硬幣!耐心點, 讓我們看看i=3時的情況。當i=3時,我們能用的硬幣就有兩種了:1元的和3元的( 5元的仍然沒用,因為你需要湊的數目是3元!5元太多了親)。 既然能用的硬幣有兩種,我就有兩種方案。如果我拿了一個1元的硬幣,我的目標就變為了: 湊夠3-1=2元需要的最少硬幣數量。即d(3)=d(3-1)+1=d(2)+1=2+1=3。 這個方案說的是,我拿3個1元的硬幣;第二種方案是我拿起一個3元的硬幣, 我的目標就變成:湊夠3-3=0元需要的最少硬幣數量。即d(3)=d(3-3)+1=d(0)+1=0+1=1. 這個方案說的是,我拿1個3元的硬幣。好了,這兩種方案哪種更優呢? 記得我們可是要用最少的硬幣數量來湊夠3元的。所以, 選擇d(3)=1,怎么來的呢?具體是這樣得到的:d(3)=min{d(3-1)+1, d(3-3)+1}。

OK,碼了這么多字講具體的東西,讓我們來點抽象的。從以上的文字中, 我們要抽出動態規劃里非常重要的兩個概念:狀態和狀態轉移方程。

上文中d(i)表示湊夠i元需要的最少硬幣數量,我們將它定義為該問題的”狀態”, 這個狀態是怎么找出來的呢?我在另一篇文章動態規劃之背包問題(一)中寫過: 根據子問題定義狀態。你找到子問題,狀態也就浮出水面了。 最終我們要求解的問題,可以用這個狀態來表示:d(11),即湊夠11元最少需要多少個硬幣。 那狀態轉移方程是什么呢?既然我們用d(i)表示狀態,那么狀態轉移方程自然包含d(i), 上文中包含狀態d(i)的方程是:d(3)=min{d(3-1)+1, d(3-3)+1}。沒錯, 它就是狀態轉移方程,描述狀態之間是如何轉移的。當然,我們要對它抽象一下,

d(i)=min{ d(i-vj)+1 },其中i-vj >=0,vj表示第j個硬幣的面值;

有了狀態和狀態轉移方程,這個問題基本上也就解決了。當然了,Talk is cheap,show me the code!

偽代碼如下:

Python代碼如下:

import os

Min=[x for x in range(12)];

VN=[1,3,5];

for i in range(1,12,1):

for j in range(3):

if VN[j]<=i and Min[i-VN[j]]+1

Min[i]=Min[i-VN[j]]+1;

print(Min[1::1]);

下圖是當i從0到11時的解:

從上圖可以得出,要湊夠11元至少需要3枚硬幣。

此外,通過追蹤我們是如何從前一個狀態值得到當前狀態值的, 可以找到每一次我們用的是什么面值的硬幣。比如,從上面的圖我們可以看出, 最終結果d(11)=d(10)+1(面值為1),而d(10)=d(5)+1(面值為5),最后d(5)=d(0)+1 (面值為5)。所以我們湊夠11元最少需要的3枚硬幣是:1元、5元、5元。

注意:原文中這里本來還有一段的,但我反反復復讀了幾遍, 大概的意思我已經在上文從i=0到i=3的分析中有所體現了。作者本來想講的通俗一些, 結果沒寫好,反而更不好懂,所以這段不翻譯了。

初級

上面討論了一個非常簡單的例子。現在讓我們來看看對于更復雜的問題, 如何找到狀態之間的轉移方式(即找到狀態轉移方程)。 為此我們要引入一個新詞叫遞推關系來將狀態聯系起來(說的還是狀態轉移方程)

OK,上例子,看看它是如何工作的。

一個序列有N個數:A[1],A[2],…,A[N],求出最長非降子序列的長度。 (講DP基本都會講到的一個問題LIS:longest increasing subsequence)

正如上面我們講的,面對這樣一個問題,我們首先要定義一個“狀態”來代表它的子問題, 并且找到它的解。注意,大部分情況下,某個狀態只與它前面出現的狀態有關, 而獨立于后面的狀態。

讓我們沿用“入門”一節里那道簡單題的思路來一步步找到“狀態”和“狀態轉移方程”。 假如我們考慮求A[1],A[2],…,A[i]的最長非降子序列的長度,其中i

為了方便理解我們是如何找到狀態轉移方程的,我先把下面的例子提到前面來講。 如果我們要求的這N個數的序列是:

5,3,4,8,6,7

根據上面找到的狀態,我們可以得到:(下文的最長非降子序列都用LIS表示)

前1個數的LIS長度d(1)=1(序列:5)

前2個數的LIS長度d(2)=1(序列:3;3前面沒有比3小的)

前3個數的LIS長度d(3)=2(序列:3,4;4前面有個比它小的3,所以d(3)=d(2)+1)

前4個數的LIS長度d(4)=3(序列:3,4,8;8前面比它小的有3個數,所以 d(4)=max{d(1),d(2),d(3)}+1=3)

OK,分析到這,我覺得狀態轉移方程已經很明顯了,如果我們已經求出了d(1)到d(i-1), 那么d(i)可以用下面的狀態轉移方程得到:

d(i) = max{1, d(j)+1},其中j

用大白話解釋就是,想要求d(i),就把i前面的各個子序列中, 最后一個數不大于A[i]的序列長度加1,然后取出最大的長度即為d(i)。 當然了,有可能i前面的各個子序列中最后一個數都大于A[i],那么d(i)=1, 即它自身成為一個長度為1的子序列。

分析完了,上圖:(第二列表示前i個數中LIS的長度, 第三列表示,LIS中到達當前這個數的上一個數的下標,根據這個可以求出LIS序列)

Talk is cheap, show me the code:

#include

using namespace std;

int lis(int A[], int n){

int *d = new int[n];

int len = 1;

for(int i=0; i<n; ++i){

d[i] = 1;

for(int j=0; j<i; ++j)

if(A[j]<=A[i] && d[j]+1>d[i])

d[i] = d[j] + 1;

if(d[i]>len) len = d[i];

}

delete[] d;

return len;

}

int main(){

int A[] = {

5, 3, 4, 8, 6, 7

};

cout<<lis(A, 6)<<endl;

return 0;

}

Python 代碼如下:

import sys

def lis(*args,num=1):

d=[0]*num;

len_num=1;

for i in range(num):

d[i]=1;

for j in range(i):

if args[j]<=args[i] and d[i]

d[i]=d[j]+1;

if d[i]>len_num:

len_num=d[i];

return len_num;

print(lis(5,3,4,8,6,7));

該算法的時間復雜度是O(n2 ),并不是最優的解法。 還有一種很巧妙的算法可以將時間復雜度降到O(nlogn),網上已經有各種文章介紹它, 這里就不再贅述。傳送門: LIS的O(nlogn)解法。 此題還可以用“排序+LCS”來解,感興趣的話可自行Google。

練習題

無向圖G有N個結點(1

提示:在每一步中,對于那些沒有計算過的結點, 及那些已經計算出從結點1到它的最短路徑的結點,如果它們間有邊, 則計算從結點1到未計算結點的最短路徑。

嘗試解決以下來自topcoder競賽的問題:

ZigZag – 2003 TCCC Semifinals 3

BadNeighbors – 2004 TCCC Round 4

FlowerGarden – 2004 TCCC Round 1

轉載自:http://www.hawstein.com/posts/dp-novice-to-advanced.html

總結

以上是生活随笔為你收集整理的python 动态规划_DP动态规划(Python实现)的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。