【動態規劃(一)】動態規劃基礎

Angry_Caveman發表於2017-07-22

1.1 動態規劃簡介

動態規劃(dynamic programming)是運籌學的一個分支,是求解決策過程(decision process)最優化的數學方法。20世紀50年代初美國數學家R.E.Bellman等人在研究多階段決策過程(multistep decision process)的優化問題時,提出了著名的最優化原理(principle of optimality),把多階段過程轉化為一系列單階段問題,利用各階段之間的關係,逐個求解,創立了解決這類過程優化問題的新方法——動態規劃。1957年出版了他的名著《Dynamic Programming》,這是該領域的第一本著作。

動態規劃要注意兩點:1,狀態;2,狀態轉移方程。

我不太願意詳細講解下面三個問題的思考過程,一步步推導沒有太大的必要,畢竟程式設計這種事情看一百遍,還不如自己在紙上畫一畫,然後敲敲程式碼。

希望能通過下面這三個小問題,來熟悉,學習動態規劃

 

1.2 硬幣問題

問題:如果我們有面值為1元、3元和5元的硬幣若干枚,如何用最少的硬幣湊夠11元?

解析:

(1)湊夠1元,11元硬幣

(2)湊夠2元,21元硬幣

(3)湊夠3元,31元硬幣或者13元硬幣,又1<3,所以選擇一個3元硬幣。以此類推至11元。

(4)其實是在之前的基礎確定的。如,

2=1元(1個硬幣)+1元(1個硬幣);//2個硬幣

3=2元(2個硬幣)+1元(1個硬幣)。

(5)用陣列Coin儲存硬幣種類,陣列Dp儲存相應所需最小硬幣個數,如Dp[m]=n,代表湊夠m至少需要n個硬幣。為方便使用,不使用Dp[0]

(6)構建方程:Dp[i]=Dp[i-Coin[j]]+1;//1代表需要Coin[j]這個硬幣

原始碼(簡單測試可用):Talk is cheap,show me the code!

int main()
{
	int coin[Count]={1,3,5};//硬幣型別
	int Dp[Sum+1];//記錄從0-11的銀幣數目
	
	//演算法
	int i,j;
	//初始化,使每一個錢數為相應個數為1的硬幣構成
	for(i=0;i<=Sum;i++)
	{
		Dp[i]=i;
	}//Dp[0]=0不使用
	for(i=1;i<=Sum;i++)
	{
		for(j=0;j<Count ;j++)
		{
			if(i>=coin[j]&&Dp[i-coin[j]]+1<Dp[i])
			{
				Dp[i]=Dp[i-coin[j]]+1;
			}
		}
	}
	//列印
	cout<<Dp[Sum]<<endl;
	for(i=1;i<=Sum;i++)
	{
		cout<<Dp[i]<<" ";
	}
}


1.3 數塔問題

問題:從頂部出發在每一個節點可以選擇向左或者向右走,一直走到底層,

要求找出一條路徑,使得路徑上的數字之和最大.

解析:

(1)自底向上分析。先使用M二維陣列儲存該數塔,然後建立Dp二維陣列,儲存每個位置的和,如下圖

(2)每次從本層結點的2個分支結點中選出最大值,

構建方程:Dp[i][j]=Max{Dp[i+1][j],Dp[i+1][j+1]}+Arry[i][j];

原始碼(簡單測試可用):Talk is cheap,show me the code!

//數塔問題處理函式,自底向上尋找
void DataTower()
{
	int i,j;
	//初始化
	for (i=0;i<Max;i++)
	{
		Dp[Max-1][i]=M[Max-1][i];//複製圖最後一列
	}

	//計算
	for(i=Max-2;i>=0;i--)//最後一行已經賦值,還有Max-1行沒有賦值
	{
		for(j=0;j<=i;j++)//第i行就有i列
		{
			//找出左右子節點最大的一個
			if(Dp[i+1][j]>Dp[i+1][j+1])
				Dp[i][j]=Dp[i+1][j]+M[i][j];
			else
				Dp[i][j]=Dp[i+1][j+1]+M[i][j];
		}
	}
}

列印,來源網上

//由Dp陣列,列印最終結果
void print ()
{
	cout << "最大路徑和:" << Dp[0][0] << '\n';
    int node_value;
    // 首先輸出塔頂元素
    cout << "最大路徑:" << M[0][0];
    int j = 0;
    for (int i = 1; i < Max; ++i)
    {
        node_value = Dp[i - 1][j] - M[i - 1][j];
        if (node_value == Dp[i][j + 1]) ++j;
        cout << "->" << M[i][j];
    }
    cout << endl;
}


1.4 最長非降子程式

問題:534867

求該數列的最長非降子序

解析:

(1)假設該序列只有一個”5”,那麼最長為1

(2)“5,3”,轉換來看“51),31)”,最長為1

(3)“5,3,4”,轉換“51),31),42)”,因為最長非降序列為“3,4”,最長為2

(4)不一一列舉,看看這條更清楚一些,51,31,42,83,63)”。“6”的確定是因為“4”,“3,4,6”在“4”對應的長度上加1,為3

(5)建立陣列Dp,構建方程:Dp[i]=Dp[j]+1;

判斷條件是A[i]>=A[j]

原始碼(簡單測試可用):Talk is cheap,show me the code!

本人依據以上思路寫的。

void Long01(int *A)
{
	int Dp[Max];//記錄i位置對應的相應的非降子序的個數
	int i,j;
	Dp[0]=1;//第一個元素為肯定為1
	for (i=1;i<Max;i++)
	{
		if(A[i]>=A[i-1])
		{
			Dp[i]=Dp[i-1]+1;
		}
		else
		{
			for(j=i-1;j>=0;j--)
			{
				if(A[i]>A[j])
				{
					Dp[i]=Dp[j]+1;
					break;
				}
			}
			if(j<0)
			{
				Dp[i]=1;
			}
		}
	}
}

網上跟上述思路一致,但是更簡潔

void  Long02(int *A)
{
	int d[Max];  
    int len = 1;  
    for(int i=0; i<Max; ++i){  
        d[i] = 1;  
        for(int j=0; j<i; ++j)  //迴圈i前面的幾個元素
            if(A[j]<=A[i] && d[j]+1>d[i])  //i前可能存在多個小於i的值,取最大者,個人覺得此處倒著找好一些
                d[i] = d[j] + 1;  
        if(d[i]>len) len = d[i];  
    }  
    cout<< len<<endl;  
}



1.5 總結 

比較簡單,如果有錯誤的地方,煩請大牛指教,上述的程式碼是在Vs2010中編寫的,如果有亂碼問題,可以在:https://github.com/AngryCaveman/C-Struct.git中檢視下載“018番外”資料夾下本人的原始碼,其他資料夾中專案是在Vs2013中編寫的。




相關文章