1. 程式人生 > >貪心演算法總結(轉)

貪心演算法總結(轉)

一、演算法的基本情況說明:

     貪心演算法的定義:在求最優解問題的過程中,依據某種貪心標準,從問題的初始狀態出發,直接去求每一步的最優解,通過若干次的貪心選擇,最終得出整個問題的最優解,這種求解方法就是貪心。

     從貪心演算法的定義可以看出,貪心演算法不是從整體上考慮問題,它所做出的選擇只是在某種意義上的區域性最優解,而由問題自身的特性決定了該題運用貪心演算法可以得到最優解。

貪心演算法存在問題: 
    1. 不能保證求得的最後解是最佳的; 
    2. 不能用來求最大或最小解問題; 
    3. 只能求滿足某些約束條件的可行解。

 貪心演算法適用的條件:
       問題的求解可以由一系列的決策步驟構成,每步決策依賴於某種區域性最優的貪心策略。正確的貪心策略要保證每一步基於區域性優化性質的選擇最終導致全域性的最優解。
如果不具有上述性質,貪心法對某些例項只能得到近似解。這就要求我們在求解貪心類的問題的時候我們將問題分成幾個步驟,然後按步驟的進行,利用我們所設定的貪心原則,先求每一步的區域性最優解最後求出全域性最優解。

利用貪心策略解題,需要解決兩個問題:
    (1)該題是否適合於用貪心策略求解;
    (2)如何選擇貪心標準,以得到問題的最優/較優解。
     當我們選擇好貪心標準後,我們是需要對這個貪心標準進行證明的,驗證這個貪心標準是有效的。但是證明往往是比較困難的。這需要紮實的數學功底。 這裡簡單的給我證明貪心標準的兩個方法:     (1)數學歸納法     (2)交換論證發 二、典型例題分類分析
       1.活動安排問題         設有n個活動的集合E={1,2,…,n},其中每個活動都要求使用同一資源,如演講會場等,而在同一時間內只有一個活動能使用這一資源。每個活動i都有一個要求使用該資源的起始時間si和一個結束時間fi,且si<fi。如果選擇了活動i,則它在半開時間區間[si ,fi )內佔用資源。若區間[si ,fi )與區間[sj,fj )不相交,則稱活動i與活動j是相容的。當 si ≥ fj 或 sj ≥ fi 時,活動i與活動j相容。活動安排問題就是在所給的活動集合中選出最大的相容活動子集合。        首先我們分析,想要安排最多的活動,那就必須得讓那些結束的早的活動先進行,這樣才會有更多的時間來安排其他的活動,這樣我們就確定了我們的貪心思。 資料結構
struct action{
int s; //起始時間
int f;         //結束時間
int index; //活動的編號
};
活動的集合E記為陣列:
action a[1000];
按活動的結束時間升序排序
排序比較因子:
bool cmp(const action &a, const action &b)
{
if (a.f<=b.f) return true;
return false;
}
使用標準模板庫函式排序(下標0未用):
sort(a, a+n+1, cmp);
//形引數組b用來記錄被選中的活動
void GreedySelector(int n, action a[], bool b[])
{
  b[1] = true;     //第1個活動是必選的
  //記錄最近一次加入到集合b中的活動
  int preEnd = 1;
  for(int i=2; i<=n; i++)
    if (a[i].s>=a[preEnd].f)
    {
      b[i] = true;
      preEnd = i;
    }
}

       2.揹包問題

       給定一個載重量為M的揹包,考慮n個物品,其中第i個物品的重量 ,價值wi (1≤i≤n),要求把物品裝滿揹包,且使揹包內的物品價值最大。
有兩類揹包問題(根據物品是否可以分割),如果物品不可以分割,稱為0—1揹包問題(動態規劃);如果物品可以分割,則稱為揹包問題(貪心演算法)。

       有3種方法來選取物品:
(1)當作0—1揹包問題,用動態規劃演算法,獲得最優值220;
(2)當作0—1揹包問題,用貪心演算法,按價效比從高到底順序選取物品,獲得最優值160。由於物品不可分割,剩下的空間白白浪費。
(3)當作揹包問題,用貪心演算法,按價效比從高到底的順序選取物品,獲得最優值240。由於物品可以分割,剩下的空間裝入物品3的一部分,而獲得了更好的效能。

       

