貪心算法

貪心算法  

 貪心算法是一個快速的、不穩定的算法。貪心算法總是作出在當前看來最好的選擇。也就是說貪心算法並不從整體最優考慮,它所作出的選擇只是在某種意義上的局部最優選擇。雖然貪心算法不能對所有問題都得到整體最優解,但對許多問題它能產生整體最優解。如單源最短路經問題,最小生成樹問題等。在一些情況下,即使貪心算法不能得到整體最優解,其最終結果卻是最優解的近似(具有不穩定性)。當“貪心序列”中的每項互異且當問題沒有重疊性時,看起來總能通過貪心算法取得(近似)最優解的。
貪心算法一般是求“最優解”這類問題的。最優解問題可描述爲:有n個輸入,它的解是由這n個輸入的某個子集組成,並且這個子集必須滿足事先給定的條件。這個條件稱爲約束條件。而把滿足約束條件的子集稱爲該問題的可行解。這些可行解可能有多個。爲了衡量可行解的優劣,事先給了一個關於可行解的函數,稱爲目標函數。目標函數最大(或最小)的可行解,稱爲最優解。  
a)求“最優解”最原始的方法爲搜索枚舉方案法(一般爲回溯法)  
除了極簡單的問題,一般用深度優先搜索或寬度優先搜索。通常優化方法爲利用約束條件進行可行性判斷剪枝;或利用目標函數下界(或上界),根據當前最優解進行分枝定界。  
b)動態規劃(需要滿足階段無後效性原則)。  
動態規劃主要是利用最最優子問題的確定性,從後向前(即從小規模向大規模)得到當前最優策略,從而避免了重複的搜索。 
一、貪心問題一般性質
1、貪心選擇性質
所謂貪心選擇性質是指所求問題的整體最優解可以通過一系列局部最優的選擇,即貪心選擇來達到。這是貪心算法可行的第一個基本要素,也是貪心算法與動態規劃算法的主要區別。
動態規劃算法通常以自底向上的方式解各子問題,而貪心算法則通常以自頂向下的方式進行,以迭代的方式作出相繼的貪心選擇,每作一次貪心選擇就將所求問題簡化爲規模更小的子問題。對於一個具體問題,要確定它是否具有貪心選擇性質,必須證明每一步所作的貪心選擇最終導致問題的整體最優解。
在動態規劃中,每一個父問題結果的得出需要它的子問題作爲條件;但是“貪心選擇性”則不需要;貪心選擇性所做的是一個非線性的子問題處理過程,即一個子問題並不依賴於另一個子問題,但是子問題間有嚴格的順序性
該算法存在問題:
1、不能保證求得的最後解是最佳的;
2、不能用來求最大或最小解問題;
3、只能求滿足某些約束條件的可行解的範圍。
2、最優子結構性質
當一個問題的最優解包含其子問題的最優解時,稱此問題具有最優子結構性質。問題的最優子結構性質是該問題可用動態規劃算法或貪心算法求解的關鍵特徵。不能採用分治法解決的問題,是理論上是不能使用貪心算法的而且,必須擁有最優子結構性質,才能保證貪心算法返回最優解。
二、貪心算法與回溯法及動態規劃算法的差異
我們以例子來說明這個問題。
1、最短路徑
27、貪心算法
如圖所示,我們如果用回溯法,則會搜索過程中,會產生如下搜索樹:
27、貪心算法
圖2
顯然,上面的搜索有大量重複性工作。比如節點8、9、10到11的最短路分別被調用了9次,從節點5、6、7到節點11也分別搜索了3次。  如果先算出節點8、9、10到11的最短路,由於它與前面的點無關,因此最優值確定下來,再用它們求定節點5、6、7到節點11的最短路徑。同理,再用節點5、6、7的最優值,來求節點2、3、4優值。最後從節點2、3、4推出1到11的最優值,顯然複雜度大爲降低。  
當然,如果本題把簡單搜索改爲搜索+記憶化的方法,則就是得能動態規劃的原理,本質上就是動態規劃,只是實現的方法不同與傳統的表格操作法。
貪心算法則不同,它不是建立在枚舉方案的基礎上的。它從前向後,根據當前情況,“貪心地”決定出下一步,從而一步一步直接走下去,最終得到解。假如本例子中,我們定下這樣的貪心策略:節點號k%3= =1。顯然,它只訪問了節點1、4、7、10、11,工作量最少,效率最高。當然,對本題來說,它不能得到最優解―――最短路徑。  從圖3中可以看出,貪心算法是一種比動態規劃更高效的算法。只是要保證得到最優解是貪心算法的關鍵。
27、貪心算法
圖3
2、揹包問題和0-1揹包問題
    1)0-1揹包問題:
    給定n種物品和一個揹包。物品i的重量是Wi,其價值爲Vi,揹包的容量爲C。應如何選擇裝入揹包的物品,使得裝入揹包中物品的總價值最大?
    在選擇裝入揹包的物品時,對每種物品i只有2種選擇,即裝入揹包或不裝入揹包。不能將物品i裝入揹包多次,也不能只裝入部分的物品i。
    2)揹包問題:
    與0-1揹包問題類似,所不同的是在選擇物品i裝入揹包時,可以選擇物品i的一部分,而不一定要全部裝入揹包,1≤i≤n。
