独立任务最优调度问题

问题:独立任务最优调度,又称双机调度问题:用两台处理机A和B处理n个作业。设第i个作业交给机器A处理时所需要的时间是a[i],若由机器B来处理,则所需要的时间是b[i]。现在要求每个作业只能由一台机器处理,每台机器都不能同时处理两个作业。设计一个动态规划算法,使得这两台机器处理完这n个作业的时间最短(从任何一台机器开工到最后一台机器停工的总的时间)。研究一个实例:n=6, a = {2, 5, 7, 10, 5, 2}, b = {3, 8, 4, 11, 3, 4}.

分析:当完成k个作业,设机器A花费了x时间,机器B所花费时间的最小值肯定是x的一个函数,设F[k][x]表示机器B所花费时间的最小值。则F[k][x]=Min{ F[k-1][x]+b[k], F[k-1][x-a[k]] };其中F[k-1][x]+b[k]表示第k个作业由机器B来处理(完成k-1个作业时机器A花费的时间仍是x),F[k-1][x-a[k]]表示第k个作业由机器A处理(完成k-1个作业时机器A花费的时间是x-a[k])。

      那么单个点对较大值Max(x, F[k][x]),表示此时(即机器A花费x时间的情况下)所需要的总时间。而机器A花费的时间x是变化的,即x=0,1,2……x(max),(理论上x的取值是离散的,但为编程方便,设为整数连续的)由此构成了点对较大值序列。要求整体时间最短,取这些点对较大值序列中最小的即是。

    理解难点在于B是A的函数表达式,也即动态规划所在。花点时间,看懂表达式,加上思考,理解了这点一切OK,后面的编程实现完全依据这个思想。先用前两个任务的枚举示例来帮助理解。

示例:前两个作业示例足矣。

初始化第一个作业:下标以0开始。

首先,机器A所花费时间的所有可能值范围:0 <= x <= a[0].
设x<0时,设F[0][x]= ∞,则max(x, ∞)= ∞;记法意义见下。
x=0时,F[0][0]=3,则Max(0,3)=3,机器A花费0时间,机器B花费3时间,而此时两个机器所需时间为3; 
x=1时,F[0][1]=3,Max(1,3)=3;
x=2时,F[0][2]=0,则Max(2,0)=2;

那么上面的点对序列中,可以看出当x=2时,完成第一个作业两台机器花费最少的时间为2,此时机器A花费2时间,机器B花费0时间。

来看第二个作业

       首先,x的取值范围是:0 <= x <= (a[0] + a[1]).
       当x<0时,记F[1][x] = ∞;这个记法编程使用,因为数组下标不能小于0。在这里的实际含义是:x是代表完成前两个作业机器A的时间,a[1]是机器A完成第2个作业的时间,若x<a[1],则势必第2个作业由机器B来处理,即在Min()中取前者。

x=0,则F[1][0]= Min{ F[0][0]+b[2], F[0][0-a[1]] }= Min{3+8,∞}=11,进而Max(0,11)=11;
x=1,则F[1][1]= Min{ F[0][1]+b[2], F[0][1-a[1]] }= Min{3+8,∞}=11,进而Max(11)=11;
x=2,则F[1][2]= Min{ F[0][2]+b[2], F[0][2-a[1]] }= Min{0+8,∞}=8,进而Max(2,8)=8;
x=3,则F[1][3]= Min{ F[0][3]+b[2], F[0][3-a[1]] }= Min{0+8,∞}=8,进而Max(3,8)=8;
x=4,则F[1][4]= Min{ F[0][4]+b[2], F[0][4-a[1]] }= Min{0+8,∞}=8,进而Max(4,8)=8;
x=5,则F[1][5]= Min{ F[0][5]+b[2], F[0][5-a[1]] }= Min{0+8,3}=3,进而Max(5,3)=5;
x=6,则F[1][6]= Min{ F[0][6]+b[2], F[0][6-a[1]] }= Min{0+8,3}=3,进而Max(6,3)=6;
x=7,则F[1][7]= Min{ F[0][7]+b[2], F[0][7-a[1]] }= Min{0+8,0}=0,进而Max(7,0)=7;

