📜  Calkin-Wilf序列中的第n个有理数(1)

📅  最后修改于: 2023-12-03 14:59:53.028000             🧑  作者: Mango

Calkin-Wilf序列中的第n个有理数介绍

Calkin-Wilf序列是一组无限有理数构成的序列,其中每个有理数都可以唯一地表示为正整数对$(a,b)$的形式,即$\dfrac{a}{b}$,且满足以下规则:

  1. 第一个有理数为$\dfrac{1}{1}$;
  2. 对于序列中的任意一项$\dfrac{a}{b}$,若其左子节点为$\dfrac{a}{a+b}$,右子节点为$\dfrac{a+b}{b}$;
  3. 序列中的每个有理数都不相同。

这个序列的前若干项为:

$$ 1, \frac{1}{2}, \frac{2}{1}, \frac{1}{3}, \frac{3}{2}, \frac{2}{3}, \frac{3}{1}, \frac{1}{4}, \frac{4}{3}, \frac{3}{5}, \frac{5}{2}, \frac{2}{5}, \frac{5}{3}, \frac{3}{4}, \frac{4}{1}, \dots $$

其中,$\dfrac{1}{2}$是第二项,$\dfrac{2}{1}$是第三项,$\dfrac{1}{3}$是第四项,以此类推。

现在的问题是,如何快速地求出Calkin-Wilf序列中的第n个有理数?本文将为大家介绍两种方法。

方法一:递归

Calkin-Wilf序列的构建是通过对每个有理数递归地构建它的左子节点和右子节点来实现的。因此,我们可以使用递归的方式求出第n个有理数:

def calkin_wilf(n):
    if n == 1:
        return (1, 1)
    # 计算当前节点的左子节点
    if (n - 2) % 2 == 0:
        a, b = calkin_wilf((n - 2) // 2 + 1)
        return (a, a + b)
    # 计算当前节点的右子节点
    else:
        a, b = calkin_wilf((n - 3) // 2 + 1)
        return (a + b, b)

上面的代码中,如果n等于1,则返回$\dfrac{1}{1}$;否则,使用公式计算当前节点的左子节点或右子节点,直到递归到第n个节点为止。

方法二:迭代

递归虽然简单,但效率比较低。因此,我们可以使用迭代的方式,通过循环计算Calkin-Wilf序列中的每个有理数,直到第n个有理数为止:

from fractions import Fraction

def calkin_wilf(n):
    stack = [(1, 1)]
    while n > 1:
        a, b = stack.pop()
        left = (a, a + b)
        right = (a + b, b)
        # 将左子节点和右子节点按照分母大小的顺序入栈
        if left[1] < right[1]:
            stack.append(right)
            stack.append(left)
        else:
            stack.append(left)
            stack.append(right)
        n -= 1
    return Fraction(*stack.pop())

上面的代码使用了栈来保存每个有理数的左子节点和右子节点,按照分母大小的顺序入栈(小的在上,大的在下),以便下一次计算栈顶元素的左子节点和右子节点。当循环n次之后,栈顶元素即为第n个有理数。

结论

以上两种方法都可以求出Calkin-Wilf序列中的第n个有理数,但迭代的方式效率更高。如果需要求出Calkin-Wilf序列中的前m个有理数,也可以使用这种方式,在循环中一次次将有理数入栈,最后转换成列表或元组输出。

代码片段为以下代码:

def calkin_wilf(n):
    stack = [(1, 1)]
    while n > 1:
        a, b = stack.pop()
        left = (a, a + b)
        right = (a + b, b)
        # 将左子节点和右子节点按照分母大小的顺序入栈
        if left[1] < right[1]:
            stack.append(right)
            stack.append(left)
        else:
            stack.append(left)
            stack.append(right)
        n -= 1
    return Fraction(*stack.pop())