为傻瓜解魔方

2023-12-22

Mr. Dum:你好,我很笨,但我还是想解出一个 3x3x3 的魔方。

斯马特先生:嗯,你很幸运。Here https://stackoverflow.com/questions/5563671/solving-rubiks-cube-programmatically就是这样做的指导!

Dum 先生:不,这对我不起作用,因为我是 Dum。我只能遵循这样的算法。

pick up cube

look up a list of moves from some smart person

while(cube is not solved)
    perform the next move from list and turn
    the cube as instructed.  If there are no
    more turns in the list, I'll start from the
    beginning again.

hey look, it's solved!

斯马特先生:啊,没问题,这是你的清单!


好的,那么什么样的列表适合解决这样的问题呢?我知道魔方离解开 20 步的距离永远不会太远 http://www.cube20.org/, 然后魔方有 43,252,003,274,489,856,000 种排列 http://b.chrishunt.co/how-many-positions-on-a-rubiks-cube。因此,我认为这个列表可能很长(20 * 43,252,003,274,489,856,000),但是

  • 有谁知道目前已知的最短的此类列表?
  • 您如何找到理论上最短的列表?

请注意,这纯粹是一个理论问题,我实际上不想对计算机进行编程来做到这一点。


通过立方体的所有排列获得这样的路径的一个想法是使用人类求解器使用的一些序列。 Mr Smart 算法的主要结构如下所示:

function getMoves(callback):
    paritySwitchingSequences = getParitySwitchingSequences()
    cornerCycleSequences = getCornerCycleSequences()
    edgeCycleSequences = getEdgeCycleSequences()
    cornerRotationSequences = getCornerRotationSequences()
    edgeFlipSequences = getEdgeFlipSequences()
    foreach paritySeq in paritySwitchingSequences:
        if callback(paritySeq) return
        foreach cornerCycleSeq in cornerCycleSequences:
            if callback(cornerCycleSeq) return
            foreach edgeCycleSeq in edgeCycleSequences:
                if callback(edgeCycleSeq) return
                foreach cornerRotationSeq in cornerRotationSequences:
                    if callback(cornerRotationSeq) return
                    foreach edgeFLipSeq in edgeFlipSequences:
                        if callback(edgeFlipSeq) return

The 5 get...函数都会返回一个序列数组,其中每个序列都是一个移动数组。回调系统将避免将所有移动保留在内存中,并且可以用更现代的生成器语法重写(如果目标语言可用)。

哑巴先生会有这样的代码:

function performMoves(sequence):
    foreach move in sequence:
        cube.do(move)
        if cube.isSolved() then return true
    return false

getMoves(performMoves)

Dumb 先生的代码将他的回调函数传递给 Smart 先生一次,然后 Smart 先生将继续回调该函数,直到返回 true。

Smart 先生的代码将遍历 5 个中的每一个get函数来检索他开始为调用者生成序列所需的基本序列。我将在下面描述这些函数,从最内层循环中使用结果的函数开始:

获取边缘翻转序列

想象一个立方体,所有部件都在其右侧插槽中并正确旋转,除了可以翻转但仍在右侧插槽中的边缘之外。如果将它们翻转,立方体就会被解决。由于有 12 条边,但同时只能翻转 2 条边,因此该立方体的边翻转(或不翻转)的方式为 2^11 = 2048。否则,12 条边中有 11 条可以具有任何翻转状态(翻转或不翻转)的边,而最后一个边受其他 11 条翻转的约束。

该函数应返回同样多的序列,以便在应用其中一个序列后,会生成具有一组唯一的翻转边的立方体的下一个状态。

function getEdgeFlipSequences
    sequences = []
    for i = 1 to 2^11:
        for edge = 1 to 11:
           if i % (2^edge) != 0 then break
        sequence = getEdgePairFlipSequence(edge, 12)
        sequences.push(sequence) 
    return sequences

内部循环确保在外部循环的每次迭代中进行一次翻转,您就可以获得所有可能的翻转状态。

这就像以二进制表示形式列出所有数字,只需翻转一位即可得出下一个数字。以这种方式生成的数字输出不会按顺序排列,但您将获得全部数字。例如,对于 4 位(而不是 11 位),它会像这样:

0000
0001
0011
0010
0110
0111
0101
0100
1100
1101
1111
1110
1010
1011
1001
1000

