1. 程式人生 > >演算法設計模式之貪婪法

演算法設計模式之貪婪法

貪婪法

      貪婪法(greedy algorithm),又稱貪心演算法,是尋找最優解問題的常用方法。這種方法模式一般將求解過程分成若干個步驟,在每個步驟都應用貪心原則,選擇當前狀態下最好的或最優的選擇(區域性最有利的選擇),並以此希望最後堆疊的結果也是最好或最優的解。貪婪法的每次決策都以當前情況為基礎並根據某個最優原則進行選擇,不從整體上考慮其他各種可能的情況。
      貪婪法和動態規劃法以及分治法一樣,都需要對問題進行分解,定義最優解的子結構。但是貪婪法與其他方法最大的不同在於,貪婪法每一步選擇完之後,區域性最優解就確定了,不再進行回溯處理,也就是,每一步驟的區域性最優解確定以後,就不再修改,直到演算法結束。因為不進行回溯處理,貪婪法只是在很少的情況下可以得到真正的最優解。但是貪婪法簡單高效,省去了為趙最優解可能需要的窮舉操作,可以得到與最優解比較接近的近似最優解,通常作為其他演算法的輔助演算法使用。

貪婪法的基本思想

      貪婪法的基本設計思想有以下三個步驟:

  1. 建立對問題精確描述的數學模型,包括定義最優解的模型;
  2. 將問題分解為一系列子問題,同時定義子問題的最優解結構;
  3. 應用貪心原則確定每個子問題的區域性最優解,並根據最優解的模型,用子問題的區域性最優解堆疊出全域性最優解。

揹包問題

      假設一個可裝35磅重東西的揹包,我們可以裝以下三個東西。
在這裡插入圖片描述
      揹包可裝35磅,而音響最貴,我們儘量裝音響,最後揹包沒有空間裝其他東西了。如下圖:
在這裡插入圖片描述


      但是如果我們裝膝上型電腦和吉他,總價將為3500美元。從這裡,我們看出貪婪演算法不一定能獲得最優解,但非常接近。
      上面的0-1揹包問題是一個貪婪法的經典例子:有N件物品和一個承重為C的揹包(也可定義為體積),每件物品的重量是wi,價值是pi,求解將哪幾件物品裝入揹包可使這些物品在重量總和不超過C的情況下價值總和最大。揹包問題是此類組合優化的NP完全問題的統稱,比如貨箱裝載問題、貨船載物問題等,因問題最初來源於如何選擇最合適的物品裝在揹包中而得名。這個問題隱含了一個條件,每個物品只有一件,也就是限定每件物品只能選擇0個或1個,因此又被稱為0-1揹包問題。

示例演示

#include <iostream>   
#include <vector>
#include <algorithm>
using namespace std;

struct BagResult
{
	int sum_value = 0; //最終揹包裡物品的總價值
	int sum_weight = 0; //最終揹包裡物品的總價值
	vector<string> names;
};

struct Goods           //表示每件物品
{
	string name;
	int weight;
	int value;
	Goods(const string& name, int weight, int value)
	{
		this->name = name;
		this->weight = weight;
		this->value = value;
	}
};

bool MaxValue(const Goods& a, const Goods& b)
{
	return a.value > b.value;
}

int main()
{
	int total_weight = 35; //揹包最多能裝的重量
	/* 物品名稱: 音響        膝上型電腦     吉他
	*  重量    : 30磅        20磅          15磅
	*  價值    : 3000美元    2000美元      1500美元
	*/
	vector<Goods> goodslist;
	goodslist.push_back({"音響", 30, 3000});
	goodslist.push_back({"吉他", 15, 1500});
	goodslist.push_back({"膝上型電腦", 20, 2000});

	//將物品按價值最大到最小排序
	sort(goodslist.begin(), goodslist.end(), MaxValue);
	cout << "Sort Result:" << endl;
	for(auto& goods : goodslist)
		cout << goods.name.c_str() << endl;

	BagResult result;
	for(auto& goods : goodslist){
		if(result.sum_weight + goods.weight > total_weight)
			break;
		result.sum_weight += goods.weight;
		result.sum_value += goods.value;
		result.names.push_back(goods.name);
	}
	cout << "Greedy Algorithm Result:" << endl;
	for(auto& name : result.names)
		cout << name.c_str() << endl;
	system("pause");
}

執行結果

在這裡插入圖片描述

總結

      貪婪演算法的優點就是簡單易行,每步都採取最優的做法。用專業術語說,就是你每步都選擇區域性最優解,最終得到的就是全域性最優解。

參考資料

  • 《演算法圖解》[M]
  • 《演算法的樂趣》[M]