【问题标题】:Recursive C function递归 C 函数
【发布时间】:2016-04-21 13:31:33
【问题描述】:

我需要在 C 中编写一个递归函数,对于某个 n,它会计算不具有三个连续数字 1 的 n 位二进制数的数量。

例如,对于 n=3,所有可能的 3 位数是: 000 001 010 011 100 101 110 111

所以函数返回 7,因为只有一个数字和三个 1。

到目前为止,我什至不知道从哪里开始。我看不到任何可以帮助我的算法。

任何帮助将不胜感激。 谢谢。

【问题讨论】:

  • 嗯...我不确定我们是否应该为您做作业...
  • @Myst 我仍然认为这是一个好问题(即使它可能是家庭作业)。这里的算法部分比C实现更有趣。
  • 您是否考虑过为什么作业建议使用递归解决方案?也许这是您应该设计的算法的提示。
  • @AlexLop。我不确定一个问题是否应该被投票,因为一位老师给出了一个有趣的作业(而不是一个无聊的作业)。
  • @Lundin 嘿,您可能(在某种程度上)对 C 和命令式语言是正确的,但函数式编程是建立在递归之上的。即使在命令式语言中,也有递归数据结构可以自然地通过递归处理。

标签: c algorithm recursion


【解决方案1】:

让我们构造一个从左到右不连续三个数字的数字。

它必须以最多两个 1 开头,因此它以没有 1、单个 1 或两个 1 开头。换句话说,我们的数字以 0、10 或 110 开头。在所有这些情况下,数字其余部分的唯一限制是它不包含任何三个连续的数字,因此这允许我们应用相同的递归函数:

#include <stdint.h>
#include <stddef.h>

uint64_t nothreeconsecutive(int n) {
    if (n <= 2) return 1 << n;
    return nothreeconsecutive(n-1) + nothreeconsecutive(n-2) + nothreeconsecutive(n-3);
}

