如何在数据结构中表示魔方


58

如果我尝试模拟Rubik's Cube,您将如何创建一个数据结构以将多维数据集的状态存储在内存中,每边X个图块?

注意事项:

  • 立方体可以是任何大小
  • 它是魔方,因此可以旋转图层

2
家庭作业?还是现实世界中的问题...
SDG 2012年

4
您可能对该Rubik的Cube Solver的源代码感兴趣。
mouviciel 2012年

22
我很确定一个立方体的边数应该是6
Simon Bergot

3
我很好奇看到Rubik's Cube的模型展平了,所以我可以同时看到所有侧面。嗯,我很想现在写。如果可能的话,它可以是T形,甚至可以是无尽的平铺(我还没有想到后者)。
Lee Kowalkowski

6
我很想引述埃里克·埃文斯(Eric Evans)的话:“模型既不正确也不错误。它们只是或多或少有用的”(引用可能不是100%正确,因为它是从记忆中引用来的)
皮特

Answers:


37

一个普通的旧大小数组[6X][X]怎么了?您不需要了解内部迷你立方体,因为您看不到它们。它们不是多维数据集状态的一部分。将两个丑陋的方法隐藏在一个漂亮且易于使用的界面后面,对其进行单元测试,然后瞧,您完成了!


31
即使是真正的魔方,也没有任何内部迷你立方体
Jk。

8
这将起作用,但是您的算法可能非常复杂,无法适应如此简单的数据结构。
maple_shaft

6
As long as you know how the six surfaces are "threaded" together 这正是更强大的数据结构将为您提供的。我认为我们正在为同一件事争论。一个数组边,一个边是块的数组,但是,关于边和块有很多有趣的属性,它们有助于弄清楚“线程” 并不是很喜欢该术语,因为它可能与多线程混淆; )
maple_shaft

7
@maple_shaft“更强大的数据结构将为您带来什么。” 我对此一无所知:具有更多“结构”的数据结构必然会带来与设置,维护和访问该结构的各个部分有关的额外附带复杂性。很难说会更复杂-在带有某些极端情况的普通数组上执行丑陋的移位,再加上对访问单个单元格的“免费乘车”,或者在某种程度上没有那么复杂的移位和有些复杂的读取的结构。
dasblinkenlight 2012年

16
作为实际编写用于操纵Rubik's Cube的程序的人,我采用了使用六个二维数组(每个面一个)的简单方法。的确,您必须在多维数据集上实现一些令人讨厌的基本操作,但是此后您就可以不用考虑表示形式了。对我来说从来没有问题。我经常想知道从性能角度来看其他表示形式将如何工作,但从编码角度来看却从未感到负担。
PeterAllenWebb '04年

39

应当指出,我是一个狂热的速度立方体,但我从未尝试以编程方式在算法或数据结构中表示Rubik立方体。

我可能会创建单独的数据结构来捕获多维数据集中每个块的独特方面。

多维数据集上有3种不同类型的块:

  1. 角块-它具有三个彩色面和三个相邻的块,可以随时与它们共享一个侧面。

  2. 边缘块-它具有两个彩色面,并具有4个相邻的块,可以随时与该块共享一侧。在3x3块中,它始终具有2个中心块和2个角块。

  3. 中心块-在3x3立方体中,该块不可移动,但可以旋转。它将始终具有4个相邻的边块。在较大的立方体中,有多个中心块可以与另一个中心块或边缘块共享。中心块永远不会与角块相邻。

知道了这一点,一个块可以具有其所接触的其他块的引用列表。我将保留另一个列表列表,这将是代表单个多维数据集面的块列表和保留对每个多维数据集面的引用的列表。

每个立方体面都将表示为唯一面。

使用这些数据结构,编写一种算法将非常容易,该算法将在每个面上执行旋转转换,将适当的块移入或移出适当的列表。

编辑:重要说明,这些列表当然必须排序,但我忘了提了。 例如,如果我翻转右侧,则左上角右侧块将移动到右侧的右角并顺时针旋转。


