伐木问题

一片环形的林带按照时钟方向分成N块相邻的林地,首尾相连,每一块林地均与另两块林地相邻。伐木工对每一块林地里可采伐的木材量进行了估计,这些木材量以 int[] woods 来表示。这个数组按顺时针顺序列出了每一块林地里的可采伐木材量。

现在要在这片林带中选择一些林地来进行采伐。但为了不破坏生态,不能有两块相邻的林地同时被采伐。你必须编写程序来计算最大可采伐的木材量。

 

函数声明:int maxWoods(int[] woods)

参数范围:woods可以包含2到50个元素。每一个元素的取值范围为1到1000。

 

测试用例:

1:{10, 3, 2, 5, 7, 8}

输出:19

注:因为林地首尾相连,所以不能同时取10和8。在此限制下,最大的开采量为10+2+7=19。

 

2:{11, 15}

输出:15

 

3:{7, 7, 7, 7, 7, 7, 7}

输出:21

 

4:{1, 2, 3, 4, 5, 1, 2, 3, 4, 5}

输出:16

 

5:{94, 40, 49, 65, 21, 21, 106, 80, 92, 81, 679, 4, 61, 6, 237, 12, 72, 74, 29, 95, 265, 35, 47, 1, 61, 397, 52, 72, 37, 51, 1, 81, 45, 435, 7, 36, 57, 86, 81, 72}

输出:2926

 

分析:

状态转移方程:

两种状态:

    1).包含i 

    2).不包含i

情况一:包含i

F(i) = Max{F(i-2) + a[i], a[i]},所以当F(i-2) < 0,时, F(i) = a[i],else: F(i) = F(i-2)+a[i]

情况二:不包含i

F(i) = F(i-1)

综合一二: F(i)= Max{Max{F(i-2)+a[i],a[i]}, F(i-1)}

初始化:

F(0)= a[0]

F(1) = Max{a[0], a[1]}

具体实现有下面三个方法:

方法一:

1. 递归实现

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace ConsoleApplication4
{
    class Program
    {
        static void Main(string[] args)
        {
            int[] input0 = { 11, 15 };
            // GetMaxSubArrayValue(input0);
            int[] input1 = { 94, 40, 49, 65, 21, 21, 106, 80, 92, 81, 679, 4, 61, 6, 237, 12, 72, 74, 29, 95, 265, 35, 47, 1, 61, 397, 52, 72, 37, 51, 1, 81, 45, 435, 7, 36, 57, 86, 81, 72 };
            int temp = GetMaxGroupValueOuter(input1);

        }

        // 方法一: 采用递归
        public static int GetMaxGroupValueOuter(int[] arrary)
        {
            int endIndex = arrary.Length - 1;
            int startIndex = 0;

            if (endIndex == 0)
            {
                return arrary[0];
            }

            if (endIndex == 1)
            {
                return Math.Max(arrary[0], arrary[1]);
            }

            int temp1 = GetMaxGroupValue(arrary, startIndex, endIndex - 1);
            int temp2 = GetMaxGroupValue(arrary, startIndex + 1, endIndex);
            return Math.Max(temp1, temp2);
        }

        // 采用递归
        public static int GetMaxGroupValue(int[] arrary, int startIndex, int endIndex)
        {
            if (startIndex == 0
             || startIndex == 1)
            {
                if (endIndex == startIndex)
                {
                    return arrary[startIndex];
                }

                if (endIndex == startIndex + 1)
                {
                    return Math.Max(arrary[startIndex], arrary[startIndex + 1]);
                }
            }
            
            int temp1 = GetMaxGroupValue(arrary, startIndex, endIndex - 2);

            if (temp1 < 0) //F(i-2)  <0;
            {
                temp1 = arrary[endIndex];
                // 
                //f(i)  = Max(F(i), F(i-2) +a[i], F(i-1)};
                //
            }
            else
            {
                temp1 += arrary[endIndex];
            }

            int temp2 = GetMaxGroupValue(arrary, startIndex, endIndex - 1);

            return Math.Max(temp1, temp2);
        }
    }
}

 

方法二:

   1.利用空间来换取时间,提高效率

   2.自底而上,顺序求解

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace ConsoleApplication4
{
    class Program
    {
        static void Main(string[] args)
        {
            int[] input0 = { 11, 15 };
            // GetMaxSubArrayValue(input0);
            int[] input1 = { 94, 40, 49, 65, 21, 21, 106, 80, 92, 81, 679, 4, 61, 6, 237, 12, 72, 74, 29, 95, 265, 35, 47, 1, 61, 397, 52, 72, 37, 51, 1, 81, 45, 435, 7, 36, 57, 86, 81, 72 };
            int temp = GetMaxGroupValueOuter2(input1);

        }
        public static int GetMaxGroupValueOuter2(int[] arrary)
        {
            int endIndex = arrary.Length - 1;
            int startIndex = 0;

            if (endIndex == 0)
            {
                return arrary[0];
            }

            if (endIndex == 1)
            {
                return Math.Max(arrary[0], arrary[1]);
            }

            int temp1 = GetGroupMax(arrary, startIndex, endIndex - 1);
            int temp2 = GetGroupMax(arrary, startIndex + 1, endIndex);
            return Math.Max(temp1, temp2);
        }

        // 方法二。自底而上,顺序求解
        public static int GetGroupMax(int[] array, int startIndex, int endIndex)
        {
            if (startIndex == 0
                || startIndex == 1)
            {
                if (endIndex == startIndex)
                {
                    return array[startIndex];
                }

                if (endIndex == startIndex + 1)
                {
                    return Math.Max(array[startIndex], array[startIndex + 1]);
                }
            }

            int[] values = new int[endIndex - startIndex + 1];

            for (int j = 0; j < endIndex - startIndex + 1; j++)
            {
                values[j] = int.MinValue;
            }

            //初始化F(0), F(1)
            values[0] = array[startIndex];
            values[1] = Math.Max(array[startIndex], array[startIndex + 1]);

            // 初始化sum
            int index = 0;           

            for (int i = 2; i < endIndex - startIndex + 1; i++)
            {
                if (values[i - 2] > 0)
                {
                    values[i] = Math.Max(values[i - 1], values[i - 2] + array[i + startIndex]);
                }
                else
                {
                    values[i] = Math.Max(values[i - 1], array[i]);
                }
            }

            for (int i = 1; i < endIndex - startIndex + 1; i++)
            {
                if (values[i] > values[index])
                {
                    index = i;
                }
            }

            return values[index];
        }
    }

}

 


方法三

 1.采用迭代

2. 自底而上,顺序求解,优化空间

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace ConsoleApplication4
{
    class Program
    {
        static void Main(string[] args)
        {
            int[] input0 = { 11, 15 };
            // GetMaxSubArrayValue(input0);
            int[] input1 = { 94, 40, 49, 65, 21, 21, 106, 80, 92, 81, 679, 4, 61, 6, 237, 12, 72, 74, 29, 95, 265, 35, 47, 1, 61, 397, 52, 72, 37, 51, 1, 81, 45, 435, 7, 36, 57, 86, 81, 72 };

            int temp = GetMaxGroupValueOuter3(input1);
        }
        // 方法三:采用迭代,自底而上,顺序求解,优化空间
        public static int GetMaxGroupValueOuter3(int[] arrary)
        {
            int endIndex = arrary.Length - 1;
            int startIndex = 0;

            if (endIndex == 0)
            {
                return arrary[0];
            }

            if (endIndex == 1)
            {
                return Math.Max(arrary[0], arrary[1]);
            }

            int temp1 = GetMaxSubArrayValue(arrary, startIndex, endIndex - 1);
            int temp2 = GetMaxSubArrayValue(arrary, startIndex + 1, endIndex);
            return Math.Max(temp1, temp2);
        }

        // 求不连续数组的子数组之和的最大值.
        public static int GetMaxSubArrayValue(int[] array, int startIndex, int endIndex)
        {
            int sum = array[0];

            int temp1 = array[startIndex]; // F(i-2)
            int temp2 = array[startIndex + 1];  // F(i-1)

            // F(i)  = Max (F(i-2) + array[i], array[i], F(i-1)
            // 采用迭代更替的方法,逐步更新. temp2代表 F(i-1)的最大值,temp1比temp2始终慢一步,代表F(i-2)的值.

            if (startIndex == 0
               || startIndex == 1)
            {
                if (endIndex == startIndex)
                {
                    return array[startIndex];
                }

                if (endIndex == startIndex + 1)
                {
                    return Math.Max(array[startIndex], array[startIndex + 1]);
                }
            }

            for (int i = startIndex + 2; i <= endIndex; i++)
            {
                if (temp1 < 0)
                {    //情况一。 F(i-2) < 0;很明显 F(i-2) + a[i] < a[i]
                    temp1 = array[i];
                    //s = e = i;
                }
                else //情况二 F(i-2) = F(i-2)+a[i]
                {
                    temp1 += array[i];
                    // e = i;
                }

                // 到这一步时, F(i-2) = Max{(F(i-2), F(i-2) + a[i])}

                // 情况三。不包含a[i]的情况,即a[0]~a[i-1]的最大值,设为sum.

                if (temp1 > sum) //用F(i-2)和sum比较
                {
                    sum = temp1;
                }

                temp1 = temp2;
                temp2 = sum;

            }

            return sum;
        }
    }
       
}

 

 

总结:1. 此题非常类似于2.14 求数组的子数组之和的最大值和最小值(动态规划),但又有所不同,主要在于此题的不连续性。所以才用在方法二中采用两个临时变量,来表示当前的最大值,和前一个最大值,交替实现。别的地方恶化子数组之和的最大值问题完全一样。

   2. 此题实际上是由不连续子数组最大和问题延伸而来

         http://www.cnblogs.com/freewater/archive/2012/08/18/2645777.html

         3.  和01背包问题也非常类似(选i和不选i)

          http://www.cnblogs.com/jiangjun/archive/2012/05/08/2489590.html


 

posted on 2013-06-06 10:49  higirle  阅读(549)  评论(0编辑  收藏  举报