海盗分金SQL优化杂谈

来源:互联网 发布:软件授权书是怎么回事 编辑:程序博客网 时间:2024/04/28 02:41

海盗分金


经济学上有个“海盗分金”模型,是说5个海盗抢得100枚金币,他们按抽签的顺序依次提方案:首先由1号提出分配方案,然后5人表决,超过半数同意方案才被通过,否则他将被扔入大海喂鲨鱼,依此类推。

假定“每人海盗都是绝顶聪明且很理智”,那么“第一个海盗提出怎样的分配方案才能够使自己的收益最大化?”

推理过程是这样的:

从后向前推,如果1至3号强盗都喂了鲨鱼,只剩4号和5号的话,5号一定投反对票让4号喂鲨鱼,以独吞全部金币。所以,4号惟有支持3号才能保命。

3号知道这一点,就会提出“100,0,0”的分配方案,对4号、5号一-_-!!不拔而将全部金币归为已有,因为他知道4号一无所获但还是会投赞成票,再加上自己一票,他的方案即可通过。

不过,2号推知3号的方案,就会提出“98,0,1,1”的方案,即放弃3号,而给予4号和5号各一枚金币。由于该方案对于4号和5号来说比在3号分配时更为有利,他们将支持他而不希望他出局而由3号来分配。这样,2号将拿走98枚金币。

同样,2号的方案也会被1号所洞悉,1号并将提出(97,0,1,2,0)或(97,0,1,0,2)的方案,即放弃2号,而给3号一枚金币,同时给4号(或5号)2枚金币。由于1号的这一方案对于3号和4号(或5号)来说,相比2号分配时更优,他们将投1号的赞成票,再加上1号自己的票,1号的方案可获通过,97枚金币可轻松落入囊中。这无疑是1号能够获取最大收益的方案了!答案是:1号强盗分给3号1枚金币,分给4号或5号强盗2枚,自己独得97枚。分配方案可写成(97,0,1,2,0)或(97,0,1,0,2)。

--海盗分金【牛人 SQL实现】:
with a as
(select 101 - rownum  n from dual connect by rownum <102),
max_one as
(select max(n) max1 from a),
max_two as
(select /*+leading(p2,p1) use_nl(p1) */ p2.n max2,p1.n max1
 from a p1,a p2
 where p1.n+p2.n=100
 and p1.n=(select max1 from max_one)
 and rownum=1),
max_three as
(select /*+leading(p3,p2,p1) use_nl(p2) use_nl(p1)*/ p3.n max3,p2.n max2,p1.n max1
 from a p1,a p2,a p3,max_two
 where p1.n+p2.n+p3.n=100
 and sign(p2.n-max2)+sign(p1.n-max1)>=0
 and rownum=1),
max_four as
(select /*+leading(p4,p3,p2,p1) use_nl(p3) use_nl(p2) use_nl(p1)*/ p4.n max4,p3.n max3,p2.n max2,p1.n max1
 from a p1,a p2,a p3,a p4,max_three
 where p1.n+p2.n+p3.n+p4.n=100
 and sign(p3.n-max3)+sign(p2.n-max2)+sign(p1.n-max1)>0
 and rownum=1),
five as
(select /*+leading(p5,p4,p3,p2,p1) use_nl(p4) use_nl(p3) use_nl(p2) use_nl(p1)*/ p5.n n5, p4.n n4,p3.n n3,p2.n n2,p1.n n1
 from a p1,a p2,a p3,a p4,a p5,max_four
 where p1.n+p2.n+p3.n+p4.n+p5.n=100
 and sign(p4.n-max4)+sign(p3.n-max3)+sign(p2.n-max2)+sign(p1.n-max1)>=0
 and rownum=1)
select * from five;
--end

虚拟机运行:

sqlplus 中 set timing on;

--select * from max_four;
--Elapsed: 00:00:04.37

--select * from five;
--Elapsed: 00:09:30.92


技术之外:


“海盗分金”其实是一个高度简化和抽象的模型,体现了博弈的思想。在“海盗分金”模型中,任何“分配者”想让自己的方案获得通过的关键是事先考虑清楚“挑战者”的分配方案是什么,并用最小的代价获取最大收益,拉拢“挑战者”分配方案中最不得意的人们。企业中的一把手,在搞内部人控制时,经常是抛开二号人物,而与会计和出纳们打得火热,就是因为公司里的小人物好收买。

1号看起来最有可能喂鲨鱼,但他牢牢地把握住先发优势,结果不但消除了死亡威胁,还收益最大。这不正是全球化过程中先进国家的先发优势吗?而5号,看起来最安全,没有死亡的威胁,甚至还能坐收渔人之利,却因不得不看别人脸色行事而只能分得一小杯羹。

不过,模型任意改变一个假设条件,最终结果都不一样。而现实世界远比模型复杂。