struct bag{
int w;
//物品的重量
int v;
//物品的價值
double c;
//價效比
}a[1001]; //存放物品的陣列
排序因子(按價效比降序):
bool cmp(bag a, bag b){
return a.c >= b.c;
}
使用標準模板庫函式排序(最好使用stable_sort()函式,在價效比相同時保持輸入的順序):
sort(a, a+n, cmp);

//形參n是物品的數量,c是揹包的容量M,陣列a是按物品的價效比降序排序
double knapsack(int n, bag a[], double c)
{
  double cleft = c;        //揹包的剩餘容量
  int i = 0;
  double b = 0;          //獲得的價值
  //當揹包還能完全裝入物品i
  while(i<n && a[i].w<cleft)
  {
    cleft -= a[i].w;
    b += a[i].v;
    i++;
  }
  //裝滿揹包的剩餘空間
  if (i<n) b += 1.0*a[i].v*cleft/a[i].w;
  return b;
}

        3.釣魚問題

問題描述: 
約翰有h(1≤h≤16)個小時的時間,在該地區有n(2≤n≤25)個湖,這些湖剛好分佈在一條路線上,該路線是單向的。約翰從湖1出發,他可以在任一個湖結束釣魚。但他只能從一個湖到達另一個與之相鄰的湖,而且不必每個湖都停留。 
假設湖i(i=1~n—1),以5分鐘為單位,從湖i到湖i+1需要的時間用ti(0<ti≤192)表示。例如t3=4,是指從湖3到湖4需要花20分鐘時間。 
已知在最初5分鐘,湖i預計釣到魚的數量為fi(fi≥0)。以後每隔5分鐘,預計釣到魚的數量將以常數di(di≥0)遞減。如果某個時段預計釣到魚的數量小於或等於di,那麼在下一時段將釣不到魚。為簡單起見,假設沒有其它的釣魚者影響約翰的釣魚數量。 
編寫程式,幫助約翰制定釣魚旅行的計劃,以便儘可能多的釣到魚。 
問題要求: 
輸入 
對每組測試例,第一行是n,接下來一行是h。 下面一行是n個整數fi(1≤i≤n),然後是一行n個整數di (1≤i≤n),最後一行是n—1個整數ti(1≤i≤n—1)。 
輸入 
對每組測試例,第一行是n,接下來一行是h。 下面一行是n個整數fi(1≤i≤n),然後是一行n個整數di (1≤i≤n),最後一行是n—1個整數ti(1≤i≤n—1)。 
對每個測試例,輸出在每個湖上花費的時間,這是約翰要實現釣到最多的魚的計劃(必須使整個計劃在同一行輸出)。接下來一行是釣到的魚的數量。(如果存在很多方案,儘可能選擇在湖1釣魚所耗費的時間,即使有些時段沒有釣到魚;如果還是無法區分,那就儘可能選擇在湖2釣魚所耗費的時間,以此類推。) 
問題分析: 
1)資料結構 
每個湖預計釣到魚的數量,定義為陣列:#define NUM 30 
int f[NUM]; 
每個湖預計釣到魚的數量的遞減值,定義為陣列: 
int d[NUM]; 
相鄰湖之間的旅行時間,定義為陣列: 
int t[NUM]; 
釣魚計劃,定義為陣列: 
int plan[NUM]; 
湖的個數n,用於釣魚的時間h,儘可能多的釣魚數量best。 
2)列舉在任意一個湖結束釣魚時的最優釣魚計劃 
首先把用於釣魚的時間h,由小時轉換為以5分鐘為單位的時間: h=h×60/5; 
這樣把釣5分鐘魚的時間稱為釣一次魚。由於約翰從湖1出發,可以在任一個湖結束釣魚,要得到最優解,就需要進行搜尋。 
3)採用貪心策略,每次選擇魚最多的湖釣一次魚 
對於每個湖來說,由於在任何時候魚的數目只和約翰在該湖裡釣魚的次數有關,和釣魚的總次數無關,所以這個策略是最優的。一共可以釣魚time次,每次在n個湖中選擇魚最多的一個湖釣魚。 
採用貪心演算法構造約翰的釣魚計劃。 
可以認為約翰能從一個湖“瞬間轉移”到另一個湖,即在任意一個時刻都可以從湖1到湖pos中任選一個釣一次魚。 

