您的位置:首页 > 其它

在公司做的第一次技术分享

2015-01-13 15:58 489 查看
林洪涛(林洪涛)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}]} 
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: