1. 程式人生 > >演算法--貪心演算法的應用

演算法--貪心演算法的應用

一、思想

貪心演算法在每一步做出當時看起來最佳的選擇,也就是說總是做出區域性最優的選擇,希望這樣能得到全域性最優解,貪心演算法不一定能得到最優解,產生最優解的條件是:

1.最優子結構;

2.貪心選擇性:當一個問題的全域性解可以通過區域性最優解得到,就稱這個問題具有貪心選擇性。

證明思路:假定首選元素不是貪心選擇所要的元素,證明將首元素替換成貪心選擇所需元素,依然得到最優解;數學歸納法證明每一步均可通過貪心選擇得到最優解。

一定要和動態規劃區分開!動態規劃具有最優子結構和子問題重疊性的性質,適用貪心演算法時,動態規劃可能不使用,類似的,適用動態規劃時,貪心演算法可能不適用。

二、應用例題

【1】poj2393:已知每週加工1單元的乳酪需要C_i分,儲存1單元的乳酪需要S分,乳酪不會變質,在第i周的時候需要向客戶提供Y_i單元重量的乳酪,這些乳酪可以是本週生產的,也可能是前幾周儲存的,設計一個演算法使其花費最小。

輸入:週數N,儲存單價:S;以及第i周的加工成本C_i和第i周的需求量Y_i;輸出:最小的成本價格

思路:剛開始想這道題的時候想的太複雜,會想著某一週的牛奶可能一部分是本週生產的或一部分是前面幾周生產的,但完全沒必要,第i周的成本或者是C_i或者是前面第k周的成本加上儲存的價格C_k+(i-k)*s,只需要選擇其中的最小值,計算成本即可。

程式碼:

#include<cstdio>
const int size=10000;
int main()
{
/*提示輸入週數和儲存成本單價*/
int n,s;
printf("請輸入週數:\n");
scanf("%d",&n);
printf("請輸入儲存成本單價:\n");
scanf("%d",&s);
/*提示輸入每週的成本單價和需求量*/
int c[size];
int y[size];
printf("請輸入成本單價和需求量:\n");
for(int i=1;i<=n;i++)
{
scanf("%d%d",&c[i],&y[i]);
}
/*選擇每週的最小成本單價*/
for(int i=1;i<=n;i++)
{
for(int j=i;j>0;j--)
{
if(c[j]+(i-j)*s<=c[i])
{
c[i]=c[j]+(i-j)*s;
}
}
}
/*計算所有周的最小花費*/
long long cost=0;
for(int i=1;i<=n;i++)
{
cost+=c[i]*y[i];
}
printf("%d周需要的最小花費是:%lld",n,cost);
}

【2】poj1328:雷達裝置,x軸上方是海洋,其中有n個小島,x軸下方是陸地;在海岸線上放置雷達,所能探測到的最大距離是d,即可以探測到半徑為d的圓,設計一種演算法使得用最少的雷達探測到所有的小島,其中小島的位置用(xi,yi)座標表示。輸入:n(小島的數目),d(雷達探測的距離)及小島的座標值,輸出由測試用例號和雷達的數量構成。

思路:剛開始想這道題時覺得應該依次設雷達的位置為從最左邊的點到最右邊的點之間的橫座標,但這樣是不對的,首先雷達的位置不一定是整數值,所以是不能直接暴力求解的,使用貪心演算法,從左到右儘可能多的覆蓋島嶼,以島嶼為圓心,探測距離為半徑畫圓,如果與橫座標軸無交點,說明該島嶼不能被覆蓋到,則不能實現直接返回0;若有交點,則將與x軸的交點儲存在陣列point[i].sta和point[i].end,分別代表左右交點。首先將所有圓按照point[i].end進行排序,然後依次從左到右選擇point[i].end對應的圓。

若point[i].end>point[i+1].sta,則第i個圓可以覆蓋下一個島嶼,則更新島嶼,選擇新的島嶼進行檢視;否則需要建立新的雷達。

程式碼如下:

#include<iostream>
#include<math.h>
#include<algorithm>
using namespace std;
const int size=1000;
//定義以小島為圓心與x軸的交點的結構體陣列
struct data
{
 float sta,end;//sta即左交點座標值,end是右交點座標值
}point[size];
//定義按右交點從小到大排序的方法
bool cmp(data a,data b)
{
 if(a.end<b.end)
 {
  return true;
 }
 else
 {
  return false;
 }
} int main()
{
 /*輸入小島數目和雷達探測距離*/
 int n,d;
 cout<<"請輸入小島數目:"<<endl;
 cin>>n;
 cout<<"請輸入雷達探測距離:"<<endl;
 cin>>d;
 /*輸入各小島的位置座標*/
 cout<<"請輸入各小島的座標值:"<<endl;
 int x[size];
 int y[size];
 int max_y=0;//定義小島中最大的y值
 for (int i=0;i<n;i++)
 {
  cin>>x[i]>>y[i];
  if(y[i]>max_y)
  {
   max_y=y[i];
  }
 }
 /*判斷小島y座標值是否大於d,若是則無法實現*/
 if(max_y>d||d<0)
 {
  cout<<"該問題值不合理無法實現。";
 } 
 /*以小島為圓心,d為半徑做圓,得到該圓與x軸的交點*/
 else
 {
  float m;
  for(int i=0;i<n;i++)
  {
   m=sqrt(d*d*1.0-y[i]*y[i]*1.0);
   point[i].sta=x[i]-m;
   point[i].end=x[i]+m;
  }
  sort(point,point + n,cmp);
   
  int number = 0; //定義雷達的數量
        bool cover[size];  //標記島嶼是否被覆蓋到
        //memset(vis, false, sizeof(vis)); 
        for(int i = 0; i < n; i ++) 
        {   //  類似的活動選擇。 
            if(!cover[i]) 
            { 
                cover[i] = true; 
                for(int j = 0; j < n; j ++) 
                    if(!cover[j] && point[j].sta <= point[i].end) 
                        cover[j] = true; 
                    number ++; 
            } 
        } 
  cout<<"最少需要的雷達數目是:"<<endl;
  cout<<number;
 } 
 return 0;
} 【3】非0-1揹包問題:給定n個物品,物品價值分別為P1,P2,...,Pn,物品重量分別W1,W2,...Wn,揹包重量為M。每種物品可部分裝入到揹包中,輸出X1,X2,...,Xn,0<=Xi<=1,使得PiXi的和最大,且WiXi的和不超過M,試設計一個演算法求解該問題,分析演算法的正確性。 思路: 0-1揹包問題可以用動態規劃來求解,每次對物品有選中和不選中兩種選擇,而該問題物品可以切割部分放入,則使用貪心演算法。使用貪心演算法時可以有多種選擇的標準: (1)按價值降序排序;(2)按重量升序排序;(3)按單位價值降序排序;(4)按單位重量排序。根據問題是要求價值最大,所以選擇第三種排序方法,定義V(n,M)為n件物品,容量為M時的價值。 首先計算每種物品的單位價值,然後降序排序,即u[i];然後依次放入揹包,若W[i]<=M,則可將該物品全部放入,則V(n,M)=p[i]+V(n-1,M-W[i]); 若W[i]>M,則將該物品部分放入,V(n,M)=P[i]*u[i],M=0. 程式碼如下: #include<iostream>
#include<algorithm>
using namespace std;
const int size=100;
struct data
{
 float u,w,p; 
} t[size];
bool cmp(data a,data b)
{
 if(a.u>b.u)
 {
  return true;
 }
 else
 {
  return false;
 }
}
int main()
{
 /*輸入物品數量、揹包容量*/
 int n,m;
 cout<<"請輸入物品件數:"<<endl;
 cin>>n;
 cout<<"請輸入揹包容量:"<<endl;
 cin>>m;
 /*請輸入每件物品的價值和重量*/
 cout<<"請輸入每件物品的價值和重量:"<<endl;
 for(int i=0;i<n;i++)
 {
  cin>>t[i].p>>t[i].w;
 }
 /*計算每件物品的單位價值,並進行排序*/
 for(int i=0;i<n;i++)
 {
  t[i].u=t[i].p/t[i].w;
 }
 sort(t,t+n,cmp);
 
 /*定義每件物品放進去的量,初始化為0*/
 float x[size];
 for(int i=0;i<n;i++)
 {
  x[i]=0;
 }
 /*求解*/
 float v[size];//定義總價值
 for(int i=0;i<n;i++)
 {
  /*如果排好序的第i件物品重量小於m,則全部放入*/
  if(t[i].w<=m)
  {
   x[i]=1;
   m-=t[i].w;
   v[i]=t[i].p;
  }
  else
  {
   x[i]=m/t[i].w;
   m=0;
   v[i]=t[i].p*x[i]; 
  } 
 }
 cout<<"物品按單位價值排序為:"<<endl;
 for(int i=0;i<n;i++)
 {
  cout<<t[i].u<<" "; 
 }
 cout<<endl;
 cout<<"對應該單價排序的物品存放比例依次為:"<<endl;
 for(int i=0;i<n;i++)
 {
  cout<<x[i]<<" "; 
 } 
 cout<<endl;
 float value=0.0;
 for(int i=0;i<n;i++)
 {
  value+=v[i];
 }
 cout<<"最大價值為:"<<value;
 return 0;
}
【4】給定直線上2n個點的序列P{1,2,...,2n},每個點要麼是白點要麼是黑點,其中共有n個白點和n個黑點,相鄰兩個點之間距離均為1,請設計一個演算法將每個白點與一黑點相連,使得連線的總長度最小。 思路:假設p[i]=0表示黑點,p[i]=1表示白點,d[i][j]表示從 第i個點到第j個點的距離,則有d[i][j]=0 (p[i]=p[j]); d[i][j]=j-i (p[i]!=p[j])。使用貪心演算法,為每個白點加上標誌:f[i]=0(p[i]=1)表示該點還沒有和黑點連線,若被使用,則更新標誌為1。每次找d[i][j](p[i]!=p[j])的最小值,最後將其值相加。使用棧進行操作。