在Stern-Brocot树中找到分数的位置


11

所述的Stern-Brocot树是级分的二进制树,其中每个级分是通过将两个级分在上面的水平相邻它的分子和分母获取。

它以“端点分数” 开头0/11/0作为“端点分数”生成,然后通过将这些分数的分子和分母加在一起,在每个连续的分数对之间放置一个分数进行迭代,如下所示:

0.  0/1                                                             1/0
1.  0/1                             1/1                             1/0
2.  0/1             1/2             1/1             2/1             1/0
3.  0/1     1/3     1/2     2/3     1/1     3/2     2/1     3/1     1/0
4.  0/1 1/4 1/3 2/5 1/2 3/5 2/3 3/4 1/1 4/3 3/2 5/3 2/1 5/2 3/1 4/1 1/0

在船尾-Brocot树(的每次迭代n次迭代),有2^n + 1该序列中的元素,这是我们可以从归于一小部分0/2^n2^n/2^n。每次新迭代都只是在每对连续分数之间插入一个分数“ halfway”。

这使得Stern-Brocot树在正有理数与0到1之间的二进制分数之间是一对一的映射,从而也证明了这两个集合具有相同的基数。

您的任务是编写一个程序或函数,给定最低有理数的分子和分母,确定与该分数在Stern-Brocot树中的位置相对应的二进制分数。

输入和输出示例如下:

2/3 -> 3/8   (4th number in iteration 3)
4/7 -> 9/32  (between 1/2 and 3/5 in the chart above)
1/1 -> 1/2   (middle number in the first iteration)

您不需要支持的输入,但包括在内以供参考:

0/1 -> 0/1   (0/1 is considered the left number)
1/0 -> 1/1   (1/0 is considered the rightmost number)

任何语言中最短的程序即可实现这一目标。


有输入/输出要求吗?例如,一个功能是否足以满足您的参考解决方案要求,还是需要作为一个独立程序?分数输出格式重要吗?
达伦·斯通

一个功能就足够了。我将在问题描述中更清楚地说明这一点。
Joe Z.

我想起来已经太晚了。明天我可能会尝试澄清它。
Joe Z.

2
好的,我想您要想到的双射是为树中的每个深度分配一个常数分母2 ^(depth + 1)并从左侧开始分配分子1、3、5、7...。
彼得·泰勒

1
其构建的另一种方法是,以第一数目的树的广度优先的顺序从1开始的节点(即1/1 => 11/2 => 22/1 => 31/3 => 4,等等)。如果为一个节点生成的数字为n,则2^lg n(二进制对数)是设置的最高位n,而所需的二进制分数为(2*(n - 2^lg n) + 1) / 2^(lg n + 1)。(任何尝试在具有最高设置位的指令集中尝试使用汇编程序解决方案的人都可能会想使用此方法)。
彼得·泰勒

Answers:


1

GolfScript(49 48 46个字符)

{0\@{}{@2*2$2$>!+@@{{\}3$)*}:j~1$-j}/\)\,?}:f;

要么

{0:x;\{}{.2$<!2x*+:x){\}*1$-{\}x)*}/x@)@,?}:g;

两者都是在堆栈上使用分子和分母,而在堆栈上保留分子和分母的函数。在线演示

核心思想在“ 具体数学”第4.5节(我的版本中为p122)中以伪代码表达:

while m != n do
    if m < n then (output(L); n := n - m)
             else (output(R); m := m - n)

如果将Ls和Rs的字符串解释为L = 0和R = 1的二进制值,则该值的两倍加1是分子,而分母则长1位。

作为Golfscripters的兴趣点,这是我发现发挥作用的罕见情况之一。(好吧,我仅将其用作循环计数器,但这总比没有好。)


1

Mathematica,130114111个字符

f=#~g~0&;0~g~q_=q;p_~g~q_:=g[#,(Sign[p-#]+q)/2]&@FromContinuedFraction[ContinuedFraction@p/.{x___,n_}:>{x,n-1}]

例:

f[2/3]

3/8

f[4/7]

9/32

f[1]

1/2


1

红宝石,132 125

@JoeZ摩擦并打高尔夫球的参考解决方案。

def t(n,d)u=k=0;v,j,f,g,b=[1,]*5;c=2
while(z=(f*d).<=>(g*n))!=0;z>0?(j,k=f,g):(u,v=f,g);b=b*2-z;f,g=u+j,v+k;c*=2;end
[b,c]end

用法示例:

>> t(2,3)
=> [3, 8]
>> t(4,7)
=> [9, 32]
>> t(1,1)
=> [1, 2]

1

Ruby(69个字符) CoffeeScript(59个字符)

此函数以分子和分母为参数,并在双射后返回包含分子和分母的数组。