该序列将确定哪条边与第 12 条边一起翻转。我不会对此进行定义获取EdgePairFlipSequence现在运行。很明显,存在翻转任何一对边的序列,并且在它们不公开的情况下,人们可以轻松地进行一些操作以使这两个边处于更好的位置,进行双重翻转并将这些边返回到原始状态通过以相反的顺序和相反的方向应用起始动作来再次定位。

获取角点旋转序列

这个想法与上面相同,但现在有旋转的角。不同的是,一个角可以有三种旋转状态。但与翻转边缘一样,如果您知道 7 个角(已位于正确位置)的旋转,则也可以确定第 8 个角的旋转。因此,立方体的角旋转有 3^7 种可能的方式。

将一个角与第 8 个角一起旋转并找到所有可能的角旋转的技巧在这里也适用。 3 基数表示的模式如下(对于 3 个角):

000
001
002
012
011
010
020
021
022
122
121
120
110
111
112
102
101
100
200
201
202
212
211
210
220
221
222

所以这个函数的代码如下所示:

function getCornerRotationSequences
    sequences = []
    for i = 1 to 3^7:
        for corner = 1 to 7:
           if i % (3^edge) != 0 break
        sequence = getCornerPairRotationSequence(corner, 8)
        sequences.push(sequence)
    return sequences

再说一遍,我不会定义获取角点对旋转序列。与边缘类似的推理也适用。

获取边缘循环序列

当您想要移动边而不影响立方体的其余部分时,您需要循环至少 3 个边,因为不可能在不改变其他任何内容的情况下交换两条边。

例如,可以交换两条边和两个角。但这超出了该函数的范围。稍后在处理最后一个函数时我会再次讨论这一点。

该函数的目的是找到通过重复循环 3 个边可以到达的所有可能的立方体状态。有 12 条边,如果您知道其中 10 条边的位置,则可以确定其余 2 条边的位置(仍然假设角保持在其位置)。因此,在这些条件下,边有 12!/2 = 239 500 800 种可能的排列。

这可能在内存方面存在一些问题,因为要生成的序列数组将占用该数字的倍数(以字节为单位),因此我们可能正在讨论几千兆字节。但我假设有足够的内存:

function getEdgeCycleSequences
    sequences = []
    cycles = getCyclesReachingAllPermutations([1,2,3,4,5,6,7,8,9,10,11,12])
    foreach cycle in cycles:
        sequence = getEdgeTripletCycleSequence(cycle[0], cycle[1], cycle[3])
        sequences.push(sequence)
    return sequences

The getCycles实现所有排列函数将返回一个三元组边的数组,这样,如果您按照三元组中列出的方式从左到右循环边,并对完整数组重复此操作,您将获得所有可能的边排列(不改变位置)角)。

几个答案这个问题 https://stackoverflow.com/questions/34829677/find-permutations-by-repeatedly-cycling-3-elements/34832886我问可以用来实现getCyclesReachingAllPermutations。伪代码基于这个答案 https://stackoverflow.com/questions/34829677/find-permutations-by-repeatedly-cycling-3-elements#answer-34832747可能看起来像这样:

function getCyclesReachingAllPermutations(n):
    c = [0] * n
    b = [0, 1, ... n]
    triplets = []

    while (true):
        triplet = [0]
        for (parity = 0; parity < 2; parity++):
            for (k = 1; k <= c[k]; k++):
                c[k] = 0
                if (k == n - 1):
                    return triplets
            c[k] = c[k] + 1
            triplet.add( b[k] )
            for (j = 1, k--; j < k; j++, k--):
                swap(b, j, k)
        triplets.add(triplet)

类似地,对于其他主要函数,这里也是对函数的依赖获取EdgeTripletCycleSequence,我就不展开展开了。有许多已知的序列可以循环三个边缘、多个位置,并且可以很容易地从它们导出其他序列。

获取角点循环序列

我将保持简短,因为它与边缘相同。如果边不移动,则角有 8!/2 种可能的排列。

function getCornerCycleSequences
    sequences = []
    cycles = getCyclesReachingAllPermutations([1,2,3,4,5,6,7,8])
    foreach cycle in cycles:
        sequence = getCornerTripletCycleSequence(cycle[0], cycle[1], cycle[3])
        sequences.push(sequence)
    return sequences

