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

林洪涛(林洪涛)11:09:06

今天晨会题目:

n种经验石,经验值分别为exp[i],每种经验石个数为count[i],现在目标经验值是value,求:各种经验石的个数,保证超出部分最小化。(i= 0n-1)

本题是由多重背包扩展的题目。参考资料《背包九讲》。

先看0-1背包类型

N件物品和一个容量为V的背包。第i件物品的费用是c[i],价值是w[i]。求解将哪些物品装入背包可使价值总和最大。

过程ZeroOnePack,表示处理一件01背包中的物品,两个参数costweight分别表明这件物品的费用和价值。

伪代码:

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] = 64108,目标经验值Value= 19, 求:各种经验石的个数,保证超出部分最小化求经验石的组合方案

解决方案:同上(记录路径则OK)。

再看原题:

先简化原题N= 4种经验石,每种经验石一个Count[i]= 3214,每一个经验石的价值Exp= [i] = 64108,目标经验值Value= 19, 求:各种经验石的个数,保证超出部分最小化求经验石的组合方案。

题目转化:N= 10种经验石,每种经验石一个N[i]= 1,每一个经验石的价值Exp= [i] = 66644108888,目标经验值Value= 19, 求:各种经验石的个数,保证超出部分最小化求经验石的组合方案。

解决方案:同上(记录路径则OK)。

-----------------------------------(题目结束)--------------------------------------

今天只是给大家介绍gb_tree的扩展使用,没有涉及复杂的平衡算法讲解。

需求,玩家获取公会列表,要维护一个上万级别的列表,要求实时的插入和查询时间得到最快。具体需求:100000次插入,每次插入一条数据(K- V),1000次查询,每次查询比K小或者大的连续20条数据。最后实现的性能LgN+ M, N是数据总量(100000,Q是查询次数(1000,M是多少条数据(20

分析:列表长100000,实时的正常顺序

删除操作:二分找到位置,直接删除,不用维护顺序

查找操作:二分找到位置,读取数据,不用维护顺序

插入操作:二分找到位置,直接插入,需要维护顺序

更新操作:二分找到位置,直接修改,需要维护顺序

拿到需求,费解就在于插入和更新操作,操作结束时,需要实时维护列表顺序,要保证服务端承受住压力,客户端能够快速得到回应,稍有不慎,反应时间将是一个数量级的增长。

关于Erlanggb_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

/ \ / \ =>83的左右孩子进行一次旋转

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这个序列,

旋转过程,是边回溯,边旋转,有点类似平横算法插入操作


荣哥的思路:

采用迭代器代替旋转模型,不过性能不是O1,也就能看出,每次跑相同的数据,要多花上一点时间

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}]} 

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值