林洪涛(林洪涛)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
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]).
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}]}