1. 程式人生 > >【演算法學習】切割木棍問題——動態規劃

【演算法學習】切割木棍問題——動態規劃

問題描述:

假設,有一條長度為n的木棍,已知木棍的銷售價格Pi與木棍長度i有關,i = 1,2,3,...n.問,怎樣切割能獲得最大收益。

長度為0的木棍收益肯定是0了,即profit[0] = 0.

切割長度(seg) 1 2 3 4 5 6 7 8 9 10
銷售價格(pi) 1 5 8 9 10 17 17 20 24 30

對於長度為n的木棍,他的遞推關係是:profit[n] = max(pi[i] + profit[length - seg[i]]), 其中i = 1,2,3,...n;

暴力解決方法:

int Cut_Common(int seg[], int pi[], int arr_len, int source_len)
{
    if (source_len == 0)
        return 0;
	int tmp = -1;
	for (int i = 0; i < arr_len; ++i)
	{
        if (source_len - seg[i] >= 0)
		    tmp = max(tmp, pi[i] + Cut_Common(seg, pi, arr_len, source_len - seg[i]));
	}
	return tmp;
}

這樣的解法,會發生多次相同的自問題求解,故效率非常差。聯想每次呼叫產生一個樹節點,葉節點代表了結束遞迴,O(2^n)的時間複雜度

為了解決對相同自問題的多次求解,故可用一個數組儲存自問題的解,下次需要時就不用再從新算。

動態規劃——自底向上方法

int _Cut_Dynamic_DownToTop(int seg[], int pi[], int arr_len, int length, int dump[])
{
    int tmp;
    dump[0] = 0;
    for (int i = 1; i <= length; ++i)
    {
        tmp = -1;   
        for (int j = 0; j < arr_len; ++j)
        {
            if (i - seg[j] >= 0)
                tmp = max(tmp, pi[j] + dump[i - seg[j]]);               
        
        }
        dump[i] = tmp;

    }
    return dump[length];
}

int Cut_Dynamic_DownToTop(int seg[], int pi[], int arr_len, int length)
{
    int *dump = (int *)malloc(sizeof(int)*length + 1);
    int tmp = _Cut_Dynamic_DownToTop(seg, pi, arr_len, length, dump);
    free(dump);
    return tmp;
}
解決的主要思路是,先求解長度為1的最大收益,再到2,3.....一直到n,而每次求解的解都儲存起來,時間複雜度為O(nm),空間複雜度為O(n)。

自底向上的解法仍有瑕疵,那就是在求解給定問題時,有些較小問題的解常常是不必須的,而自頂向下 + 記憶功能 解決了這個問題。

動態規劃——自頂向下(+記憶功能):

int _Cut_Dynamic_TopToDown(int seg[], int pi[], int arr_len, int length, int dump[])
{
    if (dump[length] >= 0)
        return dump[length];
    int tmp = -1;
    for (int i = 0; i < arr_len; ++i)
    {
        if (length - seg[i] >= 0)
            tmp = max(tmp, pi[i] + _Cut_Dynamic_TopToDown(seg, pi, arr_len, length-seg[i], dump));
    }
    dump[length] = tmp;
    return dump[length];
}

int Cut_Dynamic_TopToDown(int seg[], int pi[], int arr_len, int length)
{
    int *dump = (int *)malloc(sizeof(int)*length+1);
    for (int i = 0; i <= length; ++i)
    {
        dump[i] = -1;
    }
    dump[0] = 0;
    int tmp = _Cut_Dynamic_TopToDown(seg, pi, arr_len, length, dump);
    free(dump);
    return tmp;
}

注意到,自頂向下 和 暴力解決的 細微差別,自頂向下記錄了每一步的結果(這就是記憶功能),而暴力解決方法總是重新算結果。