1. 程式人生 > >演算法導論(六)之貪婪演算法

演算法導論(六)之貪婪演算法

動態規劃是先分析子問題,再做選擇。而貪心演算法則是先做貪心選擇,做完選擇後,生成了子問題,然後再去求解子問題

  • 與動態規劃方法相似,是更簡單的解決優化問題的方法,通常用於求解優化問題
  • 貪婪演算法不能保證一定得到最優解
  • 對於具有某些特徵的問題,貪婪演算法有最優解

1. 作業(活動)選擇問題

  • 對n個作業進行排程,這些作業在執行期間需要專用某個共同的資源
  • 選出最多個不衝突的作業

活動集合S(SifiS_i為開始時間,f_i 為結束時間):

i 1 2 3 4 5 6 7 8 9 10 11
SiS_i 1 3 0 5 3 5 6 8 8 2 12
fif_i 4 5 6 7 8 9 10 11 12 13 14

1. 遞迴實現

1. 虛擬碼

//***********活動編號已經按結束時間排序**********
//遞迴
REC-ACT-SEL (s, f, i, n)
	m ← i + 1
	while m ≤ n and s[m] < f[i] //find the first activity in S(i) to finish
		do m ← m + 1
	if m ≤ n
		then return {a(m)} and REC-ACT-SEL(s, f, m, n)
	else return null

2. 時間複雜度

T(n)=θ(n)T(n) = \theta(n)

3. java 程式碼實現

	/**
     * 貪心演算法的遞迴解:活動編號已經按結束時間排序
     *
     * @param s          活動的開始時間
     * @param f          活動的結束時間
     * @param i          要求解的子問題 S(i)
     * @param n          活動集合S的數量
     * @param activities 結果記錄
     * @return 返回 S(i) 的最大相容活動集
     */
    public static ArrayList<Integer> recursiveActivitySelection(int[] s, int[] f, int i, int n, ArrayList<Integer> activities) {
        //初始呼叫時 i = 0
        int m = i;
        activities.add(m + 1);//保證與活動序號一致(陣列從0開始)
        while (m < n && s[m] < f[i]) {//查詢 S(i) 中最早結束活動
            m++;//選擇下一個活動
        }
        if (m < n) {
            recursiveActivitySelection(s, f, m, n, activities);
        }
        return activities;
    }

	public static void main(String[] args) {
        int[] s = {1, 3, 0, 5, 3, 5, 6, 8, 8, 2, 12};
        int[] f = {4, 5, 6, 7, 9, 9, 10, 11, 12, 14, 16};

        ArrayList arr = JobSchedule.recursiveActivitySelection(s, f, 0, s.length, new ArrayList<>());
        for (int i = 0; i < arr.size(); i++) {
            System.out.println(arr.get(i));
        }
    }

2. 迭代實現

1. 虛擬碼

//***********活動編號已經按結束時間排序**********
//迭代
GREEDY-ACTIVITY-SELECTOR(s, f)
	n = s.length
	A ← {a1}
	k ← 1
	for m ← 2 to n
		do if s[m] ≥ f[k]  //activity a(m) is compatible with a(k)
			then A ← A and {a(m)}
			k ← m // a(i) is most recent addition to A
	return A

2. 時間複雜度

T(n)=θ(n)T(n) = \theta(n)

3. Java 程式碼實現

	/**
     * 貪心演算法的迭代解:活動編號已經按結束時間排序
     *
     * @param s          活動的開始時間
     * @param f          活動的結束時間
     * @param activities 結果記錄
     * @return 返回 S(i) 的最大相容活動集
     */
    public static ArrayList<Integer> greedyActivitySelection(int[] s, int[] f, ArrayList<Integer> activities) {
        //所有真正的活動(不包括 活動0和 活動n+1)中,結束時間最早的那個活動一定是最大相容活動集合中的 活動.
        int n = s.length;
        int m = 0;//從 1 開始(陣列序號為0)
        activities.add(m + 1);//保證與活動序號一致(陣列從0開始)

        for (int actId = 1; actId < n; actId++) {
            if (s[actId] >= f[m])//actId的開始時間在 m 號活動之後.--actId 與 m 沒有衝突
            {
                m = actId;
                activities.add(m + 1);
            }
        }
        return activities;
    }

    public static void main(String[] args) {
        int[] s = {1, 3, 0, 5, 3, 5, 6, 8, 8, 2, 12};
        int[] f = {4, 5, 6, 7, 9, 9, 10, 11, 12, 14, 16};
        ArrayList arr = JobSchedule.greedyActivitySelection(s, f, new ArrayList<>());
        for (int i = 0; i < arr.size(); i++) {
            System.out.println(arr.get(i));
        }
    }
}

