貪心算法入門(greedy algorithm)
貪心算法,又名貪婪法,是尋找最優解問題的常用方法,這種方法模式一般將求解過程分成若干個步驟,但每個步驟都應用貪心策略,選取當前狀態下最好/最優的選擇(局部最優解),並以此希望最后堆疊出的結果也是最好/最優的解。
解決貪心問題的基本步驟
- 將原問題分解為子問題
- 找出貪心策略
- 得到每一個子問題的最優解
- 將所有局部最優解的集合構成稱為原問題的一個解
分析思路
1. 貪心策略選擇
貪心算法的根本在於貪心策略的選擇,如果你能找出正確的貪心策略那么,貪心問題也就迎刃而解。
貪心策略 :即我們需要找到一種方法使得當前可以獲得的收益最大。舉個栗子:我們每個人都對未來有很多的憧憬和計划。我們希望我們的人生都能夠得到最優解。但是由於我們無法預知之后幾年幾十年的事情發展,我們無法精准的計算出我們再每一個時間結點的完美選擇。於是我們只好選擇每一天,或者當前時間段我都都做對自己最有幫助的事情。經過不斷的累積最后得到最終結果,未必不是一種好的做法
2. 猜
例題分析
既然是貪心的思想: 那么就首先要將問題分解為子問題,然后着眼於子問題的最優解,最后將所有子問題的最優解匯總即可
n 堆果子一定會經過 n - 1次合並,而每次消耗的體力之和為兩堆果子的重量 , 舉個栗子
我們拿樣例 n = 3 舉例
1 2 9分別為三堆果子的數量。
我們分別采用兩種極端的方法:
- 先將最大的和最大的合並:
第一次合並 : 1 11 耗費體力 : 11
第二次合並 : 12 耗費體力 : 11 + 12 = 23 - 先將最小的兩堆合並
第一次合並 : 3 9 耗費體力 : 3
第二次合並 : 12 耗費體力 : 3 + 12 = 15
其實我們已經可以發現,我們每次挑選最大的只會使得我們再下一次合並時增加我們的體力消耗,於是我們應該盡可能的避免這種消耗
也就是說我們最終采用的貪心策略實際上就是每次取出最小的兩個進行合並,合並完之后再將其合並值放入,在進行重復操作,相信大家都想到了 堆 和 赫夫曼樹。赫夫曼樹就不說了 ,已經忘記怎么寫了 hhh 在這里采用堆的形式解決也就是STL 中的優先隊列
#include <iostream>
#include <algorithm>
#include <cstdio>
#include <vector>
#include <queue>
using namespace std;
// 申請小頂堆
priority_queue<int,vector<int>,greater<int> > q;
int n ;
int main()
{
cin >> n;
int x;
for(int i = 0;i < n ;i ++)
{
cin >> x;
q.push(x);
}
long long ans = 0;
while(q.size() >= 2)
{
int a = q.top();q.pop();
int b = q.top();q.pop();
ans += a + b;
q.push(a + b);
}
cout << ans << endl;
return 0;
}
貪心策略:
貪速率,\(f=v/t\)
#include <iostream>
#include <algorithm>
#include <cstdio>
#include <vector>
using namespace std;
const int N = 10005;
int n;
struct node{
int f;
int id;
};
vector<node> v;
bool cmp(node a,node b)
{
return a.f > b.f;
}
int main()
{
cin >> n;
int a, b ;
for(int i = 0;i < n ;i ++)
{
scanf("%d %d",&a ,&b);
v.push_back({a * b, i + 1});
}
sort(v.begin(),v.end(),cmp);
for(int i = 0;i < v.size() ;i ++)
{
cout << v[i].id << " ";
}
return 0;
}
存在的問題和難點
-
證明困難,大部分貪心算法難以證明
-
大部分情況下其並不是最優解
-
貪心問題之間的跨越度比較大