获取奇偶校验切换序列

需要这个额外的级别来处理立方体可以处于奇数或偶数位置的事实。当需要奇数次四分之一移动(那么半圈算作 2)来解出立方体时,这就很奇怪了。

我之前没有提到过,但是上面使用的所有序列都不应该改变立方体的奇偶校验。当我写到排列边缘时,角应该保持在原来的位置时,我确实隐含地提到了这一点。这确保了奇偶校验不会改变。另一方面,如果您要应用同时交换两个边缘和两个角的序列,则必须切换奇偶校验。

但由于上面的四个函数没有考虑到这一点,因此需要这个额外的层。

该功能非常简单:

function getParitySwitchingSequences
    return = [
        [L], [-L]
    ]

L是一个常量,表示立方体左面的四分之一移动,并且-L是同样的动作,但方向相反。它可以是任何一张脸。

切换立方体奇偶校验的最简单方法就是:执行四分之一移动。

Thoughts

这个解决方案当然不是最佳的,但它是一个最终会遍历立方体所有状态的解决方案,尽管一路上会出现许多重复的状态。两次连续排列之间的移动次数少于 20 步即可实现这一点。移动次数将在 1(用于奇偶校验切换)和 18(用于翻转两个边缘)之间变化,允许 2 次额外移动以使边缘处于良好的相对位置,以及 2(用于在双翻转后将该边缘放回原位) 14移动,我认为这是最坏的情况。

一种快速优化是将奇偶校验循环作为内部循环,因为它只包含四分之一的移动,所以让该循环重复次数最多会更有效。

汉密尔顿图:最好的

图表已经构建完成 https://math.stackexchange.com/questions/184760/brute-force-method-of-solving-the-cube-how-many-moves-would-it-take#answer-184763其中每条边代表一次移动,节点代表all独特的立方体状态。它是循环的,这样从最后一个节点向前延伸的边将带您回到第一个节点。

因此,这应该允许您以尽可能多的移动次数遍历所有立方体状态。显然,不存在更好的解决方案。图表可以下载 http://bruce.cubing.net/index.html.

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