3. 動態規劃實現

//筆者為看懂,望大神指教

2. Huffman 編碼

編碼檔案需要的二進位制位:

  • c.freq 表示 c 在檔案中出現的頻率
  • dT(c)d_T(c) 表示 c 的葉節點在樹中的深度
  • B(T)B(T) 定義為 T 的代價

B(T)=cCc.freqdT(c)B(T) = \sum_{c\in C} {c.freq * d_T(c)}

1. 虛擬碼

HUFFMAN(C)
	n = |C|
	Q = C
	for i = 1 to n – 1
		do 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)

2. 時間複雜度

T(n)=O(nlogn)T(n) = O(n * logn)

3. Java 程式碼實現

public class Huffman {

    //定義TreeNode節點
    private static class TreeNode implements Comparable<TreeNode> {
        TreeNode left;
        TreeNode right;
        int weight;
        char ch;//儲存相應字元
        String code;//code儲存0或1
        public TreeNode(int weight, TreeNode left, TreeNode right) {
            this.weight = weight;
            this.left = left;
            this.right = right;
            this.code = "";
        }
        @Override
        public int compareTo(TreeNode o) {
            if (this.weight > o.weight) {
                return 1;
            } else if (this.weight < o.weight) {
                return -1;
            } else {
                return 0;
            }
        }
    }

    public static TreeNode huffman(TreeMap<Integer, Character> data) {
        TreeSet<TreeNode> tNodes = new TreeSet<>();
        Set<Integer> weights = data.keySet();
        Iterator<Integer> iterator = weights.iterator();
        while (iterator.hasNext()) {
            int weight = iterator.next();
            TreeNode tmp = new TreeNode(weight, null, null);
            tmp.ch = data.get(weight);
            tNodes.add(tmp);
        }
        while (tNodes.size() > 1) {
            TreeNode leftNode = tNodes.pollFirst();
            leftNode.code = "0";
            TreeNode rightNode = tNodes.pollFirst();
            rightNode.code = "1";
            TreeNode newNode = new TreeNode(leftNode.weight + rightNode.weight, leftNode, rightNode);
            tNodes.add(newNode);
        }
        return tNodes.first();
    }

    //得到字元編碼
    private static void code(TreeNode t) {
        if (t.left != null) {
            t.left.code = t.code + t.left.code;
            code(t.left);
        }
        if (t.right != null) {
            t.right.code = t.code + t.right.code;
            code(t.right);
        }
    }

    //列印字元編碼結果
    public static void print(TreeNode root) {
        if (root != null) {
            if (root.left == null && root.right == null) {
                System.out.println(root.ch + " 編碼:" + root.code);
            } else {
                print(root.left);
                print(root.right);
            }
        }
    }

    public static void main(String[] args) {
        TreeMap<Integer, Character> test = new TreeMap<>();
        test.put(5, 'f');
        test.put(9, 'e');
        test.put(12, 'c');
        test.put(13, 'b');
        test.put(16, 'd');
        test.put(45, 'a');
        TreeNode root = huffman(test);
        code(root);
        print(root);
    }
}

3. 0/1揹包問題

問題描述:給定一組物品,每種物品都有自己的重量和價格,在限定的總重量內,我們如何選擇,才能使得物品的總價格最高

  • 如果你作出貪婪選擇,即裝入平均價值最高的物件,你最終不可能獲得最大化的價值總量