3 X 3 魔方递归

Posted

技术标签:

【中文标题】3 X 3 魔方递归【英文标题】:3 X 3 magic square recursively 【发布时间】:2015-06-22 13:42:17 【问题描述】:

我正在尝试找到 3X3 幻方的所有可能解决方案。 应该正好有 8 个解。

我的代码得到了所有这些,但有很多重复。我很难跟踪递归步骤以了解为什么我会得到所有重复。

// This program finds all solutions to the magic square for a 3X3     
// square where each column, row and diagonal sum is equal

#include <iostream>
using namespace std;
#define SQUARE_SIZE 9

int anyLine = 0;
int currLine = 0;
int numSolutions = 0;


// swap two values in the square.
void swap(int arr[], int idxa, int idxb)

    int tmp = arr[idxa];
    arr[idxa] = arr[idxb];
    arr[idxb] = tmp;


void printArray(int arr[])

    for (int i = 0; i < SQUARE_SIZE; i++)
    
        cout << arr[i] << " ";
        if ((i + 1) % 3 == 0)
            cout << endl;
    
    cout << endl;


// this function tests to see if we have a "good" arrangement of numbers            
// i.e the sum of each row, column and diagonal is equal

bool checkArr(int arr[])

    anyLine = arr[0] + arr[1] + arr[2];
    currLine = 0;
    for (int i = 0; i < SQUARE_SIZE; i++)
    
        currLine += arr[i];
        if ((i + 1) % 3 == 0)
        
            if (currLine != anyLine)
                return false;
            currLine = 0;
        
    

    // check vertically
    for (int col = 0; col <3; col++)
    
        for (int row = 0; row <3; row++)
        
            currLine += arr[col + 3 * row];
        

        if (currLine != anyLine)
            return false;

        currLine = 0;
    

    // check the diagonals
    if ((arr[2] + arr[4] + arr[6]) != anyLine)
        return false;

    if ((arr[0] + arr[4] + arr[8]) != anyLine)
        return false;

    return true;


void solve(int arr[], int pos)

    if (pos == 8)
    
        if (checkArr(arr))
        
            printArray(arr);
            numSolutions++;
        
     else 
    
        for (int i = 0; i < 9; i++)
        
            if (i == pos) continue;

            if (checkArr(arr))
            
                printArray(arr);
                numSolutions++;
            
            swap(arr, pos, i);
            solve(arr, pos + 1);
        
    


int main()

    int arr[SQUARE_SIZE] =  1, 2, 3, 4, 5, 6, 7, 8, 9 ;

    solve(arr, 0);
    cout << "number of solutions is: " << numSolutions << endl;

    return 0;

【问题讨论】:

为什么不在递归调用前面放一个 cout (solve(arr,pos+1))。然后,您可以重构调用序列,并可能找出问题所在。 作为一个经验法则,当使用递归时,不要测试(在你的情况下,checkArr),除非在基本情况下(if(pos == 8))。如果您在这样的两个不同地方进行检查,可能会导致问题。 【参考方案1】:

基本上,您使用recursive permutation algorithm 查找数组的所有排列。

你需要改变 4 件事:

首先,从 pos 开始循环,而不是 0

二、递归后交换元素(回溯)

第三,仅在生成每个完整排列后进行测试(当 pos = 8 时),否则您将多次测试相同的排列。

第四,与自身交换元素(即不交换它)是一种有效的排列,因为允许元素保持在其原始位置。

void solve(int arr[], int pos)

    if (pos == 8)
    
        if (checkArr(arr))
        
            printArray(arr);
            numSolutions++;
        
    
    else
    
        for (int i = pos ; i < 9; i++)
         
            swap(arr,pos,i);
            solve(arr,pos +1);
            swap(arr,pos,i); 
        
    

Demo

【讨论】:

谢谢,这行得通,现在我必须弄清楚为什么。【参考方案2】:

您的代码从两个地方调用printArray - 递归的基本情况(即当pos == 8 时)和在调用swap 之前的循环中。第二次调用是不必要的:当你到达pos == 8 状态时,你会得到相同的方块。

这会减少重复的数量,但不会因为生成方块的方式而消除它们。您需要跟踪已打印的内容。一种方法是制作一组您找到的解决方案,并在打印新找到的解决方案之前对其进行检查:

set<int> seen;

int key(int arr[]) 
    return arr[0]
    + 10 * arr[1]
    + 100 * arr[2]
    + 1000 * arr[3]
    + 10000 * arr[4]
    + 100000 * arr[5]
    + 1000000 * arr[6]
    + 10000000 * arr[7]
    + 100000000 * arr[8];


 void printArray(int arr[]) 
    if (!seen.insert(key(arr)).second) 
        // second is set to false when a duplicate is found
        return;
    
    numSolutions++;
    for (int i = 0; i < SQUARE_SIZE; i++) 
        cout << arr[i] << " ";
        if((i+1) % 3 == 0)
            cout << endl;
    
    cout << endl;
 

Demo.

关于上述解决方案的几点注意事项:

key(int[]) 将正方形转换为单个十进制数字,因此这种方法仅适用于由十进制数字组成的正方形。对于任意数字,您需要采用不同的策略 - 例如,使用一组逗号分隔的字符串。 解决方案计数移至printArray(int[])。您可以完全放弃numSolutions,而改用seen.size();它提供了相同的答案。

【讨论】:

谢谢,效果很好,虽然其他解决方案只需要删除一些代码。【参考方案3】:

如果您不想出于练习目的实际递归解决此问题,我建议使用std::next_permutation

void solve(int(&arr)[SQUARE_SIZE], int pos)

    sort(std::begin(arr), std::end(arr));
    do 
        if (checkArr(arr))   
            numSolutions++;
            printArray(arr);
        
     while (next_permutation(begin(arr), end(arr)));   

【讨论】:

以上是关于3 X 3 魔方递归的主要内容,如果未能解决你的问题,请参考以下文章

关于python递归函数怎样理解

初学者如何理解递归

matlab分段+递归函数的表示方法

习题10-3 递归实现指数函数

递归算法的时间复杂度

递归小记