河内求解器塔


10

有关河内塔楼的信息,请用谷歌搜索或在Wikipedia页面上查看。

您的代码应该能够执行以下两项操作,它们是:

  • 接受在河内塔楼起点指定了光盘数量的用户输入
  • 以您选择的方式创建输出(只要出于某种逻辑即可)以显示塔式难题的解决方案。

逻辑输出的示例如下(使用4盘启动):

L1L2C1L1R-2R-1L1L2C1C-1R-2C1L1L2C1

L代表左钉子,C代表中心钉子,R代表右钉子,数字是该盘在该钉子上移动的方向和方向。正数表示向最右边的钉子移动的钉子的数量(因为磁盘从最左边的钉子开始)。

河内塔规则很简单:

  • 一次只能移动一个磁盘。
  • 每次移动都包括从一个钉子上取下上盘并将其滑到另一个钉子上,然后再将另一个钉子顶在该钉子上。
  • 请勿在较小的磁盘上放置任何磁盘。

磁盘自然地从最左端的钉开始,最大的在底部,最小的在顶部。


我们是否需要解决任意大的塔架,还是可以假设一些限制,例如10、100、1k,1M光盘?
用户未知

@userunknown(如果我是您),我不必担心过多的数字,但是我会说,程序可以处理的最大磁盘数应该仅受计算机的内存容量或调用堆栈限制(我猜是一样的事情,因为记忆是一个相当笼统的名词)。但是,在提交代码时,不要让任意多的数字吓到您;如果您的解决方案很有创造力,但只能处理这么多的磁盘,那么我还是会给予您荣誉的。
卡特·帕佩2012年

好吧,我的想法是一个效率很低的求解算法,如果极限是程序可以处理的话,那就没问题了。但是到目前为止,我已经了解了解决方案,并且意识到,我将参加一个完全不同的联赛。
用户未知

Answers:


2

外壳,5个字节

↑≠⁰İr

在线尝试!输出中的
每个n代表将磁盘移动n到下一个可用的销钉(循环包装)。

说明

   İr   The ruler sequence [0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, ...]
↑       Take while...
 ≠⁰     ... not equal to the input.

7

Python,76个字符

def S(n,a,b):
 if n:S(n-1,a,6-a-b);print n,a,b;S(n-1,6-a-b,b)
S(input(),1,3)

例如,对于N = 3,它返回:

1 1 3  (move disk 1 from peg 1 to peg 3)
2 1 2  (move disk 2 from peg 1 to peg 2)
1 3 2  (move disk 1 from peg 3 to peg 2)
3 1 3  ...
1 2 1
2 2 3
1 1 3

我知道我比赛有点迟了,但是这减少了13个字符:tio.run
##K6gsycjPM

6

Perl-54个字符

for(2..1<<<>){$_--;$x=$_&-$_;say(($_-$x)%3,($_+$x)%3)}

运行perl -M5.010并在stdin上输入光盘数量。

输出格式:

每行一行,第一位数字是固定汇率,第二位数字是固定汇率(从0开始)

例:

02 -- move from peg 0 to peg 2
01
21
02
10
12
02

卸下花括号可节省5个字符。$x=--$_&-$_,say(($_-$x)%3,($_+$x)%3)for 2..1<<<>
marinus,2012年

5

GolfScript(31 25 24个字符)

])~{{~3%}%.{)3%}%2,@++}*

感谢Ilmari Karonen指出我的原始trS /排列可以缩短6个字符。通过将它们分解为两个排列的乘积,我设法节省了一个。

请注意,将3%增加的长度除以一个字符:

])~{{~}%.{)}%2,@++}*{3%}%

有些人的输出格式非常复杂。这将输出从(编号0、1、2)移动的钉和移动到的钉。规范没有说明要移动到哪个钉子,因此它移动到钉子1。

例如

$ golfscript hanoi.gs <<<"3"
01021201202101

毫无疑问,sed中的相同逻辑甚至更短,但我的sed能力不尽如人意。
彼得·泰勒

1
您可以按25个字符进行操作:])~{.{3^3%}%2,@{2\-}%++}*
Ilmari Karonen 2012年

