一、什麼是貪心算法
貪心算法是一種常見的算法思想,在很多問題中都有應用。貪心算法是一種直觀而且簡單的算法,通過每一步選擇中都採取最優的選擇來達到整體最優的解。
貪心算法思路比較單一,每次選擇都是當前狀態下的最優選擇,因此複雜度通常較低。但是,貪心算法的正確性較難證明,很多問題並不是貪心選擇一定是最優解,需要對每個問題進行分析。
貪心算法用於解決那些具有最有子結構性質的問題,即問題可以分解為更小的子問題,每個子問題的最優解可以遞推得到整個問題的最優解。
二、貪心算法的基本思想
貪心算法要求在每一步選擇中都採取最優的選擇,而得到問題的最優解。
貪心算法通常分為兩個階段:
- 建立數學模型來描述問題,找到最優解的性質;
- 利用貪心思想,每一步都做出局部最優的選擇,從而達到全局最優。
貪心算法的核心是在每一步選擇中都採取最優的選擇,但此時必須具有無後效性。即某個狀態以及之前的狀態所做的選擇,不會影響以後的狀態。只需要考慮當前狀態下的最優解,和之前的選擇無關。
三、貪心算法的應用
1、背包問題
背包問題是一類經典的貪心算法問題,通常有兩種類型:0/1背包問題和完全背包問題。
對於0/1背包問題,每個物品只能選一次。物品有兩個限制,一個是總重量不能超過背包的最大承重,另一個是每個物品的重量和價值都不相同。貪心策略是每次選取價值比較高的物品放進背包,直到不能再加為止。這樣可以獲得比較好的解,但不一定是最優解。
完全背包問題中,每個物品可以選擇多次放入背包中。這種問題可以轉換為一個貪心問題,每次選取價值比較高的物品,直到背包無法再放下為止。
#include
using namespace std;
struct node{
int w;//重量
int v;//價值
};
int cmp(node a, node b){
return a.v > b.v;//按價值從大到小排序
}
double knapsack(node a[], int n, int m){
double ans = 0.0;
int i;
sort(a, a+n, cmp);
for(i=0; i m) break;
else{
ans += a[i].v;//先選價值最大的物品
m -= a[i].w;
}
}
if(i >n>>m;//n個物品,m為背包容量
int i;
node a[n];
for(i=0; i>a[i].w>>a[i].v;
}
printf("%.2lf", knapsack(a, n, m));
return 0;
}
2、活動選擇問題
活動選擇問題是一種常見的貪心算法問題,也被稱為區間調度問題。該問題要求安排一個學生參加儘可能多的互不衝突的活動,以達到時間上最合理的目的。
具體實現方法是按照每個活動的結束時間從小到大排序,然後依次選擇最早結束的活動,直到無法再加入為止。
#include
#include
using namespace std;
struct activity{
int start;//開始時間
int end;//結束時間
};
bool cmp(activity a, activity b){
return a.end>n;
activity a[n];
int i;
for(i=0; i>a[i].start;
cin>>a[i].end;
}
sort(a, a+n, cmp);//按結束時間從小到大排序
int cnt=1;
int last=a[0].end;
for(i=1; i=last){
cnt++;//計算可安排的活動數目
last=a[i].end;
}
}
cout<<cnt;
return 0;
}
3、霍夫曼編碼
霍夫曼編碼是一種使用較廣泛的編碼技術,主要應用於數據壓縮。
在進行編碼時,通常按照每個字符的出現概率從小到大排序。然後使用貪心算法構建一個霍夫曼樹,來得到一組字符的編碼方式,保證編碼長度的最小化和解碼的唯一性。
#include
#include
#include
#include
using namespace std;
struct node{
int data;//節點權值
node* left;//左子節點指針
node* right;//右子節點指針
};
struct cmp{
bool operator () (node *a, node *b){
return a->data > b->data;
}
};
string code[500];
node* root;
void preorder(node *p, string str){
if(!p) return;
if(!p->left && !p->right){
code[p->data] = str;
}
preorder(p->left, str+'0');
preorder(p->right, str+'1');
}
void build(char a[], int b[], int n){
int i;
node *left, *right;
priority_queue<node*, vector, cmp> q;
for(i=0; idata = a[i];
p->left = NULL;
p->right = NULL;
q.push(p);
}
while(q.size()>1){
left = q.top();
q.pop();
right = q.top();
q.pop();
node *p = new node;
p->data = 0;
p->left = left;
p->right = right;
p->data = left->data + right->data;
q.push(p);//構建霍夫曼樹
}
root = q.top();
preorder(root, "");
}
int main(){
char a[1000];//只考慮ASCII碼部分,總共只有256個字符
int b[1000];
memset(code, 0, sizeof(code));//所有編碼初始化為0
int n, i;
cin>>n;
for(i=0; i>a[i]>>b[i];
}
build(a, b, n);//構建霍夫曼樹
for(i=0; i<256; i++){
if(code[i]!=""){
cout<<(char)i<<" "<<code[i]<<endl;//輸出字符對應的編碼
}
}
return 0;
}
4、最小生成樹
最小生成樹是一個圖的子集,可以與所有頂點相連而不形成環,並且權值之和最小。
貪心算法應用非常廣泛,其中包括基於貪心策略的Prim算法和Kruskal算法。Prim算法是在新生成的最小生成樹中加入一個頂點,然後找到與其相鄰的最短邊,將其加入最小生成樹。Kruskal算法則是將所有邊按照權值從小到大排序,依次加入最小生成樹中,同時保證之前加入的邊不會形成環。
#include
#include
using namespace std;
const int maxn = 1000;
const int inf = 0x3f3f3f;
int eg[maxn][maxn];
int vis[maxn];
int ans;
int prim(int n){
int i, j, minn;
vis[1] = 1;//從1開始遍歷
for(i=1; i<n; i++){
minn = inf;
int u = -1;
for(j=1; j<=n; j++){
if(vis[j]){
for(int k=1; k<=n; k++){
if(!vis[k] && eg[j][k] >n>>m;//n個點,m條邊
int i, j, x, y, w;
for(i=1; i<=n; i++){
for(j=1; j<n; j++){
eg[i][j] = inf;
}
}
for(i=0; i>x>>y>>w;//x和y之間的邊權值為w
eg[x][y] = eg[y][x] = w;
}
ans = 0;
cout<<prim(n);//輸出最小生成樹的權值
return 0;
}
四、貪心算法的局限性
儘管貪心算法在許多問題中應用廣泛,但並不是所有問題都適用於貪心思想。貪心算法求得的最優解可能並不是全局最優解。
有一些問題中,每個局部最優解可以得到全局最優。比如霍夫曼編碼,貪心算法比較適用。但是對於一些問題,貪心策略可能無法達到全局最優解。比如旅行商問題,這是一個NP問題,通過貪心策略得到的最優解與全局最優解可能存在很大的誤差。
因此,在處理這些問題時,需要對每個問題進行判斷和分析。同時,可以通過優化貪心策略,得到比較好的局部最優解,來逐步逼近全局最優解。真正的全局最優解通常需要通過枚舉或動態規劃等方法得到。
原創文章,作者:小藍,如若轉載,請註明出處:https://www.506064.com/zh-hk/n/248712.html
微信掃一掃
支付寶掃一掃