《演算法導論》第十六章——貪心演算法
雖然寫這個部落格主要目的是為了給我自己做一個思路記憶錄,但是如果你恰好點了進來,那麼先對你說一聲歡迎。我並不是什麼大觸,只是一個菜菜的學生,如果您發現了什麼錯誤或者您對於某些地方有更好的意見,非常歡迎您的斧正!
貪心演算法並不保證能得到最優解,但對於很多問題確實可以得到最優解。
貪心演算法,如它的名字一樣,十分“貪心”。如果說,動態規劃是一位深謀遠慮的智者,那麼貪心演算法就是一個目光短淺的山賊,它總是選取看起來最佳的選擇,卻不為以後做考慮。
比如我有重量為60,50,30,50這樣4顆依次擺放的鑽石,它們的價值依次為6,5,3,5,我現在書包最多能裝重量為100的東西,動態規劃得到的最優解就是拿兩顆50的,但是貪心演算法在看到60的時候就會把60放入書包,最後只能裝到90。
16.1活動選擇問題
簡單地講,就是有一個公用教室,很多班級要用它來搞活動,比如(1)班想下午(3)點到下午5點借用教室,(2)班想晚上7點到晚上9點借用教室,那就不會產生衝突,但是如果現在(3)班想在晚上6點晚上8點借用教室,那就會與(2)班造成衝突,兩者必須有一者放棄。又比如(4)班要借用的時間段為下午5點到晚上7點。現在要選擇這個教室可以給哪幾個班用,使得最多的班的要求能夠達到滿足。那顯然管理員應該優先借給(1)班、(2)班與(4)班。
現在我們的活動集為S(i是班號,si是起始時間,fi是結束時間):
兩種安排為{1,4,8,11}、{2,4,9,11}
活動選擇問題的最優子結構
c[i,j]表示集合Si,j的最優解的大小。
貪心選擇
選擇最早結束的那個活動,這樣就有更多時間留給別的活動。(如果最早結束的活動有很多個,就選擇任意一個)。
迭代貪心演算法
感覺書中的虛擬碼不是很看得懂,就用白話文說一下我的理解吧。
首先我們的活動已經按照結束時間排好序了,然後我們要把第一個,也就是最早結束的活動加入到我們的安排中,從第二個活動開始遍歷,只要下一個的開始時間晚於上一個的結束時間,就把它加入到我們的安排列表中。
我們用陣列b來記錄要加入安排的活動。這是我自己寫的虛擬碼
b[1]=true
j=1
i=2 to n
if(s[i]≥f[j]) //下一個的開始時間晚於上一個的結束時間
b[i]=true//加入安排
j=i
else
b[i]=false
16.2貪心演算法原理
步驟:
1.確定問題的最優子結構
2.設計一個遞迴演算法
3.證明如果我們做出一個貪心選擇,則只剩下一個子問題
4.證明貪心選擇總是安全的
5.設計一個遞迴演算法實現貪心策略
6.將遞迴演算法轉換為迭代演算法
貪心選擇性質
當進行選擇時,我們直接做出在當前問題中看來最優的選擇,而不必考慮子問題的解。
最優子結構
如果一個問題的最優解包含其子問題的最優解,則稱此問題具有最優子結構性質。這個性質是能否應用動態規劃和貪心演算法的關鍵要素。
貪心對動態規劃
0-1揹包問題
假如我有一隻只能裝載150kg的包,現在這裡有一些物品:
物品 1 2 3 4 5 6 7
重量 35 30 60 50 40 10 25 用陣列w[]表示
價值 10 40 30 50 35 40 30 用陣列v []表示
我們有三種策略可以選擇
1、每次裝的都選擇價效比最高的,也就是價值/重量最大
2、每次裝都選擇重量最輕的
3、每次裝都選擇價值最高的
我們採用第一種方式。
16.3赫夫曼編碼
赫夫曼編碼可以很有效地壓縮資料:通常可以節省20%~90%的空間,具體壓縮率依賴於資料的特性。
來看上面一組資料,加入我們使用定長編碼,則a=000,b=001…f=101,這種方法需要3x100x1000=300000個二進位制位來編碼檔案。
假如我們採用變長編碼:a=0,b=101,c=100,d=111,e=1101,f=1100,則需要(45x1+13x3+12x3+16x3+9x4+5x4)x1000=224000位。
字首碼
我們注意到上面的編碼中:沒有任何碼字是其它碼字的字首。
將3個字元的檔案abc編碼為0·101·100=0101100,“·”表示連線操作。
二進位制碼001011101可以唯一解析為0·0·101·1101,解析碼為aabe
檔案的最優編碼方案總是對應一棵滿二叉樹。
x.freq表示x的頻率。
簡單的說,就是不停地從已有的結點中找到最小的兩個結點,然後連線起來。
這部分程式碼我不知道怎麼寫,應該說我太菜了,沒有寫成功,所以底下就只有活動選擇和0-1揹包問題的程式碼:
貪心演算法_活動安排.h
#pragma once
/*活動安排*/
void ActManage(int n, int s[], int f[], int b[]);
/*測試函式*/
void TestActManage();
貪心演算法_活動安排.cpp
#include "貪心演算法_活動安排.h"
#include <stdio.h>
#include <iostream>
#include <stdlib.h>
#include <time.h>
using namespace std;
/*活動安排*/
void ActManage(int n, int s[], int f[], bool b[])
{
b[1] = true;
int j = 1;
for (int i = 2; i < n; i++)
{
if (s[i] >= f[j])
{
b[i] = true;
j = i;
}
else
b[i] = false;
}
}
/*測試函式*/
void TestActManage()
{
int s[] = { 0,1,3,0,5,3,5,6,8,8,2,12 };/*12*/
int f[] = { 0,4,5,6,7,9,9,10,11,12,15,16 };
int n = sizeof(s) / sizeof(s[0]);
bool* b = new bool[n];
ActManage(n, s, f, b);
for (int i = 1; i < n; i++)
{
if (b[i])cout << "活動" << i << "開始於" << s[i] << ",結束於" << f[i] << endl;
}
delete[] b;
}
主函式
#include "貪心演算法_活動安排.h"
#include <stdio.h>
int main()
{
TestActManage();
getchar();
getchar();
return 0;
}
執行結果
貪心演算法_01揹包問題.h
#pragma once
/*裝載方法*/
void BackageLoad(float cost[], bool b[], int w[], int weight, int n);
/*測試函式*/
void TestBackage();
貪心演算法_01揹包問題.cpp
#include "貪心演算法_01揹包問題.h"
#include <stdio.h>
#include <iostream>
#include <stdlib.h>
#include <time.h>
using namespace std;
/*裝載方法*/
void BackageLoad(float cost[], bool b[], int w[], int weight, int n)
{
int temp;
bool flag = false;/*判斷是否還能再挑出物品*/
while (weight >= 0)/*當袋子還能裝下的時候*/
{
float max = 0;
for (int i = 0; i < n; i++)
{
if (!b[i] && cost[i] > max&&weight - w[i] >= 0)/*如果b[i]還沒有裝進去*/
{
max = cost[i];
temp = i;
flag = true;
}
}
if (!flag)
break;
weight -= w[temp];
b[temp] = true;
}
}
/*測試函式*/
void TestBackage()
{
int w[7] = { 35,30,60,50,40,10,25 };/*重量*/
int v[7] = { 10,40,30,50,35,40,30 };/*價值*/
float cost[7];/*價效比*/
bool b[7];/*記錄是否裝進袋子裡*/
int weight = 150;/*袋子承重*/
int i;
for (i = 0; i < 7; i++)
{
cost[i] = (float)v[i] / (float)w[i];
b[i] = false;
}
BackageLoad(cost, b, w, weight, 7);
for (i = 0; i < 7; i++)
{
if (b[i])
{
cout << "選擇物品" << i+1 << ",它重" << w[i] << ",價值為" << v[i] << endl;
}
}
}
主函式
#include "貪心演算法_01揹包問題.h"
#include <stdio.h>
int main()
{
TestBackage();
getchar();
getchar();
return 0;
}
執行結果
參考網站及部落格