g=(a,b,x=0,y=1)->c=a>=b;a&&g(a-b*c,b-a*!c,2*x+c,2*y)||[x,y]

在线演示

它使用与上面的GolfScript解决方案相同的方法,但是更具可读性,因为我可以使用4个变量,而不必担心装箱和拆箱成数组。我选择CoffeeScript是因为它没有在变量前加上前缀$(在PHP上保存了20个字符),具有简短的函数定义语法,该语法允许使用默认参数值(因此无需包装f(a,b,x,y)函数g(a,b) = f(a,b,0,1)),并且让我在布尔值中使用布尔值具有有用值的表达式。对于那些不知道的人,CoffeeScript没有标准的C风格的三元运算符(C?P:Q),但是我可以在C&&P||Q这里替换,因为它P永远不会虚假。

可以说是更优雅,但更短的是,替代方案是用除法和取模来代替重复的减法:

f=(a,b,x=0,y=1,p=0)->a&&f(b%a,a,(x+p<<b/a)-p,y<<b/a,1-p)||[x+p,y]

(65个字符;在线演示)。用这种方式编写它可以揭示与Euclid算法的关系。


您不需要括号,a<b这样可以节省一个字符。内联c给出另外两个。您f=->a,b,x=0,y=1{...}甚至可以考虑使用语法来简化定义。
霍华德

@Howard,我不知道您使用的是哪个版本的Ruby,但是在IDEOne上,如果我尝试删除那些括号或使用该函数语法,则会收到语法错误。
彼得·泰勒

之后请尝试c=a<b ?额外的空间b。否则,问号将被视为的一部分b
霍华德

0

Python-531

Python中的非解决方案,可以用作最后的参考解决方案:

def sbtree(n, d): 
    ufrac = [0, 1]
    lfrac = [1, 0]
    frac = [1, 1]
    bfrac = [1, 2]
    while(frac[0] * d != frac[1] * n): 
        if(frac[0] * d > frac[1] * n): 
            # push it towards lfrac
            lfrac[0] = frac[0]
            lfrac[1] = frac[1]
            bfrac[0] = bfrac[0] * 2 - 1 
        elif(frac[0] * d < frac[1] * n): 
            # push it towards ufrac
            ufrac[0] = frac[0]
            ufrac[1] = frac[1]
            bfrac[0] = bfrac[0] * 2 + 1 
        frac[0] = ufrac[0] + lfrac[0]
        frac[1] = ufrac[1] + lfrac[1]
        bfrac[1] *= 2
    return bfrac

它利用两个事实的中位数将始终在这两个分数的值之间的事实,在分数之间进行二进制搜索。


0

GolfScript,54个字符

'/'/n*~][2,.-1%]{[{.~3$~@+@@+[\]\}*].2$?0<}do.@?'/'@,(

输入必须以任务中指定的形式在STDIN上给出。您可以在线尝试代码。

> 4/7
9/32

> 9/7
35/64

> 5/1
31/32

0

Mathematica 138

不像alephalpha的程序那样精简,但这是迄今为止我所能做到的最好的方法。

q_~r~k_:=Nest[#+Sign@k/(2Denominator@# )&,q,Abs@k]  
g@d_:=
Module[{l=ContinuedFraction@d,p=-1},
l[[-1]]-=1;
(p=-p;# p)&/@l]
h[q_]:=Fold[r,1/2,g@q]

测试中

h[2/3]
h[4/7]
h[1]

3/8
9/32
1/2


0

JavaScript 186

f=(p1,q1,p2,q2)=>{if(p1*q2+1==p2*q1){return{p:p1+p2,q:q1+q2}}let p,q,pl=0,ql=1,ph=1,qh=0;for(;;){p=pl+ph;q=ql+qh;if(p*q1<=q*p1){pl=p;ql=q}else if(p2*q<=q2*p){ph=p;qh=q}else return{p,q}}}

可能更少,但是我喜欢可读的高尔夫


0

Haskell,125个字节

n((a,b):(c,d):r)=(a,b):(a+c,b+d):n((c,d):r)
n a=a
z=zip[0..]
t x=[(j,2^i)|(i,r)<-z$iterate n[(0,1),(1,0)],(j,y)<-z r,x==y]!!0

在线尝试!

输入和输出成对出现(n,d)

简要说明:

n通过查看每对分数并将新的分数插入第一个和递归之间(它将第二个分数放在此处)来构造上一个分数的下一行。基本情况非​​常简单,因为它基本上只是身份功能。该t函数根据初始状态仅使用两个边界分数来无限期地迭代该函数。t然后索引每一行(i)和该行(j)中的每个项目,并寻找与我们要查找的内容相匹配的第一个分数。当找到它时,它j作为分子和2^i分母产生。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.