📜  如何检查15个拼图的实例是否可解决?

📅  最后修改于: 2021-04-29 04:38:25             🧑  作者: Mango

给定一个具有15个图块的4×4板(每个图块都有一个1到15的数字)和一个空白空间。目的是使用空白空间按顺序将数字放置在图块上。我们可以将四个相邻的(左,右,上方和下方)磁贴滑动到空白区域。例如,

15拼图

这里的X标记了元素可以移动到的位置,并且最终配置始终保持不变,可以解决难题。
通常,对于给定的宽度N的网格,我们可以通过遵循以下简单规则来确定N * N – 1个拼图是否可解决:

  1. 如果N为奇数,则在输入状态下,如果反转次数为偶数,则拼图实例是可解的。
  2. 如果N是偶数,则拼图实例是可解的
    • 空白从底部(倒数第二,第四等)开始在偶数行上,反转次数为奇数。
    • 空格位于从底部开始计数的奇数行(倒数,倒数第三,倒数第五等),并且倒数为偶数。
  3. 对于所有其他情况,拼图实例无法解决。

什么是反转?
如果我们假设将图块写成单行(1D数组)而不是散布在N行(2D数组)中,则如果a出现在b之前但a> b,则一对图块(a,b)构成一个反转。
对于上面的示例,请考虑连续写出的图块,如下所示:
2 1 3 4 5 6 7 8 9 10 11 12 13 14 15 X
上面的网格仅形成1个反演,即(2,1)。
插图:

15puz1

15puz2

15Puzz3

15Puzz4

下面是一个简单的C++程序,用于检查给定的15个拼图实例是否可解决。该程序是通用程序,可以扩展到任何网格宽度。

C++
// C++ program to check if a given instance of N*N-1
// puzzle is solvable or not
#include 
#define N 4
using namespace std;
 
// A utility function to count inversions in given
// array 'arr[]'. Note that this function can be
// optimized to work in O(n Log n) time. The idea
// here is to keep code small and simple.
int getInvCount(int arr[])
{
    int inv_count = 0;
    for (int i = 0; i < N * N - 1; i++)
    {
        for (int j = i + 1; j < N * N; j++)
        {
            // count pairs(i, j) such that i appears
            // before j, but i > j.
            if (arr[j] && arr[i] && arr[i] > arr[j])
                inv_count++;
        }
    }
    return inv_count;
}
 
// find Position of blank from bottom
int findXPosition(int puzzle[N][N])
{
    // start from bottom-right corner of matrix
    for (int i = N - 1; i >= 0; i--)
        for (int j = N - 1; j >= 0; j--)
            if (puzzle[i][j] == 0)
                return N - i;
}
 
// This function returns true if given
// instance of N*N - 1 puzzle is solvable
bool isSolvable(int puzzle[N][N])
{
    // Count inversions in given puzzle
    int invCount = getInvCount((int*)puzzle);
 
    // If grid is odd, return true if inversion
    // count is even.
    if (N & 1)
        return !(invCount & 1);
 
    else     // grid is even
    {
        int pos = findXPosition(puzzle);
        if (pos & 1)
            return !(invCount & 1);
        else
            return invCount & 1;
    }
}
 
/* Driver program to test above functions */
int main()
{
 
    int puzzle[N][N] =
    {
        {12, 1, 10, 2},
        {7, 11, 4, 14},
        {5, 0, 9, 15}, // Value 0 is used for empty space
        {8, 13, 6, 3},
    };
    /*
    int puzzle[N][N] = {{1, 8, 2},
                    {0, 4, 3},
                    {7, 6, 5}};
 
    int puzzle[N][N] = {
                    {13, 2, 10, 3},
                    {1, 12, 8, 4},
                    {5, 0, 9, 6},
                    {15, 14, 11, 7},
                };
 
    int puzzle[N][N] = {
                    {6, 13, 7, 10},
                    {8, 9, 11, 0},
                    {15, 2, 12, 5},
                    {14, 3, 1, 4},
                };
 
 
    int puzzle[N][N] = {
                    {3, 9, 1, 15},
                    {14, 11, 4, 6},
                    {13, 0, 10, 12},
                    {2, 7, 8, 5},
                };
    */
 
    isSolvable(puzzle)? cout << "Solvable":
                        cout << "Not Solvable";
    return 0;
}


PHP
 j.
 
                $inv_count++;
        }
    }
    return $inv_count;
}
 
// find Position of blank from bottom
function findXPosition($puzzle)
{
    global $N;
    // start from bottom-right corner of matrix
    for ($i = $N - 1; $i >= 0; $i--)
        for ($j = $N - 1; $j >= 0; $j--)
            if ($puzzle[$i][$j] == 0)
                return $N - $i;
}
 
// This function returns true if given
// instance of N*N - 1 puzzle is solvable
function  isSolvable( $puzzle)
{
    global $N;
    // Count inversions in given puzzle
    $invCount = getInvCount($puzzle);
 
    // If grid is odd, return true if inversion
    // count is even.
    if ($N & 1)
        return !($invCount & 1);
 
    else     // grid is even
    {
        $pos = findXPosition($puzzle);
        if ($pos & 1)
            return !($invCount & 1);
        else
            return $invCount & 1;
    }
}
 
/* Driver program to test above functions */
 
 
    $puzzle =
    array(
        array(12, 1, 10, 2),
        array(7, 11, 4, 14),
        array(5, 0, 9, 15), // Value 0 is used for empty space
        array(8, 13, 6, 3),
    );
     
 
    if(isSolvable($puzzle)==0)
     
            echo  "Solvable";
     else
            echo  "Not Solvable";
 
 
#This code is contributed by aj_36
?>


输出

Solvable

时间复杂度:O(n 2 )

空间复杂度:O(n)

这是如何运作的?
事实1:对于宽度为奇数的网格,所有合法移动都会保留反转数量的极性(偶数或奇数)。
事实证明1

  • 沿着行(左或右)移动磁贴不会改变反转的次数,因此也不会改变其极性。
  • 沿着列(向上或向下)移动磁贴可以更改反转次数。该图块会移动经过偶数个其他图块(N – 1)。因此,将反相计数增加或减少2,或将反相计数保持不变。

事实2:对于偶数宽度的网格,以下内容是不变的:(#inversions偶数)==(从底部开始的奇数行上为空白)。

15puzz6

示例:考虑上述举动。左侧的反转次数为49,空白处在从底部开始的偶数行中。因此,不变量的值为“ false == false”,这是正确的。右侧的反转数为48,因为11个反转了2个反转,而14个反转了1个。空白位于底部的奇数行上。因此,不变式的值为“ true == true”,这仍然是正确的。
事实证明2

  • 沿着行(向左或向右)移动图块不会更改反转次数,也不会更改空白行。
  • 沿着列(向上或向下)移动磁贴确实会改变反转次数。磁贴移动经过其他奇数个磁贴(N – 1)。因此,反转次数会发生奇数次变化。空白行也从奇数更改为偶数,或从偶数更改为奇数。因此,不变性的两个部分都发生了变化。因此,它的价值得以保留。

结合事实1 +事实2 =事实3:

  • 如果宽度是奇数,则每个可解状态都有偶数个反转。
    如果宽度是偶数,那么每个可解状态都具有
    • 如果空白在从底部开始计数的奇数行中,则为偶数个反转;
    • 如果空白处在从底部开始计数的偶数行中,则反转的数目为奇数;

事实证明3:

  • 初始(已解决)状态具有这些属性。
  • 那些法律行为都会保留这些财产。
  • 通过一系列合法的举动,可以从初始状态达到任何可解决的状态。