看了雷霄驊的故事,爲之深表惋惜,一位爲科研埋頭奉獻發光發熱的人,這件事也促使我開通博客,來記錄自己一點學習的過程。
最近在看動態規劃的內容,看了硬幣找零問題,是一個很好的對動態規劃算法入門的問題,問題描述如下:有n中硬幣,面值分別爲v1,v2,v3,…,vn,每種都有無限多個,給定非零整數s,可以選用多個硬幣,使得面值之和恰好爲s。輸出所需硬幣的最小值和最大值。
動態規劃關鍵是找出狀態轉移方程, 然後依據狀態轉移方程用遞歸或遞推的方法來實現問題的求解。該問題中硬幣數目是不限的,狀態方程應重點考慮被找零的數目和硬幣面值之間的關係,問題中需要得出所需硬幣的最小值和最大值,我們用數組d來表示當前給定的s所需的最大數目和最少數目,狀態方程即爲:
d[s]=max{d[s-v[i]]+1, d[s]} (1)
d[s]=min{d[s-v[i]]+1, d[s]} (2)
d[s]表示總數爲s時所需的硬幣數,d[ s - v[i] ] 表示用總和爲s-v[i]時,所需的硬幣數,d[s-v[i]] +1個硬幣也可使得面值恰好爲s, 那麼,d[s]=max{d[s-v[i]]+1, d[s]}表示遞推(選擇)當前方案所需的硬幣數(d[s])與方案(d[s-v[i]]+1)的較大者。
下面給出分別用遞歸和遞推的方法實現的求解:
1. 遞歸法
求解最大值
void df_max(int s, int v[], int n, int d[])
{
if(s == 0)
return ;
if(s > 0)
{
for(int i =0; i<n; i++)
{
if(s >= v[i])
{
if(d[s -v[i]] == 0)
df_max(s-v[i], v, n, d);
d[s] = d[s] < (d[s - v[i]] + 1) ? (d[s - v[i]] + 1) : d[s];
}
}
}
}
int _tmain(int argc, _TCHAR* argv[])
{
int s=10;
int v[] = {1,2,5};
int n = sizeof(v)/sizeof(int);
int *d = new int[s+1];
memset(d, 0, sizeof(int)*(s+1));
d[0] = 0;
df_max(s, v, n, d);
cout<<"The max result: "<<d[s]<<endl<<endl;
for(int i=0; i<=s; i++)
{
cout<<d[i]<<' ';
}
return 0;
}
遞歸法求解最小值void df(int s, int v[], int n, int d[])
{
if(s == 0)
return ;
if(s > 0)
{
for(int i =0; i<n; i++)
{
if(s >= v[i])
{
if(d[s -v[i]] != 0)
df(s-v[i], v, n, d);
d[s] = d[s] > (d[s - v[i]] + 1) ? (d[s - v[i]] + 1) : d[s];
}
}
}
}
int _tmain(int argc, _TCHAR* argv[])
{
int s=10;
int v[] = {1,2,5};
int n = sizeof(v)/sizeof(int);
int *d = new int[s+1];
memset(d, 1, sizeof(int)*(s+1));
d[0] = 0;
df(s, v, n, d);
cout<<"The min result: "<<d[s]<<endl;
for(int i=0; i<=s; i++)
{
cout<<d[i]<<' ';
}
return 0;
}
2. 遞推法
void c_max_min(int s, int v[], int n, int max[], int min[])
{
for(int i=1; i <= s; i++)
for(int j = 0; j < n; j++)
{
if (i >= v[j])
{
max[i] = max[i] < (max[i-v[j]]+1) ? (max[i-v[j]]+1) : max[i];
min[i] = min[i] > (min[i-v[j]]+1) ? (min[i-v[j]]+1) : min[i];
}
}
}
int _tmain(int argc, _TCHAR* argv[])
{
int s=10;
int v[] = {1,2,5};
int n = sizeof(v)/sizeof(int);
int *max = new int[s+1];
int *min = new int[s+1];
memset(max, 0, sizeof(int)*(s+1));
//注意這裏初始值的設定,s爲1時需要一枚硬幣
memset(min, 1, sizeof(int)*(s+1));
min[0] = 0;
c_max_min(s, v, n, max, min);
cout<< max[s] << ' ' << min[s] <<endl;
return 0;
}
參看書目:《算法競賽入門經典》 劉汝佳 著