《算法導論》— Chapter 15 動態規劃

算法導論一書的第四部分—高級設計和分析技術從本章開始討論,主要分析高效算法的三種重要技術:動態規劃、貪心算法以及平攤分析三種。
首先,本章討論動態規劃,它是經過組合子問題的解而解決整個問題的,一般應用於最優化問題。
動態規劃算法的設計能夠分爲以下4個步驟:ios

  • 描述最優解的結構
  • 遞歸定義最優解的值
  • 按照自底向上的方式計算最優解的值
  • 由計算出的結果構造一個最優解

15.1 裝配線調度

問題描述

第一個動態規劃的例子是求解一個製造問題,Colonel汽車公司在有兩條裝配線的工廠生產汽車,具體以下圖所示:
問題描述
針對裝配線調度問題,下面給出一個實例,要求利用動態規劃方法分析設計程序實現。
裝配線調度問題實例算法

程序實現

/* * 《算法導論》第十五章 動態規劃 * 15.1 裝配線調度問題 */

#include <iostream>
#include <cstdlib>

using namespace std;

//初始化裝配線以及站點個數
const int L = 2; 
const int S = 6;

//程序輸入:裝配點耗費時間、底盤進入耗費時間、裝配點移動耗費時間、底盤離開耗費時間
int a[L + 1][S + 1] , e[L + 1], t[L + 1][S], x[L + 1] , n = S;

//程序輸出:
int f[L + 1][S + 1], l[L + 1][S + 1] , rf , rl;

//求解f[][] 用到的參數int **a, int *e, int **t, int *x, int n
void FastestWay()
{
    f[1][1] = e[1] + a[1][1];
    f[2][1] = e[2] + a[2][1];
    //由公式15.4 - 15.7 推導計算

    for (int j = 2; j <= n; j++)
    {
        if (f[1][j-1] + a[1][j] <= f[2][j - 1] + t[2][j - 1] + a[1][j])
        {
            f[1][j] = f[1][j - 1] + a[1][j];
            l[1][j] = 1;
        }
        else{
            f[1][j] = f[2][j - 1] + t[2][j - 1] + a[1][j];
            l[1][j] = 2;
        }

        if (f[2][j - 1] + a[2][j] <= f[1][j - 1] + t[1][j - 1] + a[2][j])
        {
            f[2][j] = f[2][j - 1] + a[2][j];
            l[2][j] = 2;
        }
        else{
            f[2][j] = f[1][j - 1] + t[1][j - 1] + a[2][j];
            l[2][j] = 1;
        }
    }

    if (f[1][n] + x[1] <= f[2][n] + x[2])
    {
        rf = f[1][n] + x[1];
        rl = 1;
    }
    else{
        rf = f[2][n] + x[2];
        rl = 2;
    }

}

//打印調度結果
void PrintStations()
{
    //最後一個站點的裝配線號
    int i = rl;

    cout << "倒序結果輸出:" << endl;

    cout << "line : " << i << " , station : " << n << endl;

    for (int j = n; j >= 2; j--)
    {
        i = l[i][j];
        cout << "line : " << i << " , station : " << j - 1 << endl;
    }
}

//打印調度結果 —— 以站號遞增的順序輸出裝配站
void PrintStations2()
{
    int r[S] , tem = rl;

    for (int j = S; j > 1 ; j--)
    {
        tem= l[tem][j];
        r[j - 1] = tem;
    }

    cout << "正序結果輸出:" << endl;

    for (int j = 1; j < S; j++)
    {
        cout << "line : " << r[j] << " , station : " << j << endl;
    }
    //輸出最後一個裝配站
    cout << "line : " << rl << " , station : " << n << endl;
}

void Input()
{
    cout << "請輸入裝配點耗費時間:" << endl;
    for (int i = 1; i <= L; i++)
        for (int j = 1; j <= S; j++)
        {
            cin >> a[i][j];
        }

    cout << "請輸入進入裝配點耗費時間:" << endl;
    for (int i = 1; i <= L; i++)
    {
        cin >> e[i];
    }

    cout << "請輸入站點移動耗費時間:" << endl;
    for (int i = 1; i <= L; i++)
        for (int j = 1; j < S; j++)
        {
            cin >> t[i][j];
        }
    cout << "請輸入離開裝配點耗費時間:" << endl;
    for (int i = 1; i <= L; i++)
    {
        cin >> x[i];
    }
}

