C ++永无止境的递归

时间:2014-11-12 19:47:21

标签: c++ recursion

任务是将给定状态转换为最终状态最少的步骤。例如,如果您输入如下字符串:

  

213456

状态看起来像这样(总是6个字符):
2 1 3
4 5 6

您需要将其转换为的状态始终相同:
1 2 3
4 5 6

您需要切换数字以获得最终状态,您只需水平和垂直切换它们。这不是一项艰巨的任务,但我的永无止境的递归问题。请不要评论代码(使用命名空间std;不使用函数重复进程等),因为我还没有完成,我需要你帮助我理解为什么这是一个永无止境的递归。

编辑:代码现在正在运作!

#include <iostream>
#include <map>
#include <string>
#include <algorithm>
#include <vector>

using namespace std;

int f(string s, map <string, int>& visited, int steps = 0)
{
    string s2 = s;
    vector <int> solutions;
    solutions.push_back(721);
    if(s == "123456")
        return steps;
    else
    {
        swap(s2[0], s2[1]);
        if(visited.find(s2) == visited.end() or steps < visited[s2])
        {
            visited[s2] = steps;
            solutions.push_back(f(s2, visited, steps + 1));
        }
        s2 = s;
        swap(s2[1], s2[2]);
        if(visited.find(s2) == visited.end() or steps < visited[s2])
        {
            visited[s2] = steps;
            solutions.push_back(f(s2, visited, steps + 1));
        }
        s2 = s;
        swap(s2[3], s2[4]);
        if(visited.find(s2) == visited.end() or steps < visited[s2])
        {
            visited[s2] = steps;
            solutions.push_back(f(s2, visited, steps + 1));
        }
        s2 = s;
        swap(s2[4], s2[5]);
        if(visited.find(s2) == visited.end() or steps < visited[s2])
        {
            visited[s2] = steps;
            solutions.push_back(f(s2, visited, steps + 1));
        }
        s2 = s;
        swap(s2[0], s2[3]);
        if(visited.find(s2) == visited.end() or steps < visited[s2])
        {
            visited[s2] = steps;
            solutions.push_back(f(s2, visited, steps + 1));
        }
        s2 = s;
        swap(s2[1], s2[4]);
        if(visited.find(s2) == visited.end() or steps < visited[s2])
        {
            visited[s2] = steps;
            solutions.push_back(f(s2, visited, steps + 1));
        }
        s2 = s;
        swap(s2[2], s2[5]);
        if(visited.find(s2) == visited.end() or steps < visited[s2])
        {
            visited[s2] = steps;
            solutions.push_back(f(s2, visited, steps + 1));
        }
        return *(min_element(solutions.begin(), solutions.end()));
    }
}

int main()
{
    string s;
    cin >> s;
    map <string, int> visited;
    cout << f(s, visited) << endl;
    return 0;
}

示例输入:

  

321456

示例输出:

  

3

2 个答案:

答案 0 :(得分:3)

问题是visited是按值传递的。如果您在递归调用中更改它,一旦返回,您就会失去此更改,就像您没有访问任何内容一样。

尝试通过引用传递的参数:

int f(string s, map <string, bool>& visited, int steps = 0)  // & in the dfinition of f

对我来说它返回5.我不知道它的正确值,但递归结束了

修改

我已经分析了这种递归算法:

  • 每个if对应一个潜在的替代举动。
  • 每次进一步的递归都与尝试移动相对应。
  • 一系列动作对应于一系列递归调用
  • 当通过值时,visited会累积前一级别中考虑的所有替代项。但不一定是那些动作的成功。这将导致忽略尚未导致解决方案的潜在动作。
  • 当通过引用传递时,将累积所有先前访问过的节点。同样,这将导致忽略潜在的移动。然而,被忽略的节点先前已经进入解决方案或死胡同。忽略它们不会忽视潜在的解决方案,但只是可能找不到更短的路径。
  • 正确的方法是通过值传递,但为每个替代方案重新启用visited
  • 然后算法将使用深度优先方法测试所有可能的非循环连续动作。
  • 你的拼图有6个项目,可以用720种方式排列,但你的算法似乎探索了这个组合中的每一个完整可能的组合。这是超过50万递归组合。
  • 不幸的是,算法非常昂贵:需要完成大量的矢量和地图副本并且需要内存。 4000次首次尝试花了差不多几分钟,由于内存消耗,它随着进展而变慢!总之,运行需要几个小时

现在我提出了一个名为修剪的简单技巧:我们将跟踪解决方案中最短的移动次数。每当我们探索时,我们会采取比这条最短路径更多的步骤,我们不会继续搜索。

我已经完成了另外两项更改:首先,我使用循环来探索替代方案,因为我懒得多次复制几乎相同的代码。我确实摆脱了解决方案的载体,只是用另一种方式跟踪最小数量的步骤:

int f(string s, int &sh, map <string, int> visited, int steps = 0)
{
    if (steps >= sh)  // THIS IS THE KEY: PRUNING !!! 
        return INT_MAX;
    static vector <pair<size_t, size_t>>possible_swaps{ { 0, 1 }, { 1, 2 }, { 2, 3 }, { 4, 5 }, { 0, 3 }, { 1, 4 }, { 2, 5 } };  // yes
    if (s == "123456") {  // solution found !! 
        if (sh > steps)   // check if it's the shortest solution for later pruning
            sh = steps;
        return steps;     // in any case, return the number of steps found
    }
    else
    {
        int smin = INT_MAX;  // just keep trace shortest number of steps in this iteration so far
        for (pair<size_t, size_t> sw : possible_swaps) {  // try possible swaps 
            string s2 = s;
            swap(s2[sw.first], s2[sw.second]);
            if (visited.find(s2) == visited.end()) {
                map <string, int> testvisited = visited;  // attention: we work on a temporary so to keep visited unchanged, for the next loops.  
                testvisited[s2] = true;
                int stp = f(s2, sh, testvisited, steps + 1);  // Recursion 
                if (stp < smin)    // if it's the shoretst alternative
                    smin = stp;       // keep track of it
            }
        }
        return smin;  // return the shortest nmber of steps in iteration. 
    }
}

然后在main()中你只需要调用,使用一个初始化变量,该变量应包含迄今为止在所有递归中找到的最短路径:

...
int shortest = INT_MAX; 
cout << "Result: "<<f(s, shortest, visited) << endl;

对于记录,这次我找到了3.

答案 1 :(得分:1)

而不是map <string, bool> visited,您应该使用map <string, int>& visited(请注意参考传递)。

然后您可以将if语句更改为:

if(visited.find(s2) == visited.end() || steps < visited[s2])
{
    visited[s2] = steps;
    solutions.push_back(f(s2, visited, steps + 1));
}

这样可以让你所有深度的路径相互传达一些路径信息。

请注意,您可以通过使用迭代解决方案进一步优化。广度优先搜索,但这应该足以在我们的生活中完成运行:)