C++永无止境的递归

C++ never-ending recursion

本文关键字:递归 永无止境 C++      更新时间:2023-10-16

任务是尽可能少地将给定状态转换为最终状态。例如,如果您输入这样的字符串:

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

问题是visited是通过值传递的。如果您在递归调用中对其进行更改,那么一旦返回,就会丢失此更改,就好像您什么都没访问一样。

尝试通过引用传递以下参数:

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

对我来说,它返回5。我不知道它的值是否正确,但递归已经结束了

编辑:

我分析了这个递归算法:

  • 每个CCD_ 2对应于潜在的替代移动
  • 每个进一步的递归对应于尝试移动
  • 一连串的移动对应于一连串的递归调用
  • 当传递值时,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。

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

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

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

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

请注意,您可能可以通过使用迭代解决方案&广度优先搜索,但这应该足够好,可以在我们生活中的某个时候完成运行:)