《算法導論》第16章 貪心算法 個人筆記

第16章 貪心算法

16.1 活動選擇問題

問題:假設有一個n個活動的集合S=a1,a2,...,an ,這些活動使用同一個資源,而這個資源在某個時刻只能供一個活動使用。每個活動都有一個開始時間si 和一個結束時間fi 。若sifisjfi ,則aiaj 是兼容的。在活動選擇問題中,我們希望選出一個最大兼容活動集。假設活動已按結束時間的單調遞增順序排序。

1、動態規劃法

Sij 表示在ai 結束之後開始。且在aj 開始之前結束的那些活動的集合,用c[i,j] 表示集合Sij 的最優解的大小,故有

c[i,j]=0,ifSij=ϕc[i,j]=maxakSijc[i,k]+c[h,j]+1,ifSijϕ

2、貪心選擇

定理:考慮任意非空子問題Sk ,令amSk 中結束時間最早的活動,則amSk 的某個最大兼容活動子集中。
證明:令AkSk 的一個最大兼容子集,且ajAk 中結束時間最早的活動。
aj=am ,即證。
ajam ,將Ak 中的aj 替換爲am ,因爲fmfj ,則還是兼容的,數目不變,故替換後的也是Sk 的一個最大兼容活動子集,且包含am
- 遞歸貪心算法
爲方便初始化,添加一個虛擬活動a0 ,其結束時間f0=0

public List<Integer> RECURSIV_ACTIVITY_SELECTOR(int[] s,int[] f,int k,int n){
    int m = k + 1;
    while(m<=n && s[m]<=f[k])
        m++;
    List<Integer> res = new ArrayList<>();
    if(m<=n)
        return res.add(m).addAll(RECURSIV_ACTIVITY_SELECTOR(int[] s,int []f,int k,int n))
    else
        return res;
}
  • 迭代貪心算法
public List<Integer> GREED_ACTIVITY_SELECTOR(int[] s,int[] f){
    int n = s.length - 1; //去掉虛擬活動
    List<Integer> res = new ArrayList<>();
    res.add(1);
    int k = 1;
    for(int i = 2; i<=n; i++){
        if(s[m]>=f[k]){
            res.add(m);
            k = m;
        }
    }
    return res;
}

16.2 貪心算法原理

一般地,我們按如下步驟設計貪心算法:
1. 將最優化問題轉化爲這樣的形式:對其做出一次選擇後,只剩下一個子問題需要求解。
2. 證明做出貪心選擇後,原問題總是存在最優解,即貪心選擇總是安全的。
3. 證明做出貪心選擇後,剩餘的子問題滿足性質:其最優解與貪心選擇組合即可得到原問題的最優解,這樣就得到了最優子結構。

貪心對動態規劃

0-1揹包問題:動態規劃

分數揹包問題:貪心

/**
 * @param m 表示揹包的最大容量
 * @param n 表示商品個數
 * @param w 表示商品重量數組
 * @param p 表示商品價值數組
 */
public static int[][] Package1(int m, int n, int[] w, int[] p) {
    //c[i][v]表示前i件物品恰放入一個重量爲m的揹包可以獲得的最大價值
    int c[][] = new int[n + 1][m + 1];
    for (int i = 0; i < n + 1; i++)
        c[i][0] = 0;
    for (int j = 0; j < m + 1; j++)
        c[0][j] = 0;

    for (int i = 1; i < n + 1; i++) {
        for (int j = 1; j < m + 1; j++) {
            //當物品爲i件重量爲j時,如果第i件的重量(w[i-1])小於重量j時,c[i][j]爲下列兩種情況之一:
            //(1)物品i不放入揹包中,所以c[i][j]爲c[i-1][j]的值
            //(2)物品i放入揹包中,則揹包剩餘重量爲j-w[i-1],所以c[i][j]爲c[i-1][j-w[i-1]]的值加上當前物品i的價值
            if (w[i - 1] <= j) {
                if (c[i - 1][j] < (c[i - 1][j - w[i - 1]] + p[i - 1]))
                    c[i][j] = c[i - 1][j - w[i - 1]] + p[i - 1];
                else
                    c[i][j] = c[i - 1][j];
            } else
                c[i][j] = c[i - 1][j];
        }
    }
    return c;
}
//用一維數組存儲
public static int Package2(int m, int n, int[] w, int[] p) {
    int[] f = new int[m + 1];
    for (int i = 1; i < f.length; i++)    //必裝滿則f[0]=0,f[1...m]都初始化爲無窮小
        f[i] = Integer.MIN_VALUE;
    for (int i = 0; i < n; i++) {
        for (int j = f.length - 1; j >= w[i]; j--) {
            f[j] = Math.max(f[j], f[j - w[i]] + p[i]);
        }
    }
    return f[m];
}

在具體運用時,可以首先假設全部物品裝進揹包是否超載,若不超載則直接輸出最優解。有一次某公司一道筆試題就是這樣,直接上dq就超時gg了=。=

16.3 赫夫曼編碼

赫夫曼編碼用於壓縮數據,根據每個字符的出現頻率構造最優二進制表示。
前綴碼:沒有任何碼字是其他碼字的前綴。
構造赫夫曼編碼,使用一個以屬性freq爲關鍵字的最小優先隊列Q。

HUFFMAN(C)
n = C.length
Q = C
for i =1 to n-1
    allocate a new node z
    z.left = x = EXTRACT-MIN(Q)
    z.right = y = EXTRACT-MIN(Q)
    z.freq = x.freq + y.freq
    INSERT(Q,z)
return EXTRACT-MIN(Q) //return the root of the tree

定理:過程HUFFMAN會生成一個最優前綴碼。

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