解决Rubik's Dubes for Dummies

时间:2016-01-07 13:38:54

标签: algorithm rubiks-cube

先生。 Dum:你好,我很傻但我还是想解决一个3x3x3魔方。

先生。聪明:嗯,你很幸运。 Here是做这件事的指导!

先生。 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!

先生。聪明:啊,这里没问题就是你的清单!

好的,那么什么样的列表可以解决这样的问题?我知道the Rubik's cube can never be farther away from 20 moves to solvedthere are 43,252,003,274,489,856,000 permutations of a Rubik's Cube。因此,我认为此列表可能是(20 * 43,452,003,274,489,856,000)长,但

  • 有谁知道目前已知的最短名单?
  • 您如何找到理论上最短的清单?

请注意,这纯粹是一个理论问题,我实际上并不想对计算机进行编程来执行此操作。

2 个答案:

答案 0 :(得分:4)

通过Cube的所有排列获得这样一条路径的想法是使用人类解算器使用的一些序列。 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

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

Dumb先生会有这个代码:

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 函数中的每一个,以检索开始向调用者生成序列所需的基本序列。我将在下面描述这些函数,从在最内层循环中使用结果的函数开始:

getEdgeFlipSequences

想象一个立方体,它的右侧插槽中有所有部件并且正确旋转,除了可以翻转但仍在右侧插槽中的边缘。如果它们被翻转,立方体就会被解决。由于有12个边缘,但是边缘只能同时翻转2个,这个立方体可以使其边缘翻转(或不翻转)的方式的数量是2 ^ 11 = 2048.否则放置,12个中有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条边一起翻转的边缘。我现在不打算定义 getEdgePairFlipSequence 函数。很明显,有一些用于翻转任何边缘的序列,并且在没有公开可用的地方,可以轻松地进行一些移动以使这两个边缘处于更好的位置,进行双重翻转并将这些边缘返回到其原始边缘通过以相反的顺序和相反的方向应用起始移动来再次定位。

getCornerRotationSequences

这个想法与上面相同,但现在有了旋转的角落。不同之处在于角落可以具有三种旋转状态。但是与翻转的边缘一样,如果你知道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

同样,我不会定义 getCornerPairRotationSequence 。适用于边缘的类似推理。

getEdgeCycleSequences

当您想要移动边缘而不影响立方体的其余部分时,您需要至少循环其中的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

getCyclesAchievingAllPermutations 函数将返回一个三元组边的数组,这样如果你将三元组中列出的边从左向右循环,并对整个数组重复此操作,你会得到所有可能的边缘排列(不改变角落的位置)。

我问的this question的几个答案可用于实现 getCyclesReachingAllPermutations 。基于this answer的伪代码可能如下所示:

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)

同样对于其他主要功能,这里也依赖于函数 getEdgeTripletCycleSequence ,我不会对其进行扩展。有许多已知的序列可以循环三个边缘,对于多个位置,其他可以很容易地从它们中导出。

getCornerCycleSequences

我会保持这个简短,因为它与边缘是一样的。如果边缘不移动,则角落有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

getParitySwitchingSequences

需要这个额外的级别来处理立方体可以处于奇数或偶数位置的事实。奇怪的是,需要奇数个四分之一移动(半转数计为2)才能解决立方体。

之前我没有提到它,但所有上面使用的序列都不应该改变立方体的奇偶校验。当我写下当边缘变换时,我确实暗中提到它,角落应该保持原来的位置。这可确保奇偶校验不会发生变化。另一方面,如果你应用一个同时交换两个边和两个角的序列,你必然要切换奇偶校验。

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

功能很简单:

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

L 是一个常数,表示立方体左面的四分之一移动, -L 是相同的移动,但是相反。它可能是任何面孔。

切换多维数据集奇偶校验的最简单方法就是:执行四分之一移动。

思想

这个解决方案当然不是最优的解决方案,但它是一个最终将通过立方体的所有状态的解决方案,尽管沿途出现了许多重复的状态。并且它将在两个连续排列之间进行少于20次移动。移动次数将在1之间变化 - 对于奇偶校验切换 - 和18 - 用于翻转两个边缘,允许2次额外移动以使边缘处于良好的相对位置,并且2用于在双重翻转之后将边缘放回到14移动,我认为这是最糟糕的情况。

一个快速优化是将奇偶校验循环作为内循环,因为它只包含四分之一的移动,使一个重复最多的效率更高。

汉密尔顿图:最好的

A graph has been constructed其中每条边代表一次移动,其中节点代表所有唯一的多维数据集状态。它是循环的,因此从最后一个节点向前的边缘会将您带回第一个节点。

因此,这应该允许您通过尽可能多的移动来遍历所有立方体状态。显然,不可能存在更好的解决方案。图can be downloaded

答案 1 :(得分:3)

您可以使用De Bruijn sequence获取一个肯定解决rubik立方体的序列(因为它将包含每个可能的大小为20的排列)。

来自wiki(Python):

$('#somebutton').click(
        function() {
            $.getJSON('Controller', function(searchList) {
                var $table = $('<table>').appendTo($('#somediv'));
                $.each(searchList, function(index, userBean) {
                    $('<tr>').appendTo($table).append(
                            $('<td>').text(userBean.caseNumber)).append(
                            $('<td>').text(userBean.caseOwner)).append(
                            $('<td>').text(userBean.status)).append(
                            $('<td>').text(userBean.issue)).append(
                            $('<td>').text(userBean.reason)).append(
                            $('<td>').text(userBean.age));
                });
            });
        });

你可以这样使用它:

def de_bruijn(k, n):
    """
    De Bruijn sequence for alphabet k
    and subsequences of length n.
    """
    try:
        # let's see if k can be cast to an integer;
        # if so, make our alphabet a list
        _ = int(k)
        alphabet = list(map(str, range(k)))

    except (ValueError, TypeError):
        alphabet = k
        k = len(k)

    a = [0] * k * n
    sequence = []

    def db(t, p):
        if t > n:
            if n % p == 0:
                sequence.extend(a[1:p + 1])
        else:
            a[t] = a[t - p]
            db(t + 1, p)
            for j in range(a[t - p] + 1, k):
                a[t] = j
                db(t + 1, t)
    db(1, 1)
    return "".join(alphabet[i] for i in sequence)

其中20是序列的大小,x是包含每个可能的(无法想到更好的词)的多维数据集的列表/字符串。