貪心算法

貪婪算法

貪心算法(Greedy Algorithm) 簡介貪心算法,又名貪婪法,是尋找最優解問題的經常使用方法,這種方法模式通常將求解過程分紅若干個步驟,但每一個步驟都應用貪心原則,選取當前狀態下最好/最優的選擇(局部最有利的選擇),並以此但願最後堆疊出的結果也是最好/最優的解。{看着這個名字,貪心,貪婪這兩字的內在含義最爲關鍵。這就好像一個貪婪的人,他事事都想要眼前看到最好的那個,看不到長遠的東西,也不爲最終的結果和未來着想,貪圖眼前局部的利益最大化,有點走一步看一步的感受。}ios

貪婪法的基本步驟:

步驟1:從某個初始解出發;算法

步驟2:採用迭代的過程,當能夠向目標前進一步時,就根據局部最優策略,獲得一部分解,縮小問題規模;編程

步驟3:將全部解綜合起來。函數

事例一:找零錢問題假設你開了間小店,不能電子支付,錢櫃裏的貨幣只有 25 分、10 分、5 分和 1 分四種硬幣,若是你是售貨員且要找給客戶 41 分錢的硬幣,如何安排才能找給客人的錢既正確且硬幣的個數又最少?這裏須要明確的幾個點:1.貨幣只有 25 分、10 分、5 分和 1 分四種硬幣;2.找給客戶 41 分錢的硬幣;3.硬幣最少化思考,能使用咱們今天學到的貪婪算法嗎?怎麼作?(回顧一下上文貪婪法的基本步驟,1,2,3)優化

  1. 找給顧客sum_money=41分錢,可選擇的是25 分、10 分、5 分和 1 分四種硬幣。能找25分的,不找10分的原則,初次先找給顧客25分;
  2. 還差顧客sum_money=41-25=16。而後從25 分、10 分、5 分和 1 分四種硬幣選取局部最優的給顧客,也就是選10分的,此時sum_money=16-10=6。重複迭代過程,還須要sum_money=6-5=1,sum_money=1-1=0。至此,顧客收到零錢,交易結束;
  3. 此時41分,分紅了1個25,1個10,1個5,1個1,共四枚硬幣。

編程實現spa

#include<iostream>
using namespace std;

#define ONEFEN    1
#define FIVEFEN    5
#define TENFEN    10
#define TWENTYFINEFEN 25

int main()
{
    int sum_money=41;
    int num_25=0,num_10=0,num_5=0,num_1=0;

    //不斷嘗試每一種硬幣
    while(money>=TWENTYFINEFEN) { num_25++; sum_money -=TWENTYFINEFEN; }
    while(money>=TENFEN) { num_10++; sum_money -=TENFEN; }
    while(money>=FIVEFEN)  { num_5++;  sum_money -=FIVEFEN; }
    while(money>=ONEFEN)  { num_1++;  sum_money -=ONEFEN; }

    //輸出結果
    cout<< "25分硬幣數:"<<num_25<<endl;
    cout<< "10分硬幣數:"<<num_10<<endl;
    cout<< "5分硬幣數:"<<num_5<<endl;
    cout<< "1分硬幣數:"<<num_1<<endl;

    return 0;
}

事例二:揹包最大價值問題有一個揹包,最多能承載重量爲 C=150的物品,如今有7個物品(物品不能分割成任意大小),編號爲 1~7,重量分別是 wi=[35,30,60,50,40,10,25],價值分別是 pi=[10,40,30,50,35,40,30],如今從這 7 個物品中選擇一個或多個裝入揹包,要求在物品總重量不超過 C 的前提下,所裝入的物品總價值最高。這裏須要明確的幾個點:code

  1. 每一個物品都有重量和價值兩個屬性;
  2. 每一個物品分被選中和不被選中兩個狀態(後面還有個問題,待討論);
  3. 可選物品列表已知,揹包總的承重量必定。

因此,構建描述每一個物品的數據體結構 OBJECT和揹包問題定義爲blog

//typedef是類型定義的意思
//定義待選物體的結構體類型
typedef struct tagObject
{
    int weight;
    int price;
    int status;
}OBJECT;

//定義揹包問題
typedef struct tagKnapsackProblem
{
    vector<OBJECT>objs;
    int totalC;
}KNAPSACK_PROBLEM;

這裏採用定義結構體的形式,主要是能夠減小代碼的書寫量,能夠實現代碼的複用性和可擴展性,簡化,提升可讀性。就是貪圖簡單方便,規避繁瑣。it

以下,實例化io

objectsOBJECT objects[] = { { 35,10,0 },{ 30,40,0 },{ 60,30,0 },{ 50,50,0 },
                    { 40,35,0 },{ 10,40,0 },{ 25,30,0 } };

思考:如何選,才使得裝進揹包的價值最大呢?

策略1:價值主導選擇,每次都選價值最高的物品放進揹包;

策略2:重量主導選擇,每次都選擇重量最輕的物品放進揹包;

策略3:價值密度主導選擇,每次選擇都選價值/重量最高的物品放進揹包。

(貪心法則:求解過程分紅若干個步驟,但每一個步驟都應用貪心原則,選取當前狀態下最好的或最優的選擇(局部最有利的選擇),並以此但願最後堆疊出的結果也是最好或最優的解)