同意每个块都需要具有唯一的属性。但转换不会很乏味,因为您必须更新对相邻块和的引用list of lists。最好只包含可以查询的无序块列表。并且在执行转换时只需更新相邻的块引用。如果要获取一个面中所有块的列表,则可以查询列表中与中心块相邻的所有块,对吗?
梅尔(Mel)

@Mel可以用任何一种方式来做,但是在与dasblinkenlight交谈之后,我实际上认为他的方法将不太复杂。我希望他的回答比我的要多。我对算法和最高效的算法并不太满意,我真的很喜欢rubiks多维数据集并收集它们(来自世界各地的40多种不同类型)。
maple_shaft

尽管dasblinknenlight的答案是最简单的解决方案,但我还是向您授予赏金,因为我喜欢您的答案包括这种数据结构所需的一些逻辑以及不同的块属性
Rachel

该数据模型更符合实际,但是它会使您想做的一些简单操作比应该做的要难-仅获取多维数据集的状态将需要递归地遍历多维数据集列表,这很麻烦。
2015年

@Ziv是的,但是问题是关于数据结构的问题,而不一定是关于算法的问题。
maple_shaft

13

当我想到这个问题时,我想到的是一个静态立方体,其颜色以已知模式在其上移动。所以....

多维数据集对象包含6个侧面对象,这些对象保持固定索引0-5。每侧包含9个位置对象,这些对象保持固定索引0-8。每个位置包含一种颜色。

为简单起见,以四分之一圈为增量处理每个动作。共有3个旋转轴,每个旋转轴有2个可能的方向,用于在多维数据集上总共进行6个可能的动作。有了这些信息,在多维数据集上映射6个可能的动作就变得相当简单。

因此,取决于所采取的措施,第6位置3的绿色可能会移动到第1位置3或第2位置7。我还没有进行足够的探索来找到任何数学翻译,但是可能会出现一些可以在代码中利用的模式。

使用数据结构,我如何知道处于特定状态的某个多维数据集是否可解?我本人一直在努力解决这个问题,还没有找到答案。

为此,请不要以随机立方体状态开始。而是从已解决状态开始,然后以编程方式执行n个操作以使多维数据集进入随机的开始状态。由于您仅采取法律措施才能达到当前状态,因此多维数据集必须是可解的。


1
经典的“您不想从这里开始”的建议!
Ergwun 2012年

8

我发现xyz坐标系是解决Rubik立方体的一种简单方法,而旋转矩阵是一种实现旋转的简单,通用的方法。

我创建了一个包含位置向量的Piece类(x, y, z)。可以通过将旋转矩阵应用于其位置(矩阵-矢量乘法)来旋转片段。该部件还将其颜色记录在一个元组中(cx, cy, cz),使颜色沿每个轴指向。逻辑少量确保这些颜色旋转期间适当地更新:在XY平面内旋转90度意味着我们将交换的值cxcy

因为所有旋转逻辑都封装在Piece类中,所以Cube可以存储无序的Pieces列表,并且可以以通用方式进行旋转。要旋转左侧,请选择x坐标为-1的所有片段,并将适当的旋转矩阵应用于每个片段。要旋转整个立方体,请对每个零件应用相同的旋转矩阵。

这个实现很简单,有很多好处:

  1. Piece对象的位置将改变,但是其颜色不会改变。这意味着您可以索要红绿色的块,挂在对象上,进行一些旋转,然后检查同一对象以查看红绿色的块在哪里结束。
  2. 每种类型的棋子(边缘,中心,角)都有唯一的坐标模式。对于3x3立方体,角片的位置矢量((-1, 1, 1))中不包含零,边的正好具有一个零((1, 0, -1)),而中心片具有两个零((-1, 0, 0))。
  3. 适用于3x3多维数据集的旋转矩阵将适用于NxN多维数据集。

缺点:

  1. 矩阵向量乘法比交换数组中的值慢。
  2. 按位置线性搜索零件。您必须将Pieces存储在外部数据结构中,并在旋转期间对其进行更新,以便按位置进行恒定时间的查找。这破坏了使用旋转矩阵的一些优雅,并将旋转逻辑泄漏到您的Cube类中。如果我要实现任何类型的基于搜索的求解算法,那么我将使用另一种实现。
  3. 模式分析(在求解过程中)并不尽如人意。一个棋子不知道其相邻的棋子,并且由于上述性能问题,分析将很慢。