這2類問題都具有最優子結構性質,極爲相似,但揹包問題可以用貪心算法求解,而0-1揹包問題卻不能用貪心算法求解。
首先計算每種物品單位重量的價值Vi/Wi,然後,依貪心選擇策略,將儘可能多的單位重量價值最高的物品裝入揹包。若將這種物品全部裝入揹包後,揹包內的物品總重量未超過C,則選擇單位重量價值次高的物品並儘可能多地裝入揹包。依此策略一直地進行下去,直到揹包裝滿爲止。 
對於0-1揹包問題,貪心選擇之所以不能得到最優解是因爲在這種情況下,它無法保證最終能將揹包裝滿,部分閒置的揹包空間使每公斤揹包空間的價值降低了。事實上,在考慮0-1揹包問題時,應比較選擇該物品和不選擇該物品所導致的最終方案,然後再作出最好選擇。由此就導出許多互相重疊的子問題。這正是該問題可用動態規劃算法求解的另一重要特徵。
三、貪心算法的一般框架
          經由貪心算法處理的問題需要經過排序。即把“最貪心”的子結果排在序列的最前面,一直到“最不貪心的”。這是處理問題的第一步。然後依序解決問題而得出最終結果。歸納起來,貪心算法處理問題需要下面四步:
1、 讀入一個問題
2、 進行貪心排序
3、 處理問題
4、 綜合結果並輸出
“進行貪心排序”和“處理問題”這兩步,是貪心算法的核心部分,甚至能有子問題的重疊和多個貪心問題的重疊。
    一般的,經過快速排序產生子問題結果序列的時間複雜度爲O(nlogn)。
48、貪心算法
四、貪心算法的正確性證明
貪心算法的正確性證明雖然不容易,但一些常見的方法還是值得總結的。 
 a) 構造法   
根據描述的算法,用貪心的策略,依次構造出一個解,可證明一定是合法的解,即用貪心法找可行解。 如
    有N個士兵(1≤N≤26),編號依次爲A,B,C,...。隊列訓練時,指揮官要把一些士兵從高到矮依次排成一行,但現在指揮官不能直接獲得每個人的身高信息,只能獲得“P1比P2高”這樣的比較結果(P1,P2∈A,B,C,...,Z,記爲P1>P2),如“A>B”表示A比B高。 請編一程序,根據所得到的比較結果求出一種符合條件的排隊方案。 注:比較結果中沒有涉及到的士兵不參加排隊 
b) 反證法   
用貪心的策略,依次構造出一個解S1。假設最優解S2不同與S1,可以證明是矛盾的。從而S1就是最優解。如
    在一個超市,有N個人排隊到一個櫃檯付款,已知每個人需要處理的時間爲Ti(0<i≤N),請你找一種排列次序,使每個人排隊的時間總和最小。  
c) 調整法   
    用貪心的策略,依次構造出一個解S1。假設最優解S2不同與S1,找出不同之處,在不破壞最優性的前提下,逐步調整S2,最終使其變爲S1。從而S1也是最優解。