//從湖1起到湖pos止,花費時間time(不含路程)的釣魚計劃
void greedy(int pos, int time)

  if (time <= 0) return;      //時間已經用完
  int i, j;
  int fish[MAXN];
  int p[MAXN];
  int t = 0; 
  for (i = 0; i < pos; ++i) 
    fish[i] = f[i]; 
  memset(p, 0, sizeof(p)); 
  ……
}

//在時間time內,選擇魚最多的湖釣魚;如果魚都沒有了,就把時間放在湖1上
for (i = 0; i < time; ++i)

  int max = 0; //魚最多的湖中,魚的數量
  int id = -1;     //魚最多的湖的編號
  //查詢魚最多的湖中,魚的數量和湖的編號
  for (j = 0; j < pos; ++j)
    if (fish[j] > max){ 
      max = fish[j]; 
      id = j; 
    } 
  if (id != -1)      //找到了,進行釣魚處理
  {
    ++p[id]; 
    fish[id] -= d[id]; 
    t += max; 
  }
  //沒有找到(從湖1起到湖pos全部釣完了),就把時間放在湖1上
  else ++p[0]; 

//處理最優方案
if (t > best)

  best = t;         //最優值
  memset(plan, 0, sizeof(plan));
  for (i = 0; i < pos; ++i)  //最優解
    plan[i] = p[i]; 
}

輸出釣魚計劃時,再把5乘回去,就變成實際的釣魚時間(分鐘):
for (i=0; i<n-1; ++i) 
printf(“%d, “, plan[i] * 5);
printf(“%d\n”, plan[n-1] * 5); 
printf(“Number of fish expected: %d\n”, best);

        </div>
            </div>

一、演算法的基本情況說明:

     貪心演算法的定義:在求最優解問題的過程中,依據某種貪心標準,從問題的初始狀態出發,直接去求每一步的最優解,通過若干次的貪心選擇,最終得出整個問題的最優解,這種求解方法就是貪心。

     從貪心演算法的定義可以看出,貪心演算法不是從整體上考慮問題,它所做出的選擇只是在某種意義上的區域性最優解,而由問題自身的特性決定了該題運用貪心演算法可以得到最優解。

貪心演算法存在問題: 
    1. 不能保證求得的最後解是最佳的; 
    2. 不能用來求最大或最小解問題; 
    3. 只能求滿足某些約束條件的可行解。

 貪心演算法適用的條件:
       問題的求解可以由一系列的決策步驟構成,每步決策依賴於某種區域性最優的貪心策略。正確的貪心策略要保證每一步基於區域性優化性質的選擇最終導致全域性的最優解。
如果不具有上述性質,貪心法對某些例項只能得到近似解。這就要求我們在求解貪心類的問題的時候我們將問題分成幾個步驟,然後按步驟的進行,利用我們所設定的貪心原則,先求每一步的區域性最優解最後求出全域性最優解。

利用貪心策略解題,需要解決兩個問題:
    (1)該題是否適合於用貪心策略求解;
    (2)如何選擇貪心標準,以得到問題的最優/較優解。
     當我們選擇好貪心標準後,我們是需要對這個貪心標準進行證明的,驗證這個貪心標準是有效的。但是證明往往是比較困難的。這需要紮實的數學功底。 這裡簡單的給我證明貪心標準的兩個方法:     (1)數學歸納法     (2)交換論證發 二、典型例題分類分析        1.活動安排問題         設有n個活動的集合E={1,2,…,n},其中每個活動都要求使用同一資源,如演講會場等,而在同一時間內只有一個活動能使用這一資源。每個活動i都有一個要求使用該資源的起始時間si和一個結束時間fi,且si<fi。如果選擇了活動i,則它在半開時間區間[si ,fi )內佔用資源。若區間[si ,fi )與區間[sj,fj )不相交,則稱活動i與活動j是相容的。當 si ≥ fj 或 sj ≥ fi 時,活動i與活動j相容。活動安排問題就是在所給的活動集合中選出最大的相容活動子集合。        首先我們分析,想要安排最多的活動,那就必須得讓那些結束的早的活動先進行,這樣才會有更多的時間來安排其他的活動,這樣我們就確定了我們的貪心思。 資料結構
