1-2、算法设计常用思想之贪婪法

文章内容来自王晓华老师

 

贪心算法,是寻找最优解问题的常用方法
这种方法模式一般将求解过程分成若干个步骤,但每个步骤都应用贪心原则,选取当前状态下最好的或最优的选择(局部最有利的选择),并以此希望最后堆叠出的结果也是最好或最优的解。
因为不进行回溯处理,贪婪法只在很少的情况下可以得到真正的最优解,比如最短路径问题、图的最小生成树问题。
在大多数情况下,由于选择策略的“短视”,贪婪法会错过真正的最优解,而得不到问题的真正答案。但是贪婪法简单、高效,省去了为找最优解可能需要的穷举操作,可以得到与最优解比较接近的近似最优解,通常作为其他算法的辅助算法来使用。

 

贪婪法的基本设计思想有以下三个步骤:
• 建立对问题精确描述的数学模型,包括定义最优解的模型;
• 将问题分解为一系列的子问题,同时定义子问题的最优解结构;
• 应用贪心原则确定每个子问题的局部最优解,并根据最优解的模型,用子问题的局部最优解堆叠出全局最优解。

 

背包问题:有 N 件物品和一个承重为 C 的背包(也可定义为体积),每件物品的重量是 wi,价值是 pi,求解将哪几件物品装入背包可使这些物品在重量总和不超过 C 的情况下价值总和最大。
背包问题(Knapsack Problem)是此类组合优化的NP完全问题的统称,如货箱装载问题、货船载物问题等,因问题最初来源于如何选择最合适的物品装在背包中而得名,这个问题隐含了一个条件,每个物品只有一件,也就是限定每件物品只能选择 0 个或 1 个,因此又被称为 0-1 背包问题

实例

  有一个背包,最多能承载重量为 C=150 的物品,现在有 7 个物品(物品不能分割成任意大小),编号为 1~7,重量分别是 wi=[35,30,60,50,40,10,25],价值分别是 pi=[10,40,30,50,35,40,30],现在从这 7 个物品中选择一个或多个装入背包,要求在物品总重量不超过 C 的前提下,所装入的物品总价值最高。

思路

  常见的贪婪策略有三种:第一种策略是根据物品价值选择,每次都选价值最高的物品,此时包中物品总重量是 130,总价值是 165。第二种策略是根据物品重量选择,每次都选择重量最轻的物品,此时包中物品总重量是 140,总价值是 155。第三种策略是定义一个价值密度的概念,每次选择都选价值密度最高的物品,物品的价值密度 si 定义为 pi/wi,此时包中物品的总重量是 150,总价值是 170,我们可以选用合适的策略,得到较优解

 

通用的解题思路

/**GreedyAlgo() 函数是贪婪算法的主体结构,包括子问题的分解和选择策略的选择都在这个函数中。
能够明显看出来这个算法使用了迭代法的算法模式,当然,这个算法主体的实现还可以使用递归法,
正如函数所展示的那样,它可以作为此类问题的一个通用解决思路:
*/ void GreedyAlgo(KNAPSACK_PROBLEM *problem, SELECT_POLICY spFunc) { int idx; int ntc = 0; //spFunc 每次选最符合策略的那个物品,选后再检查 while((idx = spFunc(problem->objs, problem->totalC - ntc)) != -1) { //所选物品是否满足背包承重要求? if((ntc + problem->objs[idx].weight) <= problem->totalC) { problem->objs[idx].status = 1; ntc += problem->objs[idx].weight; } else { //不能选这个物品了,做个标记后重新选 problem->objs[idx].status = 2; } } PrintResult(problem->objs); } /**spFunc 参数是选择策略函数的接口,通过替换这个参数,可以实现上文提到的三种贪婪策略,
分别得到各种贪婪策略下得到的解。以第一种策略为例,每次总是选择 price 最大的物品,可以这样实现:
*/ int Choosefunc1(std::vector<OBJECT>& objs, int c) { int index = -1; //-1表示背包容量已满 int mp = 0; for(int i = 0; i < static_cast<int>(objs.size()); i++) { if((objs[i].status == 0) && (objs[i].price > mp)) { mp = objs[i].price; index = i; } } return index; }

 

lua代码实现

-- 物品
-- obj = {
--     weight = 0,
--     price = 0,
--     status = 0  -- 0未选中  1已选中 2已不可选
-- } 

-- 背包
-- bag = {
--     obj_list = {},
--     totalC = 0
-- }

local function choose_func1(obj_bag, use_c)
    local index = -1  ---1表示背包容量已满
    local mp = 0
    for i = 1, #obj_bag.obj_list do
        if((obj_bag.obj_list[i].status == 0) and obj_bag.obj_list[i].price > mp) then
            mp = obj_bag.obj_list[i].price
            index = i
        end
    end
    return index
end

local function greedy_algo(bag, spFunc)
    local idx = nil
    local ntc = 0 -- 已用重量
    idx = spFunc(bag, ntc)
    while(idx ~= -1) do
        if(ntc + bag.obj_list[idx].weight) <= bag.totalC then
            bag.obj_list[idx].status = 1
            ntc = ntc + bag.obj_list[idx].weight
        else
            bag.obj_list[idx].status = 2
        end
        idx = spFunc(bag, ntc)
    end
    print("==============11111")
    dump(bag.obj_list)
end

local bag_1 = {
    obj_list = {
        {weight = 10, price = 10, status = 0},
        {weight = 20, price = 20, status = 0},
        {weight = 30, price = 40, status = 0},
        {weight = 40, price = 30, status = 0},
        {weight = 50, price = 50, status = 0},
        {weight = 60, price = 70, status = 0},
    },
    totalC = 150
}

greedy_algo(bag_1, choose_func1)

 

总结

贪婪法只能得到比较接近最优解的近似最优解,但是作为一种启发式辅助方法在很多算法中都得到了广泛的应用,很多常用的算法在解决局部最优决策时,都会应用到贪婪法。比如 Dijkstra 的单源最短路径算法在从 dist 中选择当前最短距离的节点时,就是采用的贪婪法策略。事实上,在任何算法中,只要在某个阶段使用了只考虑局部最优情况的选择策略,都可以理解为使用了贪婪算法。

posted @ 2019-05-28 21:27  orxx  阅读(832)  评论(0编辑  收藏  举报