五、用貪心算法解決一個實際問題--------揹包問題
物品
A
B
C
D
F
重量
1
2
3
4
5
價值
3
10
6
3
5
C++程序如下:
//GreedyAlgorithm.h
#include <stdio.h>
using namespace std;
class GreedyAlgorithm
{
public:
GreedyAlgorithm(int _weight[],int _value[],int capacity);
void inputD();
double *ComputeRatio();
void SortRatio(double _Ratio[]);
double ComputeProfit();
private:
int *weight;
int *value;
int capacity;
int classes;
double profit;
};
//GreedyAlgorithm.cpp
#include "GreedyAlgo.h"
#include <iostream>
//================================  
//函數名稱:GreedyAlgorithm  
//函數功能:初始化對象  
//函數參數說明:_weight[] 物品重量,_value[] 物品價值,_capacity 揹包容量  
//函數返回值:void     
//================================ 
GreedyAlgorithm::GreedyAlgorithm(int _weight[],int _value[],int _capacity)
{
weight=_weight;
value=_value;
capacity=_capacity;
profit=0;
classes=5;
}//GreedyAlgorithm::GreedyAlgorithm
//================================  
//函數名稱:inputD  
//函數功能:輸入數據 
//函數返回值:void   
//================================ 
void GreedyAlgorithm::inputD()
{
cout<<"Please input the class of goods:\n";
cin>>classes;
cout<<"Please input the weight and value of "<<classes<<" classes\n";
for(int i=0;i<classes;i++)
cin>>weight[i]>>value[i];
cout<<"input the capacity:\n";
cin>>capacity;
}
//================================  
//函數名稱:ComputeRatio  
//函數功能:計算出物品的單位價值  
//函數返回值:double *   
//================================  
double* GreedyAlgorithm::ComputeRatio()
{
double* Ratio=new double[classes];
for(int i=0;i<classes;i++)
Ratio[i]=(double)value[i]/weight[i];
return Ratio;
}//GreedyAlgorithm::ComputeRatio
//================================  
//函數名稱:SortRatio  
//函數功能:根據單位價值比大小,對物品的價值和重量進行非遞增排序  
//函數返回值:void  
//================================ 
void GreedyAlgorithm::SortRatio(double _Ratio[])
{
for(int i=0;i<classes;i++)
for(int j=i+1;j<classes;j++)
{
if(_Ratio[j]>_Ratio[i])
  {
  int temp=weight[i];
  weight[i]=weight[j];
  weight[j]=temp;
  temp=value[i];  
  value[i]=value[j];  
  value[j]=temp;
  }//if
}//for
}//GreedyAlgorithm::SortRatio
//================================  
//函數名稱:ComputeProfit  
//函數功能:計算揹包的內所放物品的最大價值   
//函數返回值:double  
//================================ 
double GreedyAlgorithm::ComputeProfit()
{
int temp=0,i=0;
while(temp<=capacity)
  {
   if(i==classes) break;
   else
    {
    if((weight[i]+temp)<=capacity)
  {
  profit+=value[i];
  temp+=weight[i];
  }//if
else if((weight[i]+temp)>capacity)
   {
   int _weight=capacity-temp;
   double _Ratio=(double)value[i]/weight[i];
   profit+=_Ratio*_weight;
   temp+=_weight;
   }//else if
}//else
   i++;
  }//while
return profit;
}//GreedyAlgorithm::ComputeProfit
//main.cpp
#include <iostream>
#include "GreedyAlgo.h"
using namespace std;
int main()
{
int _weight[5]={1,2,3,4,5};
int _value[5]={3,10,6,3,5};
int _capacity=7;
GreedyAlgorithm *greedy=new GreedyAlgorithm(_weight,_value,_capacity);
greedy->inputD();
greedy->SortRatio(greedy->ComputeRatio());
cout<<"The Maximun profit is: "<<greedy->ComputeProfit()<<endl;
return 1;
}
六、貪心算法應用的多樣性
(1)構造出次序 
在衆多的選擇中,按預先設計的某種次序來確定當前要找的下一步選擇。如
單源最短路徑:Dijkstra算法 
最小生成樹  
(2)局部(階段)正確 
有時整個問題不能用貪心法,但我們確定了部分因素後,後面的方案就可以用貪心算法了。即通常簡稱:枚舉+貪心  
從汽車過沙漠到登山問題 
(3)快速求得一“較好”可行解
     前面提到的搜索枚舉方案法中,要很多題都可通過貪心算法得到一個“較優可行解”。有時甚至用“啓發+隨機化”多次貪心,能得到一個很好的上界(或下界)。 
(4)快速分枝定界 在搜索過程中,有時也可以用貪心算法預測已有方案以後可能達到的最小值(或最大值),再用“當前最好解”進行分枝定界。  
從原始揹包問題到0/1揹包
七、常見可以用貪心法解決的問題
      有限期的作業排序  
      哈夫曼樹  
      最小生成樹---Prim算法  
      最小生成樹---Kruskla算法 
 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章