3
我发现这种实现最能代表3D图形程序中的多维数据集。矩阵乘法可以对图层旋转进行动画处理。有关此方法的示例实现,请参见此github回购。我正在考虑将求解器添加到我的3D立方体中,我需要其他答案之一中的算法和数据结构。
乔纳森·威尔逊

5

您可以使用一个简单的数组(每个元素都有1到1的映射到面上的正方形的元素),并以一定的排列模拟每次旋转

您仅需3个基本排列就可以逃脱:通过轴旋转一个切面穿过正面,围绕垂直轴旋转多维数据集,并通过左右表面在水平轴上方旋转多维数据集。其他所有动作都可以通过这三个动作的串联来表示。

如果您最终得到2个已互换位置的边,单个翻转边,单个翻转角或2个交换角,您有一个不可解的多维数据集


1
the most straightforward way of know whether a cube is solvable is to solve it。好吧,使用该模型,您建议我认为是正确的。但是,如果您使用更接近@maple_shaft的模型并跟踪旋转,则可以通过验证边缘翻转mod 2的总和为0以及拐角旋转mod 3的总和为0,来快速测试3x3x3立方体是否可求解。计算边缘掉期和边角掉期(需要重新求解),它们的总和mod 2必须为0(总奇偶校验)。这些是证明立方体可解决的必要和充分的测试。
jimhark

3

可解决的第一个条件是存在每个零件,并且可以使用每个零件上的颜色来组装“已保存”的立方体。这是一个相对琐碎的条件,可以通过一个简单的清单确定其真相。定义了“标准”立方体上的配色方案,但是即使您不处理标准立方体,也只有6种!求解面的可能组合。

一旦所有部件和颜色正确,那么确定是否可以解决任何给定的物理配置就成为问题。并非所有人。最简单的检查方法是运行多维数据集求解算法,看看它是否以已解决的多维数据集终止。我不知道是否有花哨的组合技术来确定可溶性,而无需实际尝试求解立方体。

至于什么数据结构...几乎没有关系。棘手的部分是正确进行转换,并能够以一种使您能够巧妙地使用文献中可用算法的方式表示多维数据集状态。如枫木轴所示,有三种类型的零件。关于魔方解题的文献总是按类型来指代棋子。转换也以常见方式表示(查找Singmaster表示法)。另外,我见过的所有解决方案都始终将一个零件作为参考点(通常将白色的中间零件放在底部)。


1
对于点2,而不是从随机立方体开始并检查它是否可解决。我将从一个已解决的多维数据集开始,并对多维数据集执行n次随机操作以使其进入随机状态。
马修·维尼斯

是的,绝对是生成物理上可能解决的配置的最简单方法。从任意配置开始并确定它是否可解决绝对是一个独立但相关的问题。
Angelo'4

2
您猜想可能存在“幻想技术”来确定多维数据集是否可以解决;其实有。如果您拆解多维数据集但贴上贴纸,然后重新组装多维数据集,则不一定获得可解决的多维数据集;其实,赔率是一到十二,你有一个可解魔方。您可以通过对边缘和拐角的奇偶性分析来确定您是否处于可解状态;您实际上不必尝试解决多维数据集。
埃里克·利珀特

1
这是对多维数据集配对属性必须保留的三种类型的简要概述。ryanheise.com/cube/cube_laws.html
埃里克·利珀特

1
我在比赛stackexchange
Mel

3

由于您已经收到了不错的答案,所以让我添加一个细节。

不管您的具体表示方式如何,都请注意,镜头是“放大”立方体各个部分的绝佳工具。例如,看一下函数cycleLeft这个Haskell代码。这是一个通用函数,可循环排列任何长度为4的列表。执行L移动的代码如下所示:

moveL :: Aut (RubiksCube a)
moveL =
    cong cube $ cong leftCols cycleLeft
              . cong leftSide rotateSideCW

因此cycleLeft操作上由下式给出的视图 leftCols。类似地,,rotateSideCW它是将其旋转版本的一侧支撑的通用函数,在给出的视图上运行leftSide。其他举动可以类似的方式实现。