【讨论】:

    【解决方案2】:

    让我们首先观察包含三个相邻设置位的n位二进制数的数量是pow(2, n)减去n位数字的数量 em> 三个相邻的设置位。

    此外,设置三个相邻位的长度为n的二进制数的数量是长度为n-1的此类数字的两倍(最后一位的值都导致设置了三个相邻位的数字),加上长度为 n-1 且初始两位设置的二进制数的数量(对于设置新位的情况),减去长度为 n-1 的那些数字以及之前已经设置了三个相邻位的两个初始位设置(因为它们已经算过一次)。

    因此,在稍微更改返回类型以计算更长的序列后,问题的解决方案可能如下所示:

    #include <stdint.h>
    #include <stddef.h>
    
    uint64_t twoadjstart(int a)
    {
      /*returns the number of binary numbers with a bits that have no more than 2 adjacent initial set bits.*/
      if (a < 2)
        return 0;
      if (a == 2)
        return 1;
      return UINT64_C(1) << (a - 3);
    }
    
    uint64_t recuadjthree(int a)
    {
    /*returns the number of binary numbers with a bits that do contain three adjacent set bits.*/
      if (a < 3)
        return 0;
      uint64_t retval = 2*recuadjthree(a-1);
      retval += twoadjstart(a-1);
      retval -= recuadjthree(a-4);
      return retval;
    }
    
    uint64_t recunothree(int a)
    {
      /*returns the number of binary numbers with a bits that do not contain three adjacent set bits.*/
      uint64_t combi = UINT64_C(1) << a;
      return combi - recuadjthree(a);
    }
    

    【讨论】:

    • 计算那些没有连续三​​个的要容易得多。它们要么以 0、10 或 110 开头,所以 f(n) = f(n-1) + f(n-2) + f(n-3)
    • @ffao:这是一种非常聪明的表达方式。不幸的是,它也大大变慢了(使用gcc -O3 编译,n==32 的系数约为 4500),这可能是由于更分支的递归。不过不错。
    • 好吧,首先讨论这个问题的效率没有多大意义,因为有比这两种解决方案更有效的方法(log n * M,其中 M用于乘以大整数)。所以我为了清楚起见而拍摄......
    • @ffao:是的,在记忆和自下而上而不是自上而下之后,差异缩小到 1:2 左右......有利于您的解决方案。发布答案,获得支持?
    【解决方案3】:

    将数字视为位串,因此问题在于如何找到子串111。位的好处是您可以对其进行位移,并尝试将低三位屏蔽掉,并比较它是否为111。试试这个:

    bool valid(int n) {
        while (n) {
            if ((n & 0x7) == 7)
                 return false;
            n = n >> 1;
        }
        return true;
    }
    

    其次,考虑如何获取所有 n 位数字。您可以很容易地注意到这些数字是从全 0 到全 1,因此您可以循环遍历所有这些数字,并在第一步中使用函数检查每个数字。

    现在你有了一个迭代解决方案,考虑一下如何实现与递归相同的东西。

    【讨论】:

    • 不是一个完全正确的解决方案...例如:n=7 你也有 1110111, 1110000, 0000111, 0001110, 0011100, 0111000
    • 如果是有符号数,移位可能会保持符号位设置(或不设置)。如果是这种情况,您可能会陷入无限循环。
    • @sabbahillel 如果结果为负,则结果由实现定义,valid 函数应接受范围为 0 到 INT_MAX 的输入。或者将参数设为无符号类型。
    • 答案不是pow(2.0, (double)n) - (n-2 &gt; 0 ? n-2 : 0)吗?
    • @Myst 太棒了!这就是数学方法。为什么要发表评论,做出回答! :)
    【解决方案4】:

    以下是错误(部分)解决方案(由@EOF 指出)。

    我留下了错误的解决方案,因此 cmets 将变得有意义,并让寻找正确解决方案的人从我的错误中吸取教训。

    错误:此解决方案仅与最后 3 位设置为 1n 位数有关,并忽略了 1 位在中间的可能性的数量。

    也许:完成这个解决方案需要我们从n 的解决方案中一次又一次地减去n-1 的解决方案(直到n == 0)。

    #include <stdio.h>
    
    #define pow2(n) ipow(2,n);
    
    int solution(int n)
    {
       if(n <= 2)
         return pow2(n);
       return pow2(n) - pow2(n-3);
       // The return statement, after the `if`, should probably have been:
       return pow2(n) - pow2(n-3) - solution(n-1);
    }
    
    int main()
    {
       int n = 7;
       printf("The solution for n = %d is %d", n, solution(n));
    }
    

    我的逻辑:

    如果有n位,那么可能的数字(包括0)的数量是2^n(2的n次方)。

    在一个 n 位数中,有 n-2 种方法来拟合三个连续的设置位(0b1110b11100b11100)...这是 IF n > 2。

    正如 cmets 中所指出的,这些数字中的每一个都具有非 0 位的可能性(即 0b111010b11100)。

    所以:

    • 对于 n = 3,我们有 0 个剩余位(1 个可能的数字 == 2^0)

    • 对于 n = 4,我们有 1 个剩余位(2 个可能的数字 == 2^1)

    • 对于 n = 5,我们有 2 个剩余位,或者分开或一起(4 种可能的解决方案 == 2^2)。

    等等……

    这些可能性是剩余位 (n-3) 的 2 次方。

    ...我怀疑位的位置很重要,我们知道我们对所有可能的数字都有 2^n 排列,如果三个位相同,那么我们有 2^(n-3) 排列数字。

    感谢 KlasLindbäck 和 EOF 的指点。

    【讨论】:

    • 给定 n=8(一个 8 位数字),您的算法给出 250。当然这不可能是正确的,因为我们有 0xF0、0xF1、0xF2...
    • 您缺少包含111 的所有数字,其中并非所有其他位都是0
    • 谢谢!这就是我错过的!
    • @KlasLindbäck - 我想知道使用 2**(n-2) 是否可以解决这个问题?
    • 不应该是(n-2)*2**(n-2),因为三元组可以在n-2 位置开始,而其他位可以有2**(n-2) 状态。
    猜你喜欢
    • 1970-01-01
    • 2016-02-13
    • 2021-11-24
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2017-11-19
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多