演算法導論(六)之貪婪演算法
阿新 • • 發佈:2018-12-17
動態規劃是先分析子問題,再做選擇。而貪心演算法則是先做貪心選擇,做完選擇後,生成了子問題,然後再去求解子問題
- 與動態規劃方法相似,是更簡單的解決優化問題的方法,通常用於求解優化問題
- 貪婪演算法不能保證一定得到最優解
- 對於具有某些特徵的問題,貪婪演算法有最優解
1. 作業(活動)選擇問題
- 對n個作業進行排程,這些作業在執行期間需要專用某個共同的資源
- 選出最多個不衝突的作業
活動集合S():
i | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 |
---|---|---|---|---|---|---|---|---|---|---|---|
1 | 3 | 0 | 5 | 3 | 5 | 6 | 8 | 8 | 2 | 12 | |
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. 時間複雜度
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. 時間複雜度
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 在檔案中出現的頻率- 表示 c 的葉節點在樹中的深度
- 定義為 T 的代價
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. 時間複雜度
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揹包問題
問題描述:給定一組物品,每種物品都有自己的重量和價格,在限定的總重量內,我們如何選擇,才能使得物品的總價格最高
- 如果你作出貪婪選擇,即裝入平均價值最高的物件,你最終不可能獲得最大化的價值總量