void Output()
{
    int i, j;
    cout << "輸出f[i][j]" << endl;
    //f[i][j]表示第j個站是在裝配線i上完成的,完成1到j的裝配最少須要的時間 
    for (i = 1; i <= L; i++)
    {
        for (j = 1; j <= S; j++)
            cout << f[i][j] << ' ';
        cout << endl;
    }

    cout << "rf = " << rf << endl;

    cout << "輸出l[i][j]" << endl;
    //l[i][j]表示使得f[i][j]最小時在哪一個裝配線上裝配j-1 
    for (i = 1; i <= L; i++)
    {
        for (j = 2; j <= S; j++)
            cout << l[i][j] << ' ';
        cout << endl;
    }

    cout << "rl = " << rl << endl;
}

//主程序
int main()
{
    //數據輸入
    Input();

    FastestWay();

    //數據輸出
    Output();

    //結果打印
    PrintStations();

    PrintStations2();

    system("pause");
    return 0;
}

運行結果

程序運行結果

15.1練習

15.1-1

//遞歸輸出結果,參數爲:(站點,裝配線編號)
void PrintStations3(int s, int ll)
{
    cout << "正序結果輸出:" << endl;

    if (s < n)
        ll = l[ll][s + 1];

    if (s > 1)
        PrintStations3(s - 1, ll);

    cout << "line : " << ll << " , station : " << s << endl;
}

15.1-5

若Canty教授猜想正確,存在知足 l1[j]=2 l2[j]=1 的站點,則公式15.6與公式15.7中的公式必須同時成立,獲得:
markdown

f1[j1]+a1,j>f2[j1]+t2,j1+a1,j

f2[j1]+a2,j>f1[j1]+t1,j1+a2,j

明顯的,爲兩個矛盾式。因此Canty教授的猜想是不正確的。

15.2 矩陣鏈乘法

問題描述

解決矩陣鏈乘問題是動態規劃的一個典型實例。
矩陣鏈乘優化

程序實現

/* * 《算法導論》第十五章 動態規劃 * 矩陣鏈乘法 */

#include <iostream>
#include <cstdlib>

using namespace std;

const int N = 6; //進行鏈乘的矩陣數目

//鏈乘矩陣維數序列
int A[N + 1] = { 30, 35, 15, 5, 10, 20, 25 };
//int A[N + 1] = { 5, 10, 3, 12, 5, 50, 6 };
//動態優化結果存儲
int m[N + 1][N + 1] , s[N+1][N+1];

void MatrixChainOrder()
{
    int i, j, k , l, q;
    for (i = 1; i <= N; i++)
            m[i][i] = 0;

    for (l = 2; l <= N; l++)
    {
        for (i = 1; i <= N - l + 1; i++)
        {
            j = i + l - 1;
            m[i][j] = 0x7fffffff;
            for (k = i; k <= j - 1; k++)
            {
                q = m[i][k] + m[k + 1][j] + A[i - 1] * A[k] * A[j];
                if (q < m[i][j])
                {
                    m[i][j] = q;
                    s[i][j] = k;
                }
            }
        }//for
    }
}

void PrintOptimalParens(int i , int j)
{
    if (i == j)
        cout << "A" << i << " ";
    else{
        cout << "(";
        PrintOptimalParens( i, s[i][j]);
        PrintOptimalParens( s[i][j] + 1, j);
        cout << ")";
    }
}

int main()
{
    MatrixChainOrder();
    PrintOptimalParens(1, N);
    cout << endl;
    system("pause");
    return 0;
}

運行結果

運行結果

15.2練習

15.2-1

最優加括號結果爲:
最優結果ui

15.2-2

15.3 動態規劃基礎