首先,现实中肯定不会是人人都“绝对理性”。回到“海盗分金”的模型中,只要3号、4号或5号中有一个人偏离了绝对聪明的假设,海盗1号无论怎么分都可能会被扔到海里去了。所以,1号首先要考虑的就是他的海盗兄弟们的聪明和理性究竟靠得住靠不住,否则先分者倒霉。

如果某人偏好看同伙被扔进海里喂鲨鱼。果真如此,1号自以为得意的方案岂不成了自掘坟墓!

再就是俗话所说的“人心隔肚皮”。由于信息不对称,谎言和虚假承诺就大有用武之地,而阴谋也会像杂-_-!!般疯长,并借机获益。如果2号对3、4、5号大放烟幕弹,宣称对于1号所提出任何分配方案,他一定会再多加上一个金币给他们。这样,结果又当如何?

通常,现实中人人都有自认的公平标准,因而时常会嘟嚷:“谁动了我的奶酪?”可以料想,一旦1号所提方案和其所想的不符,就会有人大闹……当大家都闹起来的时候,1号能拿着97枚金币毫发无损、镇定自若地走出去吗?最大的可能就是,海盗们会要求修改规则,然后重新分配。想一想二战前的希特勒德国吧!

而假如由一次博弈变成重复博弈呢?比如,大家讲清楚下次再得100枚金币时,先由2号海盗来分……然后是3号……这颇有点像美国总统选举,轮流主政。说白了,其实是民主形式下的分赃制。

最可怕的是其他四人形成一个反1号的大联盟并制定出新规则:四人平分金币,将1号扔进大海……这就是阿Q式的革命理想:高举平均主义的旗帜,将富人扔进死亡深渊……

制度规范行为,理性战胜愚昧!

再回到技术:

:)


--select level lv from dual connect by level < n;
--可查询得到1 .. n-1 的序列
--/*+LEADInG(TABLE)*/
--  将指定的表作为连接次序中的首表.
--/*+USE_nL(TABLE)*/
--  将指定表与嵌套的连接的行源进行连接,并把指定表作为内部表.



/*-------------------------------------------------

oracle 使用leading, use_nl, rownum调优

from: http://www.cnblogs.com/GoDevil/archive/2008/08/29/1279439.html

1、使用leading和use_nl来设置表的查询顺序,来加快查询速度,一般把小表设为第一个表。
/*+LEADInG(TABLE)*/
  将指定的表作为连接次序中的首表.
/*+USE_nL(TABLE)*/
  将指定表与嵌套的连接的行源进行连接,并把指定表作为内部表.

成本计算方法:
设小表100行,大表100000行。

两表均有索引:
如果小表在内,大表在外(驱动表)的话,则扫描次数为:
100000+100000*2 (其中2表示IO次数,一次索引,一次数据)
如果大表在内,小表在外(驱动表)的话,则扫描次数为:
100+100*2.

两表均无索引:
如果小表在内,大表在外的话,则扫描次数为:
100000+100*100000
如果大表在内,小表在外的话,则扫描次数为:
100+100000*100

注意:如果一个表有索引,一个表没有索引,ORACLE会将没有索引的表作驱动表。如果两个表都有索引,则外表作驱动表。如果两个都没索引的话,则也是外表作驱动表。

2、使用index直接匹配索引来查询数据提高查询速度
/*+InDEX(TABLE InDEX_nAME)*/
  表明对表选择索引的扫描方法.
3、当判断某几个表中是否存在某种关系的行时可使用rownum=1来作为条件而加快速度,如果必须有多行才满足条件时,可设置rownum <= n。
例如:获得一个v_count值判断是否大于0
equipment表 几万条,controledpnsnrange 几千条
select
count(sc.pmnum)
into
v_count
from
equipment e,
sal_controledpnsnrange sc
where
(sc.new_min_item_no <= e.itemnum)
and (sc.new_max_item_no >= e.itemnum)
and (sc.new_min_serial_no <= e.serialnum)
and (sc.new_max_serial_no >= e.serialnum)
and e.itemnum = a_item_no;
执行时间大约为3分钟。
代码修改后:
select /*+ leading(e) use_nl(sc) */
count(sc.pmnum)
into
v_count
from
equipment e,
sal_controledpnsnrange sc
where
(sc.new_min_item_no <= e.itemnum)
and (sc.new_max_item_no >= e.itemnum)
and (sc.new_min_serial_no <= e.serialnum)
and (sc.new_max_serial_no >= e.serialnum)
and e.itemnum = a_item_no;
执行时间大约为45秒。
代码再次修改后:
select /*+ leading(e) use_nl(sc) */
count(sc.pmnum)
into
v_count
from
equipment e,
sal_controledpnsnrange sc
where
(sc.new_min_item_no <= e.itemnum)
and (sc.new_max_item_no >= e.itemnum)
and (sc.new_min_serial_no <= e.serialnum)
and (sc.new_max_serial_no >= e.serialnum)
and e.itemnum = a_item_no
and rownum = 1;
执行时间大约为4秒。

---------------------------------------*/



0 0
原创粉丝点击