C++ 关于 recursion 的一个小问题 - V2EX
V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
请不要在回答技术问题时复制粘贴 AI 生成的内容
AkideLiu
V2EX    程序员

C++ 关于 recursion 的一个小问题

  •  
  •   AkideLiu 2021-05-26 16:07:30 +08:00 2493 次点击
    这是一个创建于 1603 天前的主题,其中的信息可能已经有所发展或是发生改变。

    题目:

    Consider the following recursive function:

     int recur(int n) { if (n < 3) return 1; return recur(n-1) * recur(n-2) * recur(n-3); } 

    if memoisation is applied and recur(n-1) is calculated and stored before calculating recur(n-2) and recur(n-3), for the call recur(6), how many calls will be made to recur()?

    我的解法这样的:

    int count_x = 0; unordered_map<int,int> map_x; int recur(int n) { count_x++; if (n < 3) return 1; // cout << n << endl; if (map_x.find(n) != map_x.end()) { return map_x[n]; } map_x[n] = recur(n-1) * recur(n-2) * recur(n-3); return map_x[n]; } TEST(test, recur) { cout << recur(6) << endl; cout << count_x << endl; } 

    输出结果是:

    1 13 

    现在明确题目的答案不是 13,我这么验证哪里存在问题吗

    22 条回复    2021-05-27 19:04:57 +08:00
    wutiantong
        1
    wutiantong  
       2021-05-26 16:24:19 +08:00   1
    答案是 7 么(包括 recur(6)本身)
    AkideLiu
        2
    AkideLiu  
    OP
       2021-05-26 16:28:00 +08:00
    @wutiantong 是的!请问为什么呢?
    wutiantong
        3
    wutiantong  
       2021-05-26 16:32:17 +08:00
    @AkideLiu 你那个解法的代码里 count_x 的计算没尊重 memoisation 呀
    jorneyr
        4
    jorneyr  
       2021-05-26 16:37:35 +08:00
    map_x[0], map_x[1], map_x[2] 先放到 map 里
    hitmanx
        5
    hitmanx  
       2021-05-26 16:50:13 +08:00
    “if memoisation is applied and recur(n-1) is calculated and stored before calculating recur(n-2) and recur(n-3)”

    没太搞懂,如果答案是 7,是不是 recur(0) - recur(6)各计算一次?这样的话得按照从小到大进行计算与储存,即 recur(n-3) => recur(n-2) => recur(n-1)。
    但是题目里写的是先储存 recur(n-1),那岂不是 recur(n-3)和 recur(n-2)的结果会被重复计算?这样答案为何还是 7 ?
        6
    AkideLiu  
    OP
       2021-05-26 16:52:21 +08:00
    @wutiantong count_x 在最上面啊,每次 recur function 被 call 都会+1,这个计算方式不适用于 memoisation 吗?
    AkideLiu
    jorneyr
        7
    jorneyr  
       2021-05-26 16:52:37 +08:00   1
    答案是 7 的话,执行前明显先把 3 的解也存储起来了:
    6 > 5, 4, 3
    5 > 4, 3, 2
    4 > 3, 2, 1
    3 直接查,为 1 次
    4 为 3, 2, 1: 都是直接查,为 3 次
    5 为 4, 3, 2: 因为上面 4 已经存储,也是直接查, 为 3 次

    当然,程序还可以优化为先找相对小的递归的解,这样大的问题就能从小的问题中直接查询答案:
    map_x[n] = recur(n-3) * recur(n-1) * recur(n-1);
    wutiantong
        8
    wutiantong  
       2021-05-26 16:59:40 +08:00   1
    @AkideLiu memoisation 的意义不就是减少原始调用么?所以在你的代码里,当从 map 中返回值时(相当于 memoisation ),就不应该再算作是调用次数了。
    wutiantong
        9
    wutiantong  
       2021-05-26 17:02:39 +08:00
    @hitmanx 那句话是在说 recur(n-1) * recur(n-2) * recur(n-3) 这个表达式中 recur(n-1) 被确保先于后两者求值。在 C++中,同个表达式内的求值顺序通常是不确定的。
    AkideLiu
        10
    AkideLiu  
    OP
       2021-05-26 17:03:06 +08:00
    @wutiantong @jorneyr 太感谢了!恍然大悟!

    这样子输出是 7 !!!

    ```c++

    int count_x = 0;

    unordered_map<int,int> map_x;

    int recur(int n) {
    // cout << n << endl;
    if (map_x.find(n) != map_x.end()) {
    return map_x[n];
    }

    count_x++;
    if (n < 3){
    map_x[n] = 1;
    return 1;

    }


    map_x[n] = recur(n-1) * recur(n-2) * recur(n-3);
    return map_x[n];
    }


    TEST(test, recur) {

    cout << recur(6) << endl;
    cout << count_x << endl;
    }

    ```
    monkeyNik
        11
    monkeyNik  
       2021-05-26 17:10:27 +08:00
    对本次调用你查表,但你没在内部递归调用前对每个子调用查表,导致你多了一些函数调用。
    至于次数,6 、5 、4 、3 、2 、1 、0,每一个都算一遍并缓存,闭着眼睛也是 7 次。
    lesismal
        12
    lesismal  
       2021-05-26 17:25:52 +08:00
    简单直接的判断方法:
    3 为临界值,n=3 时最多向下 n-1/n-2/n-3,即 recur 最小的参数 n 为 0 ;
    每个 n 对应的值都进行记忆的前提下,每个 n 只需要调用一次 recur ;
    首次调用 recur(6),则整个过程需要对 0-6 挨个计算、记忆,所以是 7 次(不管小于 3 的参数是否记忆,当 n=3/4/5 时 n 也被缓存过、不会被重复调用,所以小于 3 的也不会被重复调用)。
    可以类推首次调用大于等于 3 和小于 3 的次数
    jorneyr
        13
    jorneyr  
       2021-05-26 17:45:44 +08:00
    只要进入 recur 就算一次递归调用吧,不管是查表还是继续递归计算。
    也就是 recur 被调用的次数。
    AkideLiu
        14
    AkideLiu  
    OP
       2021-05-26 17:56:09 +08:00 via iPhone
    @jorneyr 不得不承认,题目确实不严谨。如果结果是 7 的话问题可以问题成 how many times of recur function actually computed 。我的理解是 return memo 就不算是 computed, 而是 cache,但是依然会有 function be called 。
    FACEB00K
        15
    FACEB00K  
       2021-05-26 18:34:37 +08:00   1
    换种写法
    ```cpp
    int count_x = 0;

    unordered_map<int,int> map_x;

    int recur(int n) {
    count_x++;

    if (map_x.find(n) != map_x.end()) {
    return map_x[n];
    }

    if (n < 3) {
    map_x[n] = 1;
    return map_x[n];
    }

    int next_1 = (map_x.find(n-1) == map_x.end()) ? recur(n-1) : map_x[n-1];
    int next_2 = (map_x.find(n-2) == map_x.end()) ? recur(n-2) : map_x[n-2];
    int next_3 = (map_x.find(n-3) == map_x.end()) ? recur(n-3) : map_x[n-3];

    map_x[n] = next_1 * next_2 * next_3;
    return map_x[n];
    }
    ```
    LigeLaige
        16
    LigeLaige  
       2021-05-26 20:02:37 +08:00
    顺着 lz 思路,修改如下
    ```cpp
    int recur(int n) {
    if (map_x.find(n) != map_x.end()) {
    return map_x[n];
    }
    count_x++;
    if (n < 3) {
    map_x[n] = 1;
    } else {
    map_x[n] = recur(n-1) * recur(n-2) * recur(n-3);
    }
    return map_x[n];
    }
    ```
    LigeLaige
        17
    LigeLaige  
       2021-05-26 20:03:27 +08:00
    <code>
    int recur(int n) {
    if (map_x.find(n) != map_x.end()) {
    return map_x[n];
    }
    count_x++;
    if (n < 3) {
    map_x[n] = 1;
    } else {
    map_x[n] = recur(n-1) * recur(n-2) * recur(n-3);
    }
    return map_x[n];
    }
    </code>
    hitmanx
        18
    hitmanx  
       2021-05-26 20:12:45 +08:00
    @wutiantong 好吧,谢谢解释。原来是这个意思。
    不过,如果是指这一行内的求值顺序的话,题目为啥要特意强调一下?除非这个顺序对于这个题目的答案(total # of recur() invoked)会有影响,但是基于 memoisation 的话两者应该一样?还是我忽略了啥


    // 1)
    auto n_3 = recur(n-3);
    auto n_2 = recur(n-2);
    auto n_1 = recur(n-1);
    return n_3 + n_2 + n1;

    2)
    auto n_1 = recur(n-1);
    auto n_2 = recur(n-2);
    auto n_3 = recur(n-3);
    return n_1 + n_2 + n3;
    hitmanx
        19
    hitmanx  
       2021-05-26 20:25:27 +08:00
    @AkideLiu 这个取决于你的 cache lookup op 是在 caller side 还是 callee side~

    像 15 楼一样,放在 caller side 的话,就会实质上而不是概念上减少 recur 调用的数量。
    no1xsyzy
        20
    no1xsyzy  
       2021-05-27 09:55:19 +08:00
    @AkideLiu @hitmanx cache lookup 也可能是在 callee wrapper
    比如 Python 可以直接拿 functools.lru_cache() 修饰符,SICP 3.5 也随便地写了一个 memo-proc
    这提倡了一种正交性。
    wutiantong
        21
    wutiantong  
       2021-05-27 10:25:18 +08:00
    @hitmanx 如果有并行求值的情况呢?
    bibitiger
        22
    bibitiger  
       2021-05-27 19:04:57 +08:00
    题目本身不严谨,我觉得应该说明是最少调用次数。
    如果是最少调用次数的话,那应该在 caller 的时候对 recur(n-1),recur(n-2),recur(n-3)也进行查表,这样就不会进入 recur()。
    而要得出 f(n-1),必然会得到 f(n-2),f(n-3)...f(n-n), 所以 f(n)对于 f()的调用必然等于 n+1 。

    int count_x = 0;

    unordered_map<int,int> map_x;

    int recur(int n) {
    count_x++;

    if (map_x.find(n) != map_x.end()) {
    return map_x[n];
    }

    if (n < 3){
    map_x[n] = 1;
    return 1;
    }


    int temp_n1 = map_x.find(n-1) == map_x.end()?recur(n-1) :map_x[n-1];
    int temp_n2 = map_x.find(n-2) == map_x.end()?recur(n-2) :map_x[n-2];
    int temp_n3 = map_x.find(n-3) == map_x.end()?recur(n-3) :map_x[n-3];

    map_x[n] = temp_n1*temp_n2*temp_n3;

    return map_x[n];
    }
    关于     帮助文档     自助推广系统     博客     API     FAQ     Solana     944 人在线   最高记录 6679       Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 24ms UTC 19:20 PVG 03:20 LAX 12:20 JFK 15:20
    Do have faith in what you're doing.
    ubao msn snddm index pchome yahoo rakuten mypaper meadowduck bidyahoo youbao zxmzxm asda bnvcg cvbfg dfscv mmhjk xxddc yybgb zznbn ccubao uaitu acv GXCV ET GDG YH FG BCVB FJFH CBRE CBC GDG ET54 WRWR RWER WREW WRWER RWER SDG EW SF DSFSF fbbs ubao fhd dfg ewr dg df ewwr ewwr et ruyut utut dfg fgd gdfgt etg dfgt dfgd ert4 gd fgg wr 235 wer3 we vsdf sdf gdf ert xcv sdf rwer hfd dfg cvb rwf afb dfh jgh bmn lgh rty gfds cxv xcv xcs vdas fdf fgd cv sdf tert sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf shasha9178 shasha9178 shasha9178 shasha9178 shasha9178 liflif2 liflif2 liflif2 liflif2 liflif2 liblib3 liblib3 liblib3 liblib3 liblib3 zhazha444 zhazha444 zhazha444 zhazha444 zhazha444 dende5 dende denden denden2 denden21 fenfen9 fenf619 fen619 fenfe9 fe619 sdf sdf sdf sdf sdf zhazh90 zhazh0 zhaa50 zha90 zh590 zho zhoz zhozh zhozho zhozho2 lislis lls95 lili95 lils5 liss9 sdf0ty987 sdft876 sdft9876 sdf09876 sd0t9876 sdf0ty98 sdf0976 sdf0ty986 sdf0ty96 sdf0t76 sdf0876 df0ty98 sf0t876 sd0ty76 sdy76 sdf76 sdf0t76 sdf0ty9 sdf0ty98 sdf0ty987 sdf0ty98 sdf6676 sdf876 sd876 sd876 sdf6 sdf6 sdf9876 sdf0t sdf06 sdf0ty9776 sdf0ty9776 sdf0ty76 sdf8876 sdf0t sd6 sdf06 s688876 sd688 sdf86