那么上面的点对序列中,可以看出当x=5时,完成两个作业两台机器花费最少的时间为5,此时机器A花费5时间,机器B花费3时间。


算法时间复杂度:按照上述思想,编程实现,结果如上图,算法时间复杂度为O(n*Sum),其中Sum为a中所有元素之和与b中所有元素之和的最小值。王晓东《算法设计与实验题解》中提供的方法时间复杂度是O(m*m*n*n*n),其中m为a、b所有元素的最大值。
      比较:Sum必然远远小于n*m;所以n*Sum<n*n*m,相比之下差了O(n*m)都不止,看来这个思想还是蛮优秀的,窃喜……

      这个是去年算法课上的一道作业,当时绞尽脑汁想了好几天,中间不乏出现过投机的方法,因为这道题的数据设计比较巧合,貌似也得到了正确结果,但随便时间序列换位一下即可发现错误。最搞笑的是助教在课堂上给出的答案正是投机取巧的错误答案。错误的解答如下,错误的原因对比下自己去想想吧。
     T=max(T,min(tai-1+ai,tbi-1+bi))其中tai表示做完第i个任务a的等待时间,同理tbi。
      Initial: T=tai=tbi=0;  
      i=1 ,T=max(0,min(0+2,0+3))=2;
      i=2 ,T=max(2,min(2+5,0+8))=7;
      i=3 ,T=max(7,min(7+7,0+4))=7;
      i=4 ,T=max(7,min(7+10,4+11))=15;
      i=5 ,T=max(15,min(7+5,15+3))=15;
      i=6 ,T=max(15,min(12+2,15+4))=15。

以下是我用C#编写的程序代码,保存了完成前K项任务的所有最优值,并记录了完成机器的顺序。编程思想完全基于前述分析,程序代码中并附有说明。用C++的同学把二维数组的写法还有输出等变换一下即可。为说明问题,并没有进行过多代码上的优化,还有改进之处。附送一张图,输出了一些数组的值(美观起见,没有输出完),方便理解。