Haskell库的目标是创建漂亮的图片。我认为它成功了: 运作中的diagrams-rubiks-cube库


2

您似乎在问两个独立的问题。

  1. 如何用X边数表示一个立方体?

如果要模拟真实世界的Rubic立方体,则所有Rubik立方体都有6个面。我认为您的意思是“每边每尺寸X的瓦片数”。原始Rubic立方体的每一侧都是3x3。其他尺寸包括4x4(教授立方体),5x5和6x6。

我将使用“标准”多维数据集求解符号来表示6个面的数据:

  • 前:面对求解器的脸
  • 背部
  • 剩下

每边都是X乘X的二维数组。


您可以购买一个17x17的立方体!它确实有一些机械方面的折衷,但与真实事物是同构的。
RBerteig

1

我喜欢@maple_shaft的想法,以不同的方式表示不同的块(微型立方体):中心块,边块和角块分别带有1、2或3种颜色。

我将它们之间的关系表示为(双向)图,其边缘连接相邻的零件。每块都将有一个用于边缘(连接)的插槽阵列:中央块有4个插槽,边缘块有4个插槽,角块有3个插槽。替代地,中心件可以分别具有到边缘件的4个连接和角件的4个连接,和/或边缘件可以分别具有2个到中心件的连接和2个与角件的连接。

这些数组是有序的,以便在图边缘上进行迭代始终表示“相同”旋转,以立方体的旋转为模。也就是说,例如对于一个中心块,如果旋转多维数据集以使其表面在顶部,则连接顺序始终是顺时针方向。对于边角件也是如此。该属性在面部旋转后保持不变(或者现在看来如此)。

  • 寻找属于边缘的片段是微不足道的。
  • 查找属于一张脸的部分是微不足道的。
  • 查找与给定方向或给定方向在给定方向上的面,将遍历2或3个定义明确的链接。
  • 要旋转面,请更新连接到面中央部分的所有部分的连接。

如果希望很容易的话,也可以很容易地检测出明显无法解决的条件(交换/翻转的边角,调换的角),因为找到特定类型和方向的零件很简单。


1

节点和指针怎么样?

假设总有6个面,并且1个节点代表1个面的1个正方形:

r , g , b
r , g , b
r , g , b
|   |   |
r , g , b - r , g , b
r , g , b - r , g , b
r , g , b - r , g , b

节点旁边有一个指向每个节点的指针。在这种情况下,圆旋转仅将指针(节点数/面数)-1个节点移到1个节点上。由于所有旋转都是圆旋转,因此只需构建一个rotate函数。它是递归的,将每个节点移动一个空间,并检查是否已经足够移动它们,因为它将收集到节点的数量,并且始终有四个面。如果不是,请增加移动值的次数并再次调用旋转。

不要忘记它是双重链接的,所以也要更新新指向的节点。总会移动高度*宽度的节点数,每个节点更新一个指针,因此应该更新高度*宽度* 2的指针数。

由于所有节点都指向彼此,因此只需走一圈就可以更新每个节点。

这适用于任何大小的多维数据集,而没有边缘情况或复杂的逻辑。只是指针的走动/更新。


-1

从个人经验来看,使用一套工具来跟踪立方体的每个旋转部分效果很好。每个子立方体分为三组,没有魔方立方体的大小。因此,要在魔方的某个位置找到子立方体,只需将三个集合相交即可(结果是一个子立方体)。要进行移动,请从移动中涉及的子集中移除受影响的子幼崽,然后将它们放回作为移动结果的子集中。

4×4立方体将有12套。围绕立方体的6个面6组,六个带6组。每个面都有16个子立方体,每个带都有12个子立方体。共有56个子立方体。每个子多维数据集都保存有关颜色和颜色方向的信息。魔方立方体本身是4×4×4的阵列,每个元素的信息都由3个集合组成,这些3个集合定义了该位置的子立方体。

与其他11个答案不同,此数据结构让您使用集合的交集定义多维数据集中每个子块的位置。这样省去了在进行更改时必须更新Near子块的工作。


在先前的11个答案中,这似乎并没有提供任何实质性的要点和解释
gnat
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.