動態規劃入門之硬幣找零問題

看了雷霄驊的故事,爲之深表惋惜,一位爲科研埋頭奉獻發光發熱的人,這件事也促使我開通博客,來記錄自己一點學習的過程。

最近在看動態規劃的內容,看了硬幣找零問題,是一個很好的對動態規劃算法入門的問題,問題描述如下:有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;
}

參看書目:《算法競賽入門經典》 劉汝佳 著

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章