using System;
namespace zydd
{
    class Program
    {
        //独立任务最优调度函数,参数为两组任务和任务个数,最优时间和顺序结果。
        static void DlrwZydd(int[] a, int[] b, int n, int[] least, string[] result)
        {
            //首先给一个大值。
            for (int i = 0; i < n; i++)
            {
                least[i] = 99;
            }

            //若任务只有一台机器完成,求得两个时间。
            int aSum = 0, bSum = 0;
            for (int i = 0; i < n; i++)
            {
                aSum += a[i];
                bSum += b[i];
            }
            //小值加1作为数组的列数,减少存储空间。
            int Sum = 1 + Math.Min(aSum, bSum);

            //创建四个行数列数相同的数组,timeA存储机器A可能用的时间,timeB存储对应机器B用的时间,
            //timeMax记录两者共需的时间,即较大的那个;who则标识完成该任务的机器是A还是B。
            int[,] timeA = new int[n, Sum];
            int[,] timeB = new int[n, Sum];
            int[,] timeMax = new int[n, Sum];
            char[,] who = new char[n, Sum];
            char[] tempRlt = new char[n];//tempRlt记录机器完成任务的机器顺序,并逐一赋值给result


            //先计算第1个任务相关值,记录在四个数组的第0行。
            for (int i = 0; i <= a[0]; i++)
            {
                timeA[0, i] = i;
                if (i < a[0])
                {
                    timeB[0, i] = b[0];
                    who[0, i] = 'b';
                }
                else
                {
                    timeB[0, i] = 0;
                    who[0, i] = 'a';
                }
                timeMax[0, i] = Math.Max(timeA[0, i], timeB[0, i]);
            }

            //尽管像下面一样,直接比较即可得出完成第1项任务的最优时间,但由于使用动态规划,计算上述值是必需的。
            if (a[0] <= b[0])
            {
                least[0] = a[0];
                tempRlt[0] = 'a';
            }
            else
            {
                least[0] = b[0];
                tempRlt[0] = 'b';
            }
            result[0] = new String(tempRlt);         

            //计算第2个至第n个任务,分别记录在四个数组相应行。
            for (int k = 1; k < n; k++)
            {

                //tempSum记录完成前k项任务机器A最多需要的时间,即全部由A完成需要的时间,亦即机器A所有可能的取值范围。
                int tempSum = 0;
                for (int temp = 0; temp <= k; temp++)
                {
                    tempSum += a[temp];
                }
                //计算出所有可能的点对(timeA,timeB),并取值timeMax。
                for (int i = 0; i <= tempSum; i++)
                {
                    //机器A在完成前k项任务时所花费的时间为i。
                    timeA[k, i] = i;

                    //i即timeA[k, i],若机器A完成前k项任务的时间 小于 它完成第k项的时间,可能吗?不可能,所以第k项任务肯定由机器B做。
                    if (i < a[k])
                    {
                        timeB[k, i] = timeB[k - 1, i] + b[k];
                        who[k, i] = 'b';
                    }
                    //按照前述动态规划方式的思想,确定机器A在花费i时间时,机器B花费的最优时间。
                    else
                    {
                        if ((timeB[k - 1, i] + b[k]) <= timeB[k - 1, i - a[k]])
                        {
                            timeB[k, i] = timeB[k - 1, i] + b[k];
                            who[k, i] = 'b';
                        }
                        else
                        {
                            timeB[k, i] = timeB[k - 1, i - a[k]];
                            who[k, i] = 'a';
                        }
                    }
                    //两台机器花费时间较大的那个为总花费时间。
                    timeMax[k, i] = Math.Max(timeA[k, i], timeB[k, i]);
                }

                //处理数组tempSum后面的值。机器A时间全部设为最大,此时机器B则无需花费时间。
                for (int i = tempSum + 1; i < aSum; i++)
                {
                    timeA[k, i] = tempSum;
                    timeB[k, i] = 0;
                }

                //完成第k项任务后,在timeMax所有可能值中,选取最小值即最优值。
                int flag = 0;//记录最优值所在的位置i值,同时也是机器A所花费的时间。
                for (int i = 0; i <= tempSum; i++)
                {
                    if (timeMax[k, i] > 0 && timeMax[k, i] < least[k])
                    {
                        least[k] = timeMax[k, i];
                        flag = i;
                    }
                }

                //用来回溯所用机器的顺序,需要注意的是机器A所花费的时间和数组的下标一样,因此可用做标记。
                //最后一项任务的回溯路径已在示意图中标出,便于理解。
                tempRlt[k] = who[k, flag];
                for (int i = k; i > 0 && flag > 0; i--)
                {
                    //如果是机器A完成第i项任务,则机器A花费的时间减去a[i]就是完成前i-1项任务的时间。
                    //同时在who中可以确定前一项任务的机器,因为机器A花费的时间可用做位置标记。
                    if (tempRlt[i] == 'a')
                    {
                        flag -= a[i];
                        tempRlt[i - 1] = who[i - 1, flag];

                    }
                    if (tempRlt[i] == 'b')
                    {
                        tempRlt[i - 1] = who[i - 1, flag];
                    }
                }

                result[k] = new String(tempRlt);
            }    
        }

        static void Main(string[] args)
        {
            const int N = 6;
            int[] a = new int[N] { 2, 5, 7, 10, 5, 2 };
            int[] b = new int[N] { 3, 8, 4, 11, 3, 4 };
            int[] least = new int[N];   //记录完成任务的最优时间
            string[] result = new string[N];    //记录完成任务机器的顺序

            DlrwZydd(a, b, N, least, result);
            Console.WriteLine();
            for (int i = 0; i < N; i++)
            {
                Console.WriteLine(" 按要求完成前{0}项任务的机器顺序为:" + result[i] + " 时间为:{1}" ,i+1,least[i]);
            }
        }
    }
}

转自http://hi.baidu.com/liongg/item/03938f0fcd75f58a03ce1b6d

  • 1
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值