策略1:價值主導選擇,每次都選價值最高的物品放進揹包根據這個策略最終選擇裝入揹包的物品編號依次是 四、二、六、5,此時包中物品總重量是 130,總價值是 165。

//遍歷沒有被選的objs,而且選擇price最大的物品,返回被選物品的編號
int Choosefunc1(std::vector<OBJECT>& objs, int c)
{
    int index = -1;  //-1表示揹包容量已滿
    int max_price = 0;
    //在objs[i].status == 0的物品裏,遍歷挑選objs[i].price最大的物品
    for (int i = 0; i < static_cast<int>(objs.size()); i++)
    {
        if ((objs[i].status == 0) && (objs[i].price > max_price ))//objs沒有被選,而且price> max_price 
        {
            max_price  = objs[i].price;
            index = i;
        }
    }

    return index;
}

策略2:重量主導選擇,每次都選擇重量最輕(小)的物品放進揹包根據這個策略最終選擇裝入揹包的物品編號依次是 六、七、二、一、5,此時包中物品總重量是 140,總價值是 155。

int Choosefunc2(std::vector<OBJECT>& objs, int c)
{
    int index = -1;
    int min_weight= 10000;
    for (int i = 0; i < static_cast<int>(objs.size()); i++)
    {
        if ((objs[i].status == 0) && (objs[i].weight < min_weight))
        {
            min_weight= objs[i].weight;
            index = i;
        }
    }

    return index;
}

策略3:價值密度主導選擇,每次選擇都選價值/重量最高(大)的物品放進揹包物品的價值密度 si 定義爲 pi/wi,這 7 件物品的價值密度分別爲 si=[0.286,1.333,0.5,1.0,0.875,4.0,1.2]。根據這個策略最終選擇裝入揹包的物品編號依次是 六、二、七、四、1,此時包中物品的總重量是 150,總價值是 170。

int Choosefunc3(std::vector<OBJECT>& objs, int c)
{
    int index = -1;
    double max_s = 0.0;
    for (int i = 0; i < static_cast<int>(objs.size()); i++)
    {
        if (objs[i].status == 0)
        {
            double si = objs[i].price;
            si = si / objs[i].weight;
            if (si > max_s)
            {
                max_s = si;
                index = i;
            }
        }
    }

    return index;
}

有了物品,有了方法,下面就是將二者結合起來的貪心算法

GreedyAlgovoid GreedyAlgo(KNAPSACK_PROBLEM *problem, SELECT_POLICY spFunc)
{
    int idx;
    int sum_weight_current = 0;
    //先選
    while ((idx = spFunc(problem->objs, problem->totalC- sum_weight_current)) != -1)
    {   //再檢查,是否能裝進去
        if ((sum_weight_current + problem->objs[idx].weight) <= problem->totalC)
        {
            problem->objs[idx].status = 1;//若是揹包沒有裝滿,還能夠再裝,標記下裝進去的物品狀態爲1
            sum_weight_current += problem->objs[idx].weight;//把這個idx的物體的重量裝進去,計算當前的重量
        }
        else
        {
            //不能選這個物品了,作個標記2後從新選剩下的
            problem->objs[idx].status = 2;
        }
    }
    PrintResult(problem->objs);//輸出函數的定義,查看源代碼
}

注意:這裏對objs[idx].status定義了三種狀態,分別是待選擇爲0(初始全部狀態均爲0),裝進包裏變爲1,判斷不符合變爲2,這樣最後只須要拿去狀態爲1的便可。主函數部分

OBJECT objects[] = { { 35,10,0 },{ 30,40,0 },{ 60,30,0 },{ 50,50,0 },
                    { 40,35,0 },{ 10,40,0 },{ 25,30,0 } };
int main()
{
    KNAPSACK_PROBLEM problem;

    problem.objs.assign(objects, objects + 7);//assign賦值,std::vector::assign
    problem.totalC = 150;

    cout << "Start to find the best way ,NOW" << endl;
    GreedyAlgo(&problem, Choosefunc3);

    system("pause");
    return 0;
}

查看策略3的輸出結果:

image

可是,咱們再回顧一下第一個事例問題如今問題變了,仍是須要找給顧客41分錢,如今的貨幣只有 25 分、20分、10 分、5 分和 1 分四種硬幣;該怎麼辦?按照貪心算法的三個步驟:1.41分,局部最優化原則,先找給顧客25分;2.此時,41-25=16分,還須要找給顧客10分,而後5分,而後1分;3.最終,找給顧客一個25分,一個10分,一個5分,一個1分,共四枚硬幣。是否是以爲哪裏不太對,若是給他2個20分,加一個1分,三枚硬幣就能夠了呢?^_^;總結:貪心算法的優缺點優勢:簡單,高效,省去了爲了找最優解可能須要窮舉操做,一般做爲其它算法的輔助算法來使用;缺點:不從整體上考慮其它可能狀況,每次選取局部最優解,再也不進行回溯處理,因此不多狀況下獲得最優解。

相關文章
相關標籤/搜索
本站公眾號
   歡迎關注本站公眾號,獲取更多信息