struct action{
int s; //起始時間
int f;         //結束時間
int index; //活動的編號
};
活動的集合E記為陣列:
action a[1000];
按活動的結束時間升序排序
排序比較因子:
bool cmp(const action &a, const action &b)
{
if (a.f<=b.f) return true;
return false;
}
使用標準模板庫函式排序(下標0未用):
sort(a, a+n+1, cmp);
//形引數組b用來記錄被選中的活動
void GreedySelector(int n, action a[], bool b[])
{
  b[1] = true;     //第1個活動是必選的
  //記錄最近一次加入到集合b中的活動
  int preEnd = 1;
  for(int i=2; i<=n; i++)
    if (a[i].s>=a[preEnd].f)
    {
      b[i] = true;
      preEnd = i;
    }
}

       2.揹包問題

       給定一個載重量為M的揹包,考慮n個物品,其中第i個物品的重量 ,價值wi (1≤i≤n),要求把物品裝滿揹包,且使揹包內的物品價值最大。
有兩類揹包問題(根據物品是否可以分割),如果物品不可以分割,稱為0—1揹包問題(動態規劃);如果物品可以分割,則稱為揹包問題(貪心演算法)。

       有3種方法來選取物品:
(1)當作0—1揹包問題,用動態規劃演算法,獲得最優值220;
(2)當作0—1揹包問題,用貪心演算法,按價效比從高到底順序選取物品,獲得最優值160。由於物品不可分割,剩下的空間白白浪費。
(3)當作揹包問題,用貪心演算法,按價效比從高到底的順序選取物品,獲得最優值240。由於物品可以分割,剩下的空間裝入物品3的一部分,而獲得了更好的效能。

       

struct bag{
int w;
//物品的重量
int v;
//物品的價值
double c;
//價效比
}a[1001]; //存放物品的陣列
排序因子(按價效比降序):
bool cmp(bag a, bag b){
return a.c >= b.c;
}
使用標準模板庫函式排序(最好使用stable_sort()函式,在價效比相同時保持輸入的順序):
sort(a, a+n, cmp);

//形參n是物品的數量,c是揹包的容量M,陣列a是按物品的價效比降序排序
double knapsack(int n, bag a[], double c)
{
  double cleft = c;        //揹包的剩餘容量
  int i = 0;
  double b = 0;          //獲得的價值
  //當揹包還能完全裝入物品i
  while(i<n && a[i].w<cleft)
  {
    cleft -= a[i].w;
    b += a[i].v;
    i++;
  }
  //裝滿揹包的剩餘空間
  if (i<n) b += 1.0*a[i].v*cleft/a[i].w;
  return b;
}

        3.釣魚問題