为傻瓜解魔方 的相关文章

  • 最慢的计算复杂度(Big-O)

    在这些算法中 我知道 Alg1 是最快的 因为它是 n 平方的 接下来是 Alg4 因为它是 n 的立方 然后 Alg2 可能是最慢的 因为它是 2 n 这应该具有非常差的性能 然而Alg3和Alg5在我的阅读速度方面还没有遇到过 这两种算
  • 点集子集的最小周长凸包

    给定平面上的 n 个点 没有 3 个共线 给定数字 k 找到 k 个点的子集 使得 k 个点的凸包在 k 个点的子集的任何凸包中具有最小周长 我可以想到一个简单的方法 运行时间为 O n k k log k 找到大小为 k 的每个子集的凸包
  • 解释一下从 N 个给定集合中的每一个中给出第 K 个最大数字的示例?

    今天我尝试解决一个Facebook 编程挑战赛 https facebook interviewstreet com recruit challenges 我遇到的挑战是 酒吧问题 可以找到here https github com alo
  • 时间复杂度和运行时间有什么区别?

    时间复杂度和运行时间有什么区别 它们是一样的吗 运行时间是指程序运行所需的时间 时间复杂度是对输入大小趋于无穷大时运行时间渐进行为的描述 您可以说运行时间 是 O n 2 或其他什么 因为这是描述复杂性类和大 O 表示法的惯用方式 事实上
  • 关于在字典中查找所有有效单词的算法问题

    给定一个字典 只是一个字符串列表 您收到来自外部来源的未知数量的信件 给定字母串 您将如何列出您可以通过这些字母的任意组合组成的所有有效单词 来自字典 因此 如果您收到 applead 你应该找到apple bad pad lead等 我知
  • 使用多级解决方案计算二维网格中的最近邻

    我有一个问题 在 x y 大小的网格中 我提供了一个点 并且我需要找到最近的邻居 在实践中 我试图在 pygame 中找到距离光标最近的点 该点跨越颜色距离阈值 计算如下 sqrt rgb1 0 rgb2 0 2 rgb1 1 rgb2 1
  • 如何有效地找到距给定点最远的点(从一组点中)?

    我正在寻找一种算法或数据结构来解决以下问题 给你一组点 S 然后你会得到另一个点形式的 Q 查询 对于每个查询 找到集合中距离给定点最远的点 集合中最多有 10 5 个点和 10 5 个查询 所有点的坐标都在 0 到 10 5 范围内 我想
  • 直接选择排序与交换选择排序

    有什么区别直接选择排序 vs 交换选择排序 今天我陷入了一场争论 我的教授在他的讲义中使用了这两个术语 维基百科和任何教科书或网站都会为您提供的选择排序就是他所说的 交换选择排序 我以前从未听说过 交换选择排序 这个术语 仅 选择排序 并且
  • 如何检查是否存在可能的路径?

    我正在开发一个基于 javascript 的实验性游戏 玩家必须在二维平铺地图上移动才能退出 请随意检查这个小提琴并演奏 http jsfiddle net moonlife 74vLd 我只是随机放置障碍物 但有时障碍物会挡住玩家和出口之
  • 找到一条穿过任意节点序列的最短路径?

    In 这个先前的问题 https stackoverflow com questions 7314333 find shortest path from vertex u to v passing through a vertex wOP询
  • 需要一种将网络块范围折叠为超集范围列表的算法

    我的数学不及格 我需要一种有效的方法将网络范围缩小为超集 例如如果我输入 IP 范围列表 1 1 1 1至2 2 2 5 1 1 1 2至2 2 2 4 10 5 5 5至155 5 5 5 10 5 5 6至10 5 5 7 我想返回以下
  • 具有 2 个属性的背包算法。如何在 3d 数组中实现它?

    当有超过 1 个属性时 我无法理解背包问题 当有 1 个属性时 我必须编写一个使用具有 2 个属性的背包算法的程序 老师告诉我们 它必须在 3d 数组中完成 错误的实现将导致 O 2 n 处理时间 我无法想象这样的数组会是什么样子 假设这是
  • 两组点之间的最佳匹配

    I ve got two lists of points let s call them L1 P1 x1 y1 Pn xn yn and L2 P 1 x 1 y 1 P n x n y n 我的任务是找到它们点之间的最佳匹配 以最小化它
  • 用于开始和/或包含搜索的最快字符串集合结构/算法是什么

    我有以下情况 我有一个大的字符串集合 比如说 250 000 平均长度可能是 30 我要做的就是在这些搜索中进行许多搜索 大多数搜索都是 StartsWith 和 Contains 类型的 该集合在运行时是静态的 这意味着选择的集合的初始读
  • 归并排序中的递归:两次递归调用

    private void mergesort int low int high line 1 if low lt high line 2 int middle low high 2 line 3 mergesort low middle l
  • 高效列出目录中的所有子目录

    请参阅迄今为止所采取的建议的编辑 我正在尝试使用 WinAPI 和 C 列出给定目录中的所有目录 文件夹 现在我的算法又慢又低效 使用 FindFirstFileEx 打开我正在搜索的文件夹 然后我查看目录中的每个文件 使用 FindNex
  • 数量重新分配逻辑 - 具有外部数据集的 MapGroups

    我正在研究一种复杂的逻辑 需要将数量从一个数据集重新分配到另一个数据集 在例子中我们有Owner and Invoice 我们需要从数量中减去Invoice准确地Owner匹配 在给定汽车的给定邮政编码处 减去的数量需要重新分配回同一辆车出
  • 将名称字符串编码为唯一的数字

    我有一大堆名字 数以百万计 他们每个人都有一个名字 一个可选的中间名和一个姓氏 我需要将这些名称编码为唯一代表这些名称的数字 编码应该是一对一的 即一个名称只能与一个数字相关联 一个数字只能与一个名称相关联 对此进行编码的明智方法是什么 我
  • 列出所有 k 元组,其条目总和为 n,忽略旋转

    有没有一种有效的算法来查找所有序列k总和为的非负整数n 同时避免旋转 如果可能的话 完全避免 顺序很重要 但对于我正在解决的问题来说 轮换是多余的 例如 与k 3 和n 3 我想要得到一个如下所示的列表 3 0 0 2 1 0 2 0 1
  • 使用 A 星查找路径的启发式函数

    I am trying to find a optimal solution for the following problem 每个节点内表示的数字表示为 x y 一个节点的相邻节点总是有一个y值为 当前节点 y 值 1 更改的成本为 1

随机推荐