|
点评69楼guostong的答案, 希望作者修改:
column name format a10;
column name_BACK format a10;
column name_HIST format a20;
var num number;
------ num 为每次过河的人数限制
exec :num := 3;
with
t_accessory (totalsteps, cnt, md) as
(
select ceil((count(1)-1)/(:num-1)) + ceil((count(1)-1)/(:num-1)) - 1 totalsteps ---- 这个totalsteps用来限制总步数,它的前提是每次过河都是尽可能更多的人,但这个前提是错的,有时候过河的人更少反而节省总体的时间, 例子见94楼。
, count(1) cnt
, mod(count(1)-1,2) md ------ 总人数是奇数还是偶数
from bridge_crossing
),
t_positions (name, time, ps, od) as
(
select name, time, power(2,row_number() over (order by name) -1) ps, ---- 每个人编为二进制的一位。
rank() over (order by time) od --------- 按速度排序,用的是RANK()所以相同速度的人会有相同的序号, 可考虑改为ROW_NUMBER()
from bridge_crossing
),
t_combinations ( name, ps, p_ps, time, step, od ) as ------- 用递归的方法生成 1~num人的所有组合,每种组合需要耗费的时间。
(
select name, ps, ps, time, 1 step, od from t_positions
union all
select t.name||' ' || b.name, t.ps + b.ps, b.ps, greatest(t.time, b.time), step + 1, t.od
from t_combinations t, t_positions b
where b.ps > t.p_ps and t.step < :num
),
steps (name, ps, name_hist, name_back, ps_back, time, step) as
---- 用递归的方法模拟过河的每一步, name: 已过河的人名单 ps:已过河的人的二进制之和,name_hist: 过河名单历史
----- name_back: 返回的人名单历史 ps_back:最近一次返回的人的二进制编号 time:已用总时间 step: 用了几步
---- 因为用了BITAND操作,这几个列已经毫无意义:name, name_back, ps_back 可以考虑去掉。
(
-- step 1
select name,ps, name, '', 0, time, 1
from t_combinations where step = :num -------- 假设第一步总是过去最多的人, 这个也有问题
union all
select
case when mod(s.step,2) = 1 then replace(s.name, c.name) --- s.step 为奇数,意味着返回,从河对岸的名单s.name去除返回人c.name, 此处前提是所有人名不出现互相包含的情况,比如有人叫A,另一人叫AA这样的。
else s.name || c.name ---- 如果s.step 为偶数则意味着过去, 把名单并入到过河名单中
end name,
case when mod(s.step,2) = 1 then s.ps - c.ps --- 类似上面,用的是二进制的和来表示哪些人已过河。返回的扣减,过河的增加
else s.ps + c.ps
end ps,
s.name_hist||','|| c.name, ----- 过河名单历史
case when mod(s.step,2) = 1 then s.name_back || c.name ----- 如果是返回,则增加返回的人名单历史
else s.name_back ------ 如果是过河,则无须修改这个返回名单历史
end name_back,
case when mod(s.step,2) = 1 then c.ps ----- 最近一次返回的人的二进制编码
else 0
end ps_back,
s.time + c.time, s.step + 1
from steps s, t_combinations c, t_accessory a
where s.step < a.totalsteps -----限制总步数不能超过计算值,但有时候过河的人更少反而节省总体的时间, 例子见94楼。建议修改递归出口条件为 s.ps<>power(2, cnt) - 1
-- back
and ( ---------- 如果是返回,则总是单人(c.step = 1)并且是速度靠前的那些人(用c.od 判断)
(mod(s.step,2) = 1 and bitand(s.ps, c.ps) > 0 and c.step = 1 and c.od <= :num)
-- forward
or
(mod(s.step,2) = 0 and bitand(s.ps, c.ps) = 0
and( ----- 如果不是最后一步,或者最后一步且总人数为偶数(这个没什么道理?),则过河人数总是选最多的num人。这个不一定成立,反例见94楼
(a.totalsteps > s.step + 1 or a.totalsteps = s.step + 1 and a.md = 0 ) and c.step = :num
or ----- 如果是最后一步,并且总人数为奇数(这个也没什么道理?), 则剩下的人全部过河
a.totalsteps = s.step + 1 and a.md <> 0 and c.step < case when :num=2 then 3 else :num end
and s.ps + c.ps = power(2, cnt) - 1
)
)
)
)
select * from
(
select steps.*, row_number() over (order by steps.time) od from steps, t_accessory t
where step = t.totalsteps order by time
) where od <= 10;
|
|