貪心算法(英語:greedy algorithm),又稱貪婪算法,是一種在每一步選擇中都採取在當前狀態下最好或最優(即最有利)的選擇,從而希望導致結果是最好或最優的算法 —維基百科
貪心是一種解決問題的策略.如果策略正確,那麼貪心法往往是易於描述,易於實現的. —劉汝佳
題目類型 :
1. 最優裝載問題
2. 部分揹包問題
3. 乘船問題
4. 選擇不相交區間
5. 區間選點問題
6. 區間覆蓋問題
7.任務調度問題
8.Huffman編碼
由於 Huffman編碼我暫時也搞的也不是很清楚 就不 班門弄斧 了 ,如果想看Huffman 可以先走了,不用耽誤時間了.搞清楚之後,在更新
最優裝載問題
題幹 : 給出n個物體,第 i 個物體重量爲 wi,選擇儘量多 的物體,使得總重量不超過 c
由於只關心個數,而忽略重量 .
策略 : 每次選當先剩下物體中重量最小的物體,在 c 的範圍內 .
部分揹包問題
題幹 : 有 n 個物體,第 i 個物體的重量爲 wi,價值爲 vi.在總重量不超過 c 的情況下讓總價值儘量高,每一個物體都可以只取走一部分,價值和重量按比例計算.
這類題型 ,選擇重點就是 我們經常所說的 性價比
策略 : 每次所選的 是剩下物體裏面 性價比 最高的物體.當揹包裝不下 整個物體時,可以按照 百分比 選擇其中的一部分
乘船問題
題幹 : 有 n 個人,第 i 個人重量 爲wi,美艘 船的最大載重量均爲 c ,且最多隻能乘 2 個人,用最少的船裝載所有人.
由於所求的是 用最少的船裝載 ,所以每次儘量 讓 2 個人 同時乘船 當 船的重量允許時.
策略 : 在船的載重量的範圍內 ,每次選擇時, 讓當前所有人中 最輕 的 當前所有人中最重 的人乘坐 一艘船,這樣就會使船 的載重量 浪費最少.
當最輕的人 無法和 船上的任意 一人同坐一艘船時,剩下的所有人 都只能一人坐一艘船了 .
因爲 當最輕的人都無法 和 別人一起乘坐時 ,再剩下的所有人中,任意兩人 的重量 都大於 最輕的人和另一個人的重量,所以只能一人乘坐一艘船了
選擇不相交區間
題幹 : 數軸上有n 個開區間(ai,bi).選擇儘量多個區間,使得這些區間兩兩沒有公共點.
按照 bi從小到大的順序給區間排序
策略 :每次選擇的是目前不相交區間裏的第一個區間.
原因
當 a1 == a2 時
因爲 要選擇儘量多的區間,所以浪費的區間越小,其他的區間能利用的空間就越大,很明顯 選擇是第一個
當 a1 > a2 時
同上, 當在這 所浪費的空間只有 b2 - b1,而 a1 - a2 的空間不算浪費 ,想想爲什麼
當 a1 < a2 時
因爲 每選擇一個區間 後,這個區間 的 起始端 到 它前面 一個區間的 末端 這塊 區間 是無法利用的,也就是 b0 到 a1這塊區間是利用不到的,減少浪費 是因爲 爲了後面的區間 能利用上這塊區間,當利用不 上的時候 節省 也就沒有了意義.
換句話來講,每次選擇的 是 剩下區間 裏 bi 最小的一個.當然在不相交的前提下.
#include<iostream>
#include<algorithm>
using namespace std;
struct node
{
int start;
int end;
};
struct node a[105];
int cmp(struct node a,struct node b)
{
return a.end < b.end;
}
int main( )
{
int n;
while(cin >> n && n != 0)
{
for(int i = 1;i <= n;i++) cin >> a[i].start >> a[i].end;
sort(a+1,a+n+1,cmp);
int cnt = 1;
int temp = a[1].end;
for(int i = 2;i <= n;i++)
{
if(temp <= a[i].start)
{
cnt++;
temp = a[i].end;
}
}
cout << cnt << endl;
}
}
區間選點問題
題幹 : 數軸上有 n 個閉區間[ai,bi],取儘量少的點,使得每個 區間內 至少有一個點(不同區間含的點 可以是 同一個)
按照 b 從小到大 排序
策略 : 每次選取 當前區間的最後 一個點
原因
如上圖 : 如果區間選擇 了 (a1,b1) 時,只有在點 D 是,纔是最正確的點.也是 目前 最好的選擇
#include<iostream>
#include<algorithm>
#include<cmath>
using namespace std;
struct node
{
double start;
double end;
};
struct node a[1005];
int cmp(struct node a,struct node b)
{
if(a.end != b.end) return a.end < b.end;
else return a.start > b.start;
}
int main( )
{
int n,radius;
double tempx,tempy;
int flag;
int num = 1;
while(cin >> n >> radius && n != 0)
{
flag = 0;
for(int i = 1;i <= n;i++)
{
cin >> tempx >> tempy;
if(tempy > radius) flag = 1;
a[i].start = tempx - sqrt((radius * radius *1.0) - (tempy * tempy*1.0));
a[i].end = tempx + sqrt((radius * radius *1.0) - (tempy * tempy*1.0));
}
if(flag == 1)
{
cout << "Case " << num << ": -1" << endl;
num++;
continue;
}
sort(a+1,a+n+1,cmp);
int cnt = 1;
double temp = a[1].end;
for(int i = 2;i <= n;i++)
{
if(a[i].start <= temp) continue;
else
{
cnt++;
temp = a[i].end;
}
}
cout << "Case " << num << ": " << cnt << endl;
num++;
}
return 0;
}
區間覆蓋問題
數軸上有n 個閉區間[ai,bi],選擇 儘量少的區間覆蓋一條指定線段[s,t]
把各區間按照 ai 從小到大排序
策略 : 如果只有一個起點,就選擇這個起點,如果是多個起點則 在每次起點 上的 所有區間裏 選擇 最長的區間.(也就是 bi 最大的區間)
若,起點爲 a1,那當然 只能選擇 (a1,b1)區間了,若起點 爲 A ,則選擇的是 區間(a2,b2).因爲 相同的起點,當然 b2 越長,所選擇的 區間就越少了.
這類題思想簡單,實現 時 就稍微 困難點.
Minimal coverage
#include<iostream>
#include<algorithm>
#include<cstring>
using namespace std;
struct node
{
int start;
int end;
};
struct node a[100005];
int book[100005];
int cmp(struct node a,struct node b)
{
return a.start < b.start;
}
int main( )
{
int T;
cin >> T;
while(T--)
{
memset(book,0,sizeof(book));
int len;
cin >> len;
int i = 1;
int tempstart,tempend;
while(cin >> tempstart >> tempend && (tempstart || tempend))
{
if(tempend <= 0 || tempstart >= len) continue;
a[i].start = tempstart;
a[i].end = tempend;
i++;
}
sort(a+1,a+i,cmp);
if(a[1].start > 0)
{
cout << 0 << endl;
continue;
}
int temp = 0,tempmax = 0,coordinate;
int t = -1; //用來備份起點相同 的上一個 區間
for(int j = 1;j < i;j++)
{
if(a[j].start <= temp)
{
if(a[j].end > tempmax)
{
book[j] = 1;
if(t != -1) book[t] = 0;
tempmax = a[j].end;
t = j;
}
}
else
{
temp = tempmax;
t = -1;
j--;
}
if(tempmax >= len) break;
}
if(tempmax < len)
{
cout << 0 << endl;
continue;
}
int cnt = 0;
for(int j = 1;j < i;j++) if(book[j]) cnt++;
cout << cnt << endl;
for(int j = 1;j < i;j++)
{
if(book[j])
{
cout << a[j].start << " " << a[j].end << endl;
}
}
}
return 0;
}
任務調度問題
題幹 : 給定 n 向任務,每項任務的開始時間 爲si,結束時間爲ei,(1 <= i <= n,0<= si< ei),且沒想任務只能在一臺機器上完成每臺機器一次只能完成一項任務.如果任務 i 和任務 j 滿足 ei <= sj 或 ej < si.則任務 i 和 任務 j 是不衝突的,可以在一臺機器上完成
對每項任務開始時間 升序進行排序
最少用的機器臺數爲 m = 0
策略 : 每次選擇當前最小開始時間的任務
if(任務 i 和已經執行的任務不衝突) 安排任務 i 在空閒的機器上完成
else
{
m++; //添加一臺新機器
任務 i 在新機器完成
}
#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstdio>
#include<cstring>
using namespace std;
typedef pair<int ,int> pii;//時間點,前一個表示時間點的是兼職,後一個表示這個時間點是開始時間還是結束時間
const int N = 1e5 + 10;
pii a[2*N]; //2n 個時間點
int l[N],r[N]; //l 記錄機器的開機時間 ,r 機器的關機時間
int main()
{
int t;
long long sum = 0;
scanf( "%d",&t);
while(t--)
{
int n;//一共n 項任務
scanf( "%d",&n);
for(int i = 1;i <= n;i++)
{
int left,right;
scanf("%d%d",&left,&right);
a[2*i - 1] = pii(left,1);
a[2*i] = pii(right,-1);
}
sort(a+1,a+2*n+1);
memset(l,-1,sizeof(l));
memset(r,-1,sizeof(r));
int num = 0,ans = 0; //num 表示當前運行的機器數量 ans 表示到當前一共開過多少臺機器
for(int i = 1;i <= 2*n;i++)
{
if(a[i].second == 1)
{
num++;
if(l[num] == -1) l[num] = r[num] = a[i].first;//num 是新開的機器
ans = max(ans,num);
}
else
{
r[num] = a[i].first;
num--;
}
}
sum = 0;
for(int i = 1;i <= ans;i++)
{
sum +=(r[i] - l[i]);
}
cout << ans << " " << sum << endl;
}
}