3

Perl,75个79个字符

完全窃取了Keith Randall的输出格式:

sub h{my($n,$p,$q)=@_;h($n,$p^$q^h($n,$p,$p^$q),$q*say"@_")if$n--}h pop,1,3

调用与-M5.010say

(我认为,如果您能找到一种方法来利用函数的返回值而不是仅仅抑制它,则可以改善这一点。)


[ say推荐使用“推荐”]
JB 2012年

好的-但是我是否不必将启用5.10功能的成本计入我的字符计数中?
面包箱

1
您可以- 但它是免费的。只需记下如何调用您的程序,以便那些不精通Perl调用细节的人都可以尝试一下。
JB 2012年

感谢您的链接;我早些时候在找那种东西。
面包箱

3

SML,63

fun f(0,_,_)=[]|f(n,s,t)=f(n-1,s,6-s-t)@[n,s,t]@f(n-1,6-s-t,t);

调用函数f(n,s,t)

  • n个磁盘
  • 的起点
  • 目标点

2

重击(64个字符)

t(){ tr 2$1 $12 <<<$s;};for((i=$1;i--;))do s=`t 1`01`t 0`;done;t

尽管发布它的长度是GolfScript的两倍以上,但仍要发布它,因为我喜欢重复使用t作为它echo $s


2

Scala,92 88 87个字符

def?(n:Int,a:Int,b:Int){if(n>0){?(n-1,a,a^b)
print(n,a,b);?(n-1,a^b,b)}};?(readInt,1,3)

输出格式

假设磁盘数量= 3,

(1,1,3)(2,1,2)(1,3,2)(3,1,3)(1,2,1)(2,2,3)(1,1,3) (disk number,from peg, to peg)
                                                   \---------------------------/       
                                                            Move 1              ... Move n

很好地使用xor。
彼得·泰勒

2

C,98 92 87个字符

实现最简单的算法。
输出格式为ab ab ab每对表示“将顶部圆盘从钉a移到钉b”。
编辑:移动现在以十六进制编码-0x12表示从钉1移到钉2。保存了一些字符。
编辑:从标准输入而不是参数中读取数字。短一点
例:
% echo 3 | ./hanoi
13 12 32 13 21 23 13

n;
h(d){n--&&h(d^d%16*16,printf("%02x ",d,h(d^d/16))),n++;}
main(){scanf("%d",&n);h(19);}

有人可以解释函数h()的语法-尤其是其递归调用中明显的两个参数(d ^ d%16 * 16和printf(...)),最后一个操作显然挂在了最后。根据我的知识,该函数有两个语法错误,但是我已经知道它可以构建(包括stdio之后)并可以正确执行。
格里芬2012年

1
可能传递比函数想要的更多的参数。他们的价值观无济于事。h(x,printf(...))只是在调用printf之前调用的一种方式h。最后一次n++是在内部h返回之后进行的。用于撤消initial n--
ugoren

谢谢,这很有意义(n ++的目的很明显)。为什么没有以n ++开头的分号而不是逗号,或者它有所作为?
格里芬2012年

@格里芬,实际上;这里是一样的。,通常是有用的(例如if(x)a,b;replaces if(x){a;b;}),但是在这里没有优势。
ugoren

2

果冻,5个字节

2*Ṗọ2

在线尝试!

0将最小的磁盘向右移动一个空间(如有必要,请换回开头)
1将第二小的磁盘移至唯一的其他合法列,
2将第三小的磁盘移至唯一的其他合法列,依此类推

算法

我们可以递归地看到河内塔问题的解决方案。移动尺寸的堆叠Ñ,移动尺寸的堆叠ñ -1从Ç,那么大小的磁盘Ñ,然后移动尺寸的堆叠ñ -1从Ç。这将产生以下形式的模式(以该程序使用的输出格式):

0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2 …

我们可以观察到该序列在OEIS上为A007814。序列的另一个可能定义是“序列的第k个(从1开始)元素是二进制数k的末尾的零个数”。这就是程序在计算的内容。

说明

