看了雷霄骅的故事,为之深表惋惜,一位为科研埋头奉献发光发热的人,这件事也促使我开通博客,来记录自己一点学习的过程。
最近在看动态规划的内容,看了硬币找零问题,是一个很好的对动态规划算法入门的问题,问题描述如下:有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;
}
参看书目:《算法竞赛入门经典》 刘汝佳 著