採用動態規劃方法的最優化問題中的兩個要素:最優子結構和重疊子問題。spa

  1. 最優子結構
    用動態規劃求解優化問題的第一步是描述最優解的結構,若是問題的一個最優解中包含了子問題的最優解,則該問題具備最優子結構。

2.重疊子問題
適用於動態規劃求解的最優化問題必須具備的第二個要素是子問題的空間要「很小」,也就是用來解原問題的遞歸算法能夠反覆的解一樣的子問題,而不是總在產生新的子問題。設計

重疊子問題實現矩陣鏈乘

/* * 《算法導論》第十五章 動態規劃 * 15.3 動態規劃基礎 * 重疊子問題實現 */

#include <iostream>
#include <cstdlib>

using namespace std;

const int N = 6; //進行鏈乘的矩陣數目


//動態優化結果存儲
int m[N + 1][N + 1], s[N + 1][N + 1];

int RecusiveMatrixChain(int *A, int i, int j)
{
    if (i == j)
        return 0;
    m[i][j] = 0x7fffffff;

    for (int k = i; k <= j - 1; k++)
    {
        int q = RecusiveMatrixChain(A, i, k) + RecusiveMatrixChain(A, k + 1, j) + A[i - 1] * A[k] * A[j];

        if (q < m[i][j])
        {
            m[i][j] = q;
            s[i][j] = k;
        }

    }//for
    return m[i][j];
}


void PrintOptimalParens(int i, int j)
{
    if (i == j)
        cout << "A" << i << " ";
    else{
        cout << "(";
        PrintOptimalParens(i, s[i][j]);
        PrintOptimalParens(s[i][j] + 1, j);
        cout << ")";
    }
}

int main()
{
    //鏈乘矩陣維數序列
    int A[N + 1] = { 30, 35, 15, 5, 10, 20, 25 };
    //int A[N + 1] = { 5, 10, 3, 12, 5, 50, 6 };

    RecusiveMatrixChain(A, 1, N);
    PrintOptimalParens(1, N);

    cout << endl;
    system("pause");
    return 0;
}

作備忘錄實現矩陣鏈乘

動態規劃有一種變形,它既具備一般的動態規劃方法的效率,又採用了一種自頂向下的策略。其思想就是備忘原問題的天然但低效的遞歸算法。code

/* * 《算法導論》第十五章 動態規劃 * 15.3 動態規劃基礎 * 作備忘錄 */

#include <iostream>
#include <cstdlib>

using namespace std;

const int N = 6; //進行鏈乘的矩陣數目


//動態優化結果存儲
int m[N + 1][N + 1], s[N + 1][N + 1];


int LookUpChain(int *A, int i, int j)
{

    if (m[i][j] < 0x7fffffff)
        return m[i][j];

    if (i == j)
        m[i][j] = 0;
    else
    {
        for (int k = i; k <= j - 1; k++)
        {
            int q = LookUpChain(A, i, k) + LookUpChain(A, k + 1, j) + A[i - 1] * A[k] * A[j];

            if (q < m[i][j])
            {
                m[i][j] = q;
                s[i][j] = k;
            }

        }//for
    }

    return m[i][j];
}



int MemorizedMatrixChain(int *A)
{
    for (int i = 1; i <= N; i++)
        for (int j = 1; j <= N; j++)
        {
            m[i][j] = 0x7fffffff;
        }

    return LookUpChain(A, 1, N);
}

void PrintOptimalParens(int i, int j)
{
    if (i == j)
        cout << "A" << i << " ";
    else{
        cout << "(";
        PrintOptimalParens(i, s[i][j]);
        PrintOptimalParens(s[i][j] + 1, j);
        cout << ")";
    }
}

int main()
{
    //鏈乘矩陣維數序列
    int A[N + 1] = { 30, 35, 15, 5, 10, 20, 25 };
    //int A[N + 1] = { 5, 10, 3, 12, 5, 50, 6 };

    MemorizedMatrixChain(A);
    PrintOptimalParens(1, N);

    cout << endl;
    system("pause");
    return 0;
}

15.3練習

相關文章
相關標籤/搜索