首先,我们计算解中的移动数2 n -1。实际计算出一个额外的举动并在以后将其丢弃是最短的,因此,这是2*,即某物的幂2。(到目前为止,我们仅有的输入是命令行参数,因此默认情况下会使用它。)

接下来,我们使用Jelly的内建函数来计算以b为底的数字结尾处的零个数;那是。正如我们以二进制计算时,它是。我们需要做的就是将此内置函数应用于1到2 n -1(含)之间的数字。bọ2

在Jelly 和中R,有两种简单的方法可以迭代一定范围内的数字,而我在此问题上的早期尝试使用了其中一种方法。但是,在这种情况下,可以缩短一点:当给定数字作为输入时,将允许您进行迭代,使一个元素短(通常是一个内置函数,用于处理某个元素的除一个元素外)。在这种情况下,这正是我们想要的(因为2*生成了太多的要素),因此使用它进行链接2*ọ2使其成为2*Ṗọ2一个5字节的解决方案。


1

Awk,72个字符

function t(n,a,b){if(n){t(n-1,a,a^b);print n,a,b;t(n-1,a^b,b)}}t($0,1,3)

输出格式与Keith Randall的格式相同。

awk -f tower.awk <<< "3"    
1 1 1
2 1 1
1 1 1
3 1 3
1 1 1
2 1 3
1 1 3

1

Bash脚本,100个96个字符

t(){ [[ $1<1 ]] && return
t $(($1-1)) $2 $(($2^$3))
echo $@
t $(($1-1)) $(($2^$3)) $3
}
t $1 1 3

输出格式与Keith Randall的格式相同。

1 1 3
2 1 2
1 3 2
3 1 3
1 2 1
2 2 3
1 1 3

编辑:通过彼得的评论保存了4个字符。


1
您可以添加空格并通过回显节省一些字符$@
Peter Taylor

@PeterTaylor:好点。让我更新它。
约翰·卫斯理亲王

1

J,23个字节

二进制数解

2&(+/@:~:/\)@#:@i.@^~&2

此解决方案使用此视频中描述的二进制计数方法。

这就是说,我产生二进制数字从12^n,然后取长度为2的缀和每一位比较前面的号码的对应位和检查,如果他们不公平。不相等位数是该移动的输出。

输出,例如,对于3个磁盘,其中最小的磁盘标记为1:

1 2 1 3 1 2 1

1 表示“将最小的磁盘一个钉向右移动,如果需要,则循环回到第一个钉”

n,对于其他所有符号n,则表示“将磁盘n移至合法钉子”(永远都是一个)

在线尝试!

递归解

((],[,])$:@<:)`]@.(1=])

输出与上述解决方案相同,但是此处的逻辑使问题的递归性质更加清晰。

将其可视化为树也强调了这一点:

              4
             / \
            /   \
           /     \
          /       \
         /         \
        /           \
       /             \
      3               3      
     / \             / \    
    /   \           /   \
   /     \         /     \ 
  2       2       2       2  
 / \     / \     / \     / \
1   1   1   1   1   1   1   1

在线尝试!


1
原始问题提出后5年内提交您的答案的巧合性质是在我返回查看我5年前提交的该问题的答案的同一小时内……哇。+1
Carter Pape



0

R,73字节

在地图上放置R。受[Keith Randall's answer] [1]的启发,它具有简化的输入,仅打印结尾并开始固定以节省2个字节。也是0索引钉。

f=function(n,s=0,e=2){if(n){f(n-1,s,3-s-e)
print(c(s,e))
f(n-1,3-s-e,e)}}

在线尝试!


0

JavaScript(ES6),45b

h=(n,f,a,t)=>n?h(--n,f,t,a)+f+t+h(n,a,f,t):''

例如调用h(4, 'A', 'B', 'C')(使用辅助钉B将4个光盘从钉A移动到钉C)

返回'ABACBCABCACBABACBCBACABCABACBC'(将光盘从钉A移至钉B,将光盘从钉A移至钉C,将光盘从钉B移至钉C,等等)


1
真好 我想知道f,a,t参数是否应该在函数定义中包含默认值?否则,提交可以在其他参数中包含任意数据。我是新手,所以有经验的人应该建议。
约翰·里斯
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.