0-1背包问题-回溯&贪心算法-C#Demo

概述

     回溯算法的效率不是很高,就起本质而言,属于穷举,不同的是提供了一个穷举的思路:回溯。回溯算法也称试探法,基本思想是:从一条路往前走,能进则进。

what

     回溯法采用试错的思想,它尝试分步的去解决一个问题。在分步解决问题的过程中,当它通过尝试发现现有的分步答案不能得到有效的正确的解答的时候,它将取消上一步甚至是上几步的计算,再通过其它的可能的分步解答再次尝试寻找问题的答案。回溯法通常用最简单的递归方法来实现,在反复重复上述的步骤后可能出现两种情况:
    1.找到一个可能存在的正确的答案
    2.在尝试了所有可能的分步方法后宣告该问题没有答案

特点

     深度搜索,确定了解空间的组织结构后,回溯法就从开始结点(根结点)出发,以深度优先的方式搜索整个解空间。这个开始结点就成为一个活结点,同时也成为当前的扩展结点。在当前的扩展结点处,搜索向纵深方向移至一个新结点。这个新结点就成为一个新的活结点,并成为当前扩展结点。如果在当前的扩展结点处不能再向纵深方向移动,则当前扩展结点就成为死结点。此时,应往回移动(回溯)至最近的一个活结点处,并使这个活结点成为当前的扩展结点。

     剪枝,回溯法是一个即带有系统性又带有跳跃性的搜索算法,当节点肯定不包含,则跳过对以该结点为根的子树的系统搜索,逐层向其祖先结点回溯,剪枝使得回溯算法效率提高了。

     有去有回,去按照深度遍历,回体现了回溯的思想。

C# Demo

     对于回溯的理解,是在代码的基础上不断加深的,分享一下自己写的代码,希望大家可以拍砖斧正~

 #region 回溯+贪心--刘雅雯--2017年9月26日21:36:29

        /// <summary>
        /// 从K节点扩展下去,计算得到的最大价值
        /// </summary>
        /// <param name="values"></param>
        /// <param name="weight"></param>
        /// <param name="VM"></param>
        /// <param name="n"></param>
        /// <param name="W"></param>
        /// <param name="Profit_Gained">最大价值</param>
        /// <param name="weight_Used">背包使用的容量</param>
        /// <param name="k">k节点</param>
        /// <returns></returns>
        float Bound(float[] values, float[] weight, float[] VW, int n, float W, float Profit_Gained, float weight_Used, int k)
        {
            int i;
            for ( i = k+1; i <n; i++)
            {
                if (weight_Used+weight[i]<=W) //背包容量可以放下整个物品
                {
                    Profit_Gained += values[i];
                    weight_Used += weight[i];

                }
                else
                {
                    Profit_Gained+=VW[i]*(W-weight_Used); //放不下整个物品,放部分
                    weight_Used = W;
                    return Profit_Gained;
                }
            }
            return Profit_Gained;
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="values"></param>
        /// <param name="weight"></param>
        /// <param name="VW"></param>
        /// <param name="n"></param>
        /// <param name="W"></param>
        /// <returns></returns>
        int[,] Knapsack(float[] values, float[] weight, float[] VW, int n, float W)
        {

            int count=0; //存可行解

            int[,] result = new int[9,8];
            int[,] Newresult = new int[9, 8];

            float current_weight = 0;   //背包当前的使用重量
            float current_profit = 0;
            float Weight = 0;   //最终背包的使用重量
            float Profit = -1;  //背包最终的价值 @@@@和current_profit关系
            int index = 0;
            int[] status = new int[9];
            int[] newStatus=new int[8];
            while (true) //@@@可以省略吗,如果省略的话要有一个回调的函数在这里调用,保证向后循环
            {
                while (index<n && current_weight+weight[index]<=W)  //物品可以整个放入背包
                {
                    current_weight += weight[index];
                    current_profit += values[index];
                    status[index] = 1;  //物品在背包中
                    index++;
                }
                if (index>=n)  //@@@物品放完了???回答:物品判断完了最后一个
                {
                   Weight= current_weight;
                   Profit = current_profit;
                   index = n;

                   for (int i = 0; i < n; i++)
                   {
                       newStatus[i] =status[i]; //物品的状态重新保存
                   }
                }
                else
                {
                    status[index] = 0; // 不在包中,状态为0
                }
                while(Bound(values,weight,VW,n,W,current_profit,current_weight,index)<=Profit) //@@@何时满足条件,第一次满足:遍历完物品,之后条件不固定
                {                                                                              //当>的时候说明有继续追踪的必要,可能会放进包中
                                                                                               //当<=的时候,说明该节点没有扩展的必要了,可以剪枝
                    //装下可行性的结果
                    for (int j = 0; j < newStatus.Length; j++)
                    {
                        result[count, j] = newStatus[j];
                    }
                    count++;


                    while (index!=0 && status[index]!=1)//@@@!=1是什么样的节点?物品没有放进去的节点 相当于出口
                    {
                        index--;
                    }
                    if (index==0)
                    {

                        return result;
                    }            
                    status[index] = 0;  //1的情况已经考虑,现在要考虑O的情况了
                    current_weight -= weight[index];
                    current_profit -= values[index];
                }
                index++; //遍历
            }
        }
        #endregion

     调用方式如下:

private void backtracking_Click(object sender, EventArgs e)
        {
            //物品容量和价值的对应
            float[] value = new float[8] { 11, 21, 31, 33, 43, 53, 55, 65 };
            float[] weight = new float[8] { 1, 11, 21, 23, 33, 43, 45, 55 };
            float[] VM = new float[8] { 11, 1.9F,1.5F,1.4F,1.3F,1.23F,1.2F,1.2F };
            int[,] c = new int[0,0];
            c = Knapsack(value, weight, VM, 8, 110);

            for (int i = 0; i < c.Length/8; i++)
            {

                for (int j = 0; j < 8; j++)
                {
                    labBacktracking.Text = labBacktracking.Text + c[i, j].ToString() + ",";
                }
                labBacktracking.Text = labBacktracking.Text + "\r\n";
            }
        }

结语

     算法我们要从不同的角度去深入和了解,不断的思考,每次会有不同的体会。

《0-1背包问题-回溯&贪心算法-C#Demo》

    原文作者:贪心算法
    原文地址: https://blog.csdn.net/SweetyoYY/article/details/78178588
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