問題描述: 
約翰有h(1≤h≤16)個小時的時間,在該地區有n(2≤n≤25)個湖,這些湖剛好分佈在一條路線上,該路線是單向的。約翰從湖1出發,他可以在任一個湖結束釣魚。但他只能從一個湖到達另一個與之相鄰的湖,而且不必每個湖都停留。 
假設湖i(i=1~n—1),以5分鐘為單位,從湖i到湖i+1需要的時間用ti(0<ti≤192)表示。例如t3=4,是指從湖3到湖4需要花20分鐘時間。 
已知在最初5分鐘,湖i預計釣到魚的數量為fi(fi≥0)。以後每隔5分鐘,預計釣到魚的數量將以常數di(di≥0)遞減。如果某個時段預計釣到魚的數量小於或等於di,那麼在下一時段將釣不到魚。為簡單起見,假設沒有其它的釣魚者影響約翰的釣魚數量。 
編寫程式,幫助約翰制定釣魚旅行的計劃,以便儘可能多的釣到魚。 
問題要求: 
輸入 
對每組測試例,第一行是n,接下來一行是h。 下面一行是n個整數fi(1≤i≤n),然後是一行n個整數di (1≤i≤n),最後一行是n—1個整數ti(1≤i≤n—1)。 
輸入 
對每組測試例,第一行是n,接下來一行是h。 下面一行是n個整數fi(1≤i≤n),然後是一行n個整數di (1≤i≤n),最後一行是n—1個整數ti(1≤i≤n—1)。 
對每個測試例,輸出在每個湖上花費的時間,這是約翰要實現釣到最多的魚的計劃(必須使整個計劃在同一行輸出)。接下來一行是釣到的魚的數量。(如果存在很多方案,儘可能選擇在湖1釣魚所耗費的時間,即使有些時段沒有釣到魚;如果還是無法區分,那就儘可能選擇在湖2釣魚所耗費的時間,以此類推。) 
問題分析: 
1)資料結構 
每個湖預計釣到魚的數量,定義為陣列:#define NUM 30 
int f[NUM]; 
每個湖預計釣到魚的數量的遞減值,定義為陣列: 
int d[NUM]; 
相鄰湖之間的旅行時間,定義為陣列: 
int t[NUM]; 
釣魚計劃,定義為陣列: 
int plan[NUM]; 
湖的個數n,用於釣魚的時間h,儘可能多的釣魚數量best。 
2)列舉在任意一個湖結束釣魚時的最優釣魚計劃 
首先把用於釣魚的時間h,由小時轉換為以5分鐘為單位的時間: h=h×60/5; 
這樣把釣5分鐘魚的時間稱為釣一次魚。由於約翰從湖1出發,可以在任一個湖結束釣魚,要得到最優解,就需要進行搜尋。 
3)採用貪心策略,每次選擇魚最多的湖釣一次魚 
對於每個湖來說,由於在任何時候魚的數目只和約翰在該湖裡釣魚的次數有關,和釣魚的總次數無關,所以這個策略是最優的。一共可以釣魚time次,每次在n個湖中選擇魚最多的一個湖釣魚。 
採用貪心演算法構造約翰的釣魚計劃。 
可以認為約翰能從一個湖“瞬間轉移”到另一個湖,即在任意一個時刻都可以從湖1到湖pos中任選一個釣一次魚。 

//從湖1起到湖pos止,花費時間time(不含路程)的釣魚計劃
void greedy(int pos, int time)

  if (time <= 0) return;      //時間已經用完
  int i, j;
  int fish[MAXN];
  int p[MAXN];
  int t = 0; 
  for (i = 0; i < pos; ++i) 
    fish[i] = f[i]; 
  memset(p, 0, sizeof(p)); 
  ……
}

//在時間time內,選擇魚最多的湖釣魚;如果魚都沒有了,就把時間放在湖1上
for (i = 0; i < time; ++i)

  int max = 0; //魚最多的湖中,魚的數量
  int id = -1;     //魚最多的湖的編號
  //查詢魚最多的湖中,魚的數量和湖的編號
  for (j = 0; j < pos; ++j)
    if (fish[j] > max){ 
      max = fish[j]; 
      id = j; 
    } 
  if (id != -1)      //找到了,進行釣魚處理
  {
    ++p[id]; 
    fish[id] -= d[id]; 
    t += max; 
  }
  //沒有找到(從湖1起到湖pos全部釣完了),就把時間放在湖1上
  else ++p[0]; 

//處理最優方案
if (t > best)

  best = t;         //最優值
  memset(plan, 0, sizeof(plan));
  for (i = 0; i < pos; ++i)  //最優解
    plan[i] = p[i]; 
}

輸出釣魚計劃時,再把5乘回去,就變成實際的釣魚時間(分鐘):
for (i=0; i<n-1; ++i) 
printf(“%d, “, plan[i] * 5);
printf(“%d\n”, plan[n-1] * 5); 
printf(“Number of fish expected: %d\n”, best);

        </div>
            </div>