林洪涛(林洪涛)11:09:06
今天晨会题目:
有n种经验石,经验值分别为exp[i],每种经验石个数为count[i],现在目标经验值是value,求:各种经验石的个数,保证超出部分最小化。(i= 0到n-1)
本题是由多重背包扩展的题目。参考资料《背包九讲》。
先看0-1背包类型
有N件物品和一个容量为V的背包。第i件物品的费用是c[i],价值是w[i]。求解将哪些物品装入背包可使价值总和最大。
过程ZeroOnePack,表示处理一件01背包中的物品,两个参数cost、weight分别表明这件物品的费用和价值。
伪代码:
procedureZeroOnePack(cost,weight)
forv=V..cost
f[v]=max{f[v],f[v-cost]+weight}
注意这个过程里的处理与前面给出的伪代码有所不同。前面的示例程序写成v=V..0是为了在程序中体现每个状态都按照方程求解了,避免不必要的思维复杂度。而这里既然已经抽象成看作黑箱的过程了,就可以加入优化。费用为cost的物品不会影响状态f[0..cost-1],这是显然的。有了这个过程以后,01背包问题的伪代码就可以这样写:
fori=1..N
ZeroOnePack(c[i],w[i]);
C++代码:
for(i=1;i<=v; i++) f[i] = 0;
for(i= 0; i<num; i++)
for(int j=v; j>=C[i]; j--)
f[j]= max( f[j], f[j – C[i]] +W[j] );
例:若有价值3元、4元、5元、6元、7元硬币各一枚,花最少的硬币付清价值20元物品,求方案见表格(附件)
再回到题目,
先简化原题N= 4种经验石,每种经验石一个Count[i→ 0~N-1] = 1,每一个经验石的价值Exp= [i] = 6、4、10、8,目标经验值Value= 19, 求:各种经验石的个数,保证超出部分最小化求经验石的组合方案
解决方案:同上(记录路径则OK)。
再看原题:
先简化原题N= 4种经验石,每种经验石一个Count[i]= 3、2、1、4,每一个经验石的价值Exp= [i] = 6、4、10、8,目标经验值Value= 19, 求:各种经验石的个数,保证超出部分最小化求经验石的组合方案。
题目转化:N= 10种经验石,每种经验石一个N[i]= 1,每一个经验石的价值Exp= [i] = 6、6、6、4、4、10、8、8、8、8,目标经验值Value= 19, 求:各种经验石的个数,保证超出部分最小化求经验石的组合方案。
解决方案:同上(记录路径则OK)。
-----------------------------------(题目结束)--------------------------------------
今天只是给大家介绍gb_tree的扩展使用,没有涉及复杂的平衡算法讲解。
需求,玩家获取公会列表,要维护一个上万级别的列表,要求实时的插入和查询时间得到最快。具体需求:100000次插入,每次插入一条数据(K- V),1000次查询,每次查询比K小或者大的连续20条数据。最后实现的性能Q×(LgN+ M), N是数据总量(100000),Q是查询次数(1000),M是多少条数据(20)
分析:列表长100000,实时的正常顺序
删除操作:二分找到位置,直接删除,不用维护顺序
查找操作:二分找到位置,读取数据,不用维护顺序
插入操作:二分找到位置,直接插入,需要维护顺序
更新操作:二分找到位置,直接修改,需要维护顺序
拿到需求,费解就在于插入和更新操作,操作结束时,需要实时维护列表顺序,要保证服务端承受住压力,客户端能够快速得到回应,稍有不慎,反应时间将是一个数量级的增长。
关于Erlang的gb_tree用法介绍:
http://www.cnblogs.com/me-sa/archive/2012/06/23/erlang-gb_trees.html
解决上诉问题,可以选择使用gb_tree数据结构来干有关平衡算法:
红黑树平衡算法(网上绝大多数博客都是摘抄《算法导论》的翻译)
http://www.cnblogs.com/Anker/archive/2013/01/30/2882773.html
http://blog.chinaunix.net/uid-26575352-id-3061918.html
站在前人的肩膀看世界,好处是底层已经给我们封装好了复杂的算法逻辑代码:
预备知识:
gb_tree的模型:{Key, Value, Smaller, Bigger}
Smaller是左子树
Bigger是右子树
规律:LeftChildKey > FatherKey > RightChildKey
11
/ \
6 16
/ \ / \
3 8 13 18
/ \ / \ / \ / \
1 4 7 9 12 14 17 19
/\ / \ / \ / \ / \ / \ / \ / \
nil......nil......nil........nil.........nil.........nil....
问题1:给出任意一个Key,找出第一个大于或等于Key的值。
图示:假设我们要查询10
很显然查询路径:11→ 6 → 8 → 9 → nil
当查询到nil时,那么回溯时出现的第一个大于Key的值是我们所需要的值。答案:11
问题2:给出任意一个Key,找出第一个小于或等于Key的值。
图示:假设我们要查询11.5
很显然查询路径:11→ 16 → 13 → 12 → nil
当查询到nil时,那么回溯时出现的第一个小于Key的值是我们所需要的值。答案:11
问题3:给出任意一个Key,找出比key(若相等,则含Key)大的M个数值
图示:假设我们要查询比11.5大的5个数
第一步:跑问题1找到12
第二步:开始回溯遍历中序,使用计数器Count= 5,控制回溯边界
问题4:给出任意一个Key,找出比key(若相等,则含Key)小的M个数值
图示:假设我们要查询比11.5小的5个数
第一步:跑问题2找到11
第二步:开始回溯遍历(中序的反序),使用计数器Count= 5,控制回溯边界
此时第二步操作出现问题,如何实现。。。。?我们要找到
9→ 8 → 7 → 6序列
我的解决方案:建立树的旋转模型
11
/ \
6 16
/ \ / \
3 8 13 18
/ \ / \ / \ / \
1 4 7 9 12 14 17 19
/\ / \ / \ / \ / \ / \ / \ / \
nil......nil......nil........nil.........nil.........nil....
第1步:我们找到11的左孩子
6
/ \
3 8
/ \ / \ =>将6的左右孩子进行一次旋转
1 4 7 9
/\ / \ / \ / \
nil......nil......nil........
6
/ \
8 3
/ \ / \ =>将8、3的左右孩子进行一次旋转
7 9 1 4
/\ / \ / \ / \
nil......nil......nil........
6
/ \
8 3
/ \ / \旋转后的结果,此时规律就出来了,
9 7 4 1
/\ / \ / \ / \
nil......nil......nil........
旋转后的树:
11
/ \
6 16
/ \ / \
8 3 13 18
/ \ / \ / \ / \
9 7 4 1 12 14 17 19
/\ / \ / \ / \ / \ / \ / \ / \
nil......nil......nil........nil.........nil.........nil....
再回到问题:我们需要找的是9→ 8 → 7 → 6这个序列,
旋转过程,是边回溯,边旋转,有点类似平横算法插入操作
荣哥的思路:
采用迭代器代替旋转模型,不过性能不是O(1),也就能看出,每次跑相同的数据,要多花上一点时间
iterator= [子树列表],每次取出一颗子树(树可能是单节点)时,可能需要将子树分解一次(不是完全分解),时间要看子树的高度
理论讲完了,看代码吧。。。。。
昨天接到一个需求,要维护一个上万级别的列表,要求实时的插入和查询时间得到最快,
具体需求:100000次插入,每次插入一条数据(K - V),1000次查询,每次查询比K小的连续20条数据。
拿到需求首先想到gb_tree,但是翻了一下gb_tree的源码,没有发现直接可使用的借口,于是自己模拟一个
接口出来。最后实现的性能 LgN + Q*M , N是数据总量(100000), Q是查询次数(1000), M是多少条数据(20)
erlang代码实现
-module(tree_misc). -export([ make_empty_tree/0, insert/2, delete/2, lookup/2, update/3, lookup_smaller_key/2, lookup_bigger_key/2, lookup_smaller_n/3, lookup_bigger_n/3 %iterator_find_multi_with_key/3 ]). make_empty_tree() -> gb_trees:from_orddict([]). insert({Key, Value}, Tree) -> gb_trees:insert(Key, Value, Tree); insert([{Key, Value} | List], Tree) -> NTree = gb_trees:insert(Key, Value, Tree), insert(List, NTree); insert([], Tree) -> Tree. delete(Key, Tree) -> gb_trees:delete(Key, Tree). lookup(Key, Tree) -> gb_trees:lookup(Key, Tree). update(Key, Value, Tree) -> gb_trees:update(Key, Value, Tree). %-------------------------------------------- %%查找 > 或 =:= Key出现的第一个Key lookup_bigger_key(Key, {_, T} = _Tree) -> lookup_bigger_key1(Key, T). lookup_bigger_key1(Key, {Key1, _Value1, Smaller, _Bigger}) when Key < Key1 -> case lookup_bigger_key1(Key, Smaller) of none -> Key1; Other -> Other end; lookup_bigger_key1(Key, {Key1, _Value1, _Smaller, _Bigger}) when Key =:= Key1 -> Key; lookup_bigger_key1(Key, {Key1, _Value1, _Smaller, Bigger}) when Key > Key1 -> lookup_bigger_key1(Key, Bigger); lookup_bigger_key1(_, nil) -> none. %% 查找比Key大的Num个值,即[{K1, V1}, {K2, V2} ...Num个...] lookup_bigger_n(Num, Key, {_, T} = _Tree) -> lookup_bigger_n1(Num, Key, T). lookup_bigger_n1(Num, Key, {Key1, Value1, Smaller, Bigger}) when Key < Key1 -> case lookup_bigger_n1(Num, Key, Smaller) of none -> %% 左子树为空时,处理掉右子树 traverse_tree_right(Num-1, Bigger, [{Key1, Value1}]); {NNum, List} -> if NNum > 0 -> traverse_tree_right(NNum-1, Bigger, [{Key1, Value1} | List]); true -> {0, List} end end; lookup_bigger_n1(Num, Key, {Key1, Value1, _Smaller, Bigger}) when Key =:= Key1 -> %%相等时把右子树处理掉 traverse_tree_right(Num-1, Bigger, [{Key1, Value1}]); lookup_bigger_n1(Num, Key, {Key1, _Value1, _Smaller, Bigger}) when Key > Key1 -> lookup_bigger_n1(Num, Key, Bigger); lookup_bigger_n1(_, _, nil) -> none. %% 扫描符合条件的右子树 traverse_tree_right(Num, {Key, Value, Smaller, Bigger}, List) -> if Num =< 0 -> {0, List}; true -> {NNum, NList} = traverse_tree_right(Num, Smaller, List), if NNum =< 0 -> {0, NList}; true -> traverse_tree_right(NNum-1, Bigger, [{Key, Value}|NList]) end end; traverse_tree_right(0, _, List) -> {0, List}; traverse_tree_right(Num, nil, List) -> {Num, List}. % ----------------------------------------------------------------------------------- %%查找 < 或 =:= Key出现的第一个Key lookup_smaller_key(Key, {_, T} = _Tree) -> lookup_smaller_key1(Key, T). lookup_smaller_key1(Key, {Key1, _Value1, Smaller, _Bigger}) when Key < Key1 -> lookup_smaller_key1(Key, Smaller); lookup_smaller_key1(Key, {Key1, _Value1, _Smaller, _Bigger}) when Key =:= Key1 -> Key; lookup_smaller_key1(Key, {Key1, Value1, Smaller, Bigger}) when Key > Key1 -> case lookup_smaller_key1(Key, Bigger) of none -> {{Key1, Value1}, Smaller}; Other -> Other end; lookup_smaller_key1(_, nil) -> none. %查找比Key小的Num个值,即[{K1, V1}, {K2, V2} ...Num个...] lookup_smaller_n(Num, Key, {_, T}) -> {_, ResultList} = lookup_smaller_n1(Num, Key, T), ResultList. lookup_smaller_n1(Num, Key, {Key1, _Value1, Smaller, _Bigger}) when Key < Key1 -> lookup_smaller_n1(Num, Key, Smaller); lookup_smaller_n1(Num, Key, {Key1, Value1, Smaller, _Bigger}) when Key =:= Key1 -> %%相等时把左子树处理掉 traverse_tree_left(Num-1, Smaller, [{Key1, Value1}]); lookup_smaller_n1(Num, Key, {Key1, Value1, Smaller, Bigger}) when Key > Key1 -> case lookup_smaller_n1(Num, Key, Bigger) of none -> %% 右子树为空时也处理掉左子树 %% {{Key1, Value1}, Smaller}; traverse_tree_left(Num-1, Smaller, [{Key1, Value1}]); {NNum, List} -> %% 由右子树递归回溯时,则把左子树处理掉 if NNum > 0 -> traverse_tree_left(NNum-1, Smaller, [{Key1, Value1} | List]); true -> {0, List} end end; lookup_smaller_n1(_, _, nil) -> none. %% 扫描符合条件的左子树 traverse_tree_left(Num, {Key, Value, Smaller, Bigger}, List) -> if Num =< 0 -> {0, List}; true -> {NNum, NList} = traverse_tree_left(Num, Bigger, List), if NNum =< 0 -> {0, NList}; true -> traverse_tree_left(NNum-1, Smaller, [{Key, Value}|NList]) end end; traverse_tree_left(0, _, List) -> {0, List}; traverse_tree_left(Num, nil, List) -> {Num, List}.
测试代码
test() -> Tree = tree_misc:make_empty_tree(), F1 = fun() -> lists:foldl(fun(X, Acc) -> [{X, X} | Acc] end, [], lists:seq(1, 10000)) end, {R1, List} = timer:tc(F1), io:format("R1 : ~p ~n", [R1]), F2 = fun() -> lists:foldl(fun({K, V}, OldTree)-> gb_trees:insert(K, V, OldTree) end, Tree, List) end, {R2, NTree} = timer:tc(F2), io:format("R2 : ~p ~n", [R2]), RandNumList = hmisc:rand_n(1000, List), %% io:format("NTree: ~p~n", [NTree]), io:format("Start....~n"), F3 = fun() -> lists:map(fun({Key, _Value}) -> tree_misc:lookup_bigger_n(20, Key + 0.5, NTree) %tree_misc:iterator_find_multi_with_key(Key + 0.5, 20, NTree) end, RandNumList) end, {R3, [Result|_]} = timer:tc(F3), io:format("R3 : ~p, Result: ~p ~n", [R3, Result]).
Result:
Start....
R3 : 5014, Result: {0,
[{5545,5545},
{5544,5544},
{5543,5543},
{5542,5542},
{5541,5541},
{5540,5540},
{5539,5539},
{5538,5538},
{5537,5537},
{5536,5536},
{5535,5535},
{5534,5534},
{5533,5533},
{5532,5532},
{5531,5531},
{5530,5530},
{5529,5529},
{5528,5528},
{5527,5527},
{5526,5526}]}