【题目】
假设电梯只在某一楼层停,然后乘客下电梯步行至各自目的楼层,求电梯应停在哪一层能使乘客步行的楼层数和最少。
【问题分析】
总论:
采用归纳总结的方法,先分析简单的情况,总结出规律,再推演出算法。
步骤1:分析题目中的输入、输出
假设楼层有7层,在2至7层下电梯的乘客数量分别为a、b、c、d、e、f,电梯停靠在x层,则所有乘客步行的总层数
y = |x-2|*a + |x-3|*b + |x-4|*c + |x-5|*d + |x-6|*e + |x-7|*f
此函数是一个区间段函数,在相邻整数区间段内事一条斜率为正或为负的线段
步骤2:分析区间段
下面对各个区间段进行分析
当2=<x<3,y = (x-2)*a - (x-3)*b - (x-4)*c - (x-5)*d - (x-6)*e - (x-7)*f (1)
= (a-(b+c+d+e+f))*x + (3b+4c+5d+6e+7f) - 2a
当3=<x<4,y = (x-2)*a + (x-3)*b - (x-4)*c - (x-5)*d - (x-6)*e - (x-7)*f (2)
= ((a+b)-(c+d+e+f))*x + (4c+5d+6e+7f) - (2a+3b)
......
当6=<x<7,y = (x-2)*a + (x-3)*b + (x-4)*c + (x-5)*d + (x-6)*e - (x-7)*f (6)
= ((a+b+c+d+e)-f)*x + 7f - (2a+3b+4c+5d+6e)
由于a、b、c、d、e、f为不小于0的整数,
如果a > b+c+d+e+f,则函数式(1)~(6)中x的一次项的值都大于0,即斜率为正,在各个整数区间,y都为向上的折线段,因此当x=2时,y为最小值;
如果a = b+c+d+e+f,则函数式(1)~(6)中x的一次项的值都大于等于0,且前n(1=<n<=6)个算式中x的一次项的值为0,即斜率为0,线段为平行线,后6-n个算式中x的一次项的值大于0,即斜率为正,y为向上的折线段,因此x为集合{x|2=<x<=6, x的一次项的值为0}中的任一值时,y为最小值;
如果a < b+c+d+e+f,即斜率为负,y为向下的折线段,没有最小值,继续分析a+b与c+d+e+f的大小
步骤3:演绎归纳
楼层总楼层为m,在2至m层下电梯的乘客数量分别为n2、n3、n4、…、nm
当 n2+n3…+np-1 < np+np+1…nm
n2+n3…+np-1+np > np+1+np+2…nm
x=p(2<=p<m)时,y为最小值ymin=((n2+…+np) - (np+1+…+nm))*p + np+1*(p+1) + np+2*(p+2) + … + nm*m - n2*2 - n3*3 - … - np*p
当 n2+n3…+np-1 < np+np+1…nm
n2+n3…+np-1+np = np+1+np+2…nm
……
n2+n3…+nq-1+nq = nq+1…nm
n2+n3…+nq+nq+1 > nq+2…nm
x为集合{x|p=<x<=q, 2=<p=<q<=m, p到q的一次项的值等于0}中的任一值时,y为最小值ymin=((n2+…+np) - (np+1+…+nm))*p + np+1*(p+1) + np+2*(p+2) + … + nm*m - n2*2 - n3*3 - … - np*p
当 n2 < n3+…nm
……
n2+n3…+nm-1 < nm
x=m时,y为最小值ymin=((n2+…+np) - (np+1+…+nm))*p + np+1*(p+1) + np+2*(p+2) + … + nm*m - n2*2 - n3*3 - … - np*p
结论:
函数在各个区间的斜率是单调递增的,如果在两个端点出没有最小值,则函数内必然存在最小值的拐点
【解决方案】
1 -module(elevatorberth). 2 -export([start/1]). 3 4 %% 入口函数 5 %% Passengers:形如[{2,3}, {3,5}, {4,2}...]的元祖列表, 6 %% 元祖的第一个参数表示楼层,第二个参数表示在该楼层下电梯的乘客数量 7 start(Passengers) -> 8 [First | Remain] = Passengers, 9 {Floors, MinSteps} = calu([First], Remain, []), 10 io:format("elevator should berth in ~p~n", [Floors]), 11 io:format("min steps ~p~n", [MinSteps]). 12 13 %% 计算停靠层的集合和最小的步行总数 14 %% 算法:将全部楼层分为一段段的射线,元祖表示端点的x、y坐标, 15 %% 将各条射线连接成一条折线,求折线的拐点 16 %% CaluedRaysList:计算过的射线端点列表 17 %% CaluedRaysList:待计算的射线端点列表 18 %% Floors:停靠层的集合 19 20 %% CaluedRaysList为空,全部楼层计算完毕, 21 %% Floors为空,表示折线单调递减 22 calu(CaluedRaysList, [], []) -> 23 [CurrnetRay | _] = CaluedRaysList, 24 {Floor, _} = CurrnetRay, 25 {Floor, min_steps(CaluedRaysList, Floor, 0)}; %停靠在最高楼层 26 27 %% CaluedRaysList为空,全部楼层计算完毕, 28 %% Floors不为空,表示最后一条射线斜率为0 29 calu(CaluedRaysList, [], Floors) -> 30 [CurrnetRay | _] = CaluedRaysList, 31 {Floor, _} = CurrnetRay, 32 {Floors ++ [Floor], min_steps(CaluedRaysList, Floor, 0)}; 33 calu(CaluedRaysList, RemainRaysList, Floors) -> 34 RaySlope = calu_values_sum(CaluedRaysList, 0) - calu_values_sum(RemainRaysList, 0), 35 [CurrnetRay | _] = CaluedRaysList, 36 {Floor, _} = CurrnetRay, 37 [NextRay | NewRaysList] = RemainRaysList, 38 if 39 RaySlope > 0 -> %斜率大于0 40 {Floor, min_steps(CaluedRaysList ++ RemainRaysList, Floor, 0)}; 41 RaySlope =:= 0 -> %斜率等于0 42 calu([NextRay] ++ CaluedRaysList, NewRaysList, Floors ++ [Floor]); 43 RaySlope < 0 -> %斜率小于0 44 calu([NextRay] ++ CaluedRaysList, NewRaysList, Floors) 45 end. 46 47 %% 计算元祖列表中值的算术和 48 calu_values_sum([], Sum) -> 49 Sum; 50 calu_values_sum(Passengers, Sum) -> 51 [Current | Remain] = Passengers, 52 {_, Num} = Current, 53 calu_values_sum(Remain, Sum + Num). 54 55 %% 最小的步行总数 56 min_steps([], _, Value) -> 57 Value; 58 min_steps(Passengers, Floor, Value) -> 59 [Current | Remain] = Passengers, 60 {CurFloor, CurNum} = Current, 61 min_steps(Remain, Floor, Value + abs(Floor-CurFloor) * CurNum).
运行时结果如下: 16> elevatorberth:start([{2,2},{3,1},{4,1},{5,2},{6,4},{7,10}]). elevator should berth in [6,7] min steps 25 ok