【问题标题】:How can I convert a basic nested loop to recursive in C++如何在 C++ 中将基本的嵌套循环转换为递归
【发布时间】:2018-05-10 02:48:02
【问题描述】:

我正在学习递归,我正在寻找一个简单的例子来说明如何将这个基本的嵌套循环转换为递归函数。感谢您的输入: 编辑:我提供了转换嵌套循环的失败尝试。我还不能想象递归过程,但我的研究表明这是递归格式。它不会显示输出,因为我不确定在哪里放置 cout 线。

嵌套循环:

#include "stdafx.h"
#include<string>
#include<fstream>
#include<iomanip>
#include<vector>
#include<iostream>
#include<string.h>


using namespace std;

void recursive(int x, int y)
{
    for (int i = x; i > 0; i--)
        for (int j = y; j > 0; j--)
        {
            cout << i << " , " << j << endl;
        }
}
int main()
{
    int x, y;
    cout << "Enter 2 numbers:\n";
    cin >> x >> y;
    recursive(x, y);
    return 0;
}

我尝试转换为递归函数:

void recursive(int start, int N)
{

    for (int x = start; x < N; x++)
    {

        recursive(x + 1, N);

    }



    for (int y = start; y < N; y++)
    {

        recursive(y + 1, N);

    }
}

int Main()
{
    recursive(0,3);
    return 0;
}

【问题讨论】:

  • 啊!伙计,我们有问题!所以不是那样工作的。尝试提出一个递归代码,然后寻求改进/纠正它的建议。
  • 开始考虑两个递归函数。
  • 这两天我一直在努力转换它,我向你保证这不是懒惰。我只是很难想象递归函数对数据做了什么。我尝试绘制堆栈调用,并查看各种解决方案。我认为转换这个简单的递归将有助于我设想它。
  • “存根”功能的荣誉和 +1。在一个简单的存根中,您提供了所需的名称、所需的参数(输入)和一些(迭代)代码来描述结果(输出)。

标签: c++ loops recursion nested


【解决方案1】:

如果我错了,请纠正我,但我认为这是将所有内容打包到一个递归函数中的“最简单”方式......它非常丑陋(嵌套的 for 循环更好更容易)但如果你真的想要递归,这将完成工作。

我通过思考嵌套 for 循环的作用得出了这个结论——它迭代 y 次,递减 x,将 y 重置为其原始值,然后迭代 y 次,递减 @987654325 @,重置y .......直到x == 0。这个递归函数使用类似的思维方式,它调用自己,每次递减y,一旦y == 1,它递减x,并重置y(使用start_y)。一旦x == 1 它返回true,然后所有调用者也返回true,直到它返回main()

通读代码中的cmets,看看你是否明白。我对此并不感到自豪,但使用递归函数制作嵌套循环并不是很漂亮。这就是嵌套循环存在的原因......

void recursive(int x, int y, int start_y)
{
    cout << x << " , " << y << endl; 

    // calls itself until y = 1, then goes on to if(x>1), decrementing x
    if (y > 1)                              
    {
            recursive(x, --y, start_y);
            return;
    }

    // calls itself and sets y = starting value of y (the userinput) 
    // this has the same effect as completing the inner for loop, then 
    // re-running it with --x
    if (x > 1)
    {
        recursive(--x, start_y, start_y);  // resets y value to start_y. if this is true, it's a signal that the function is done, so return true. 
        return;
    }
}


int main()
{
    int x, y;
    cout << "Enter 2 numbers:\n";
    cin >> x >> y;

    // inner loop -- I recommend this method... :-)
    for_loop(x, y);

    cout << endl; 

    // using recursion -- I don't recommend this method... :-(
    recursive(x, y, y); 

    return 0;
}

【讨论】:

  • 我同意你的观点,嵌套循环更好更容易。但是将其转换为递归的目的是让我可以变得更好并使用它。您的解决方案效果很好。最好的问候。
  • @DOUGLASO.MOEN,谢谢你 - 我不知道我在想什么:-)。我修复了它,使它现在只返回任何内容。
【解决方案2】:
void recursive(int x, int y, int temp)
{
  if(x > 0) {
    if(y > 0) {
      cout << x << " " << y << endl;
      recursive(x,y-1,temp);
    }
    else {
      y = temp;
      recursive(x-1,y,temp);
    }
  }
}

到目前为止,我找到了最好的解决方案,但它需要额外的变量才能让 y 回到它的原始值。您必须通过recursive(x,y,y); 调用它

【讨论】:

  • 是的,它是一个优雅而简单的解决方案,我忽略了需要一个“开始”变量的事实。最好的问候。
【解决方案3】:

如果允许多个递归函数,则为每个循环级别使用一个。这将递归深度限制为 x+y 实例而不是 x*y 实例。

#include <iostream>

using namespace std;

// inner loop    
void recursivey(int x, int y)
{
    if(y <= 0)
        return;
    cout << x << " , " << y << endl;
    recursivey(x, y-1);
}

// outer loop    
void recursivex(int x, int y)
{
    if(x <= 0)
        return;
    recursivey(x, y);
    recursivex(x-1, y);
}

int main()
{
    int x, y;
    cout << "Enter 2 numbers:\n";
    cin >> x >> y;
    recursivex(x, y);
    return 0;
}

【讨论】:

  • “这将递归深度限制为 x+y 实例而不是 x*y 实例。” 技术上是正确的,但如果开启尾递归优化,则不会差别很大。
【解决方案4】:

我正在寻找一个简单的例子来说明如何转换这个基本的嵌套 循环到递归函数

'简单'的例子,当然。 (为什么不呢?)

我认为之前提交的所有 3 个贡献都可以。我相信我已经在我的系统上编译并运行了它们中的每一个。

向 rcgldr 致敬 - 该提交文件在实现尾递归方面脱颖而出。 (我没有在其他人身上确认这个问题。)

幻数:

for 循环有 3 个“参数”:开始、停止和步幅

 for (int v = start; v < stop; v += stride)

因为 for 循环具有严格的布局,所以可以容忍幻数,并且 也许是想要的。恕我直言,您可以使用 2 个幻数(0 和 -1)。

在具有更灵活布局(其他循环类型、递归等)的代码中,不鼓励使用这种幻数。

可测试性:

  • 存根“报告”的垂直格式并不容易 人工检查/比较。考虑“水平输出”显示 (提出了一种替代方案。)

  • 在性能测量期间需要抑制任何文本输出。 (介绍了一种技术)

C 风格与 C++ 风格

  • 到目前为止,只提交了 C 风格的函数。
  • 如果您的 C++ 代码没有类,何必呢?

我的环境

  • Ubuntu 15.10,g++-5,GNU Emacs 26,性能测量使用 -O3 (调试使用-O0)

  • 我的构建选项:

    g++-5 -m64 -O3 -ggdb -std=c++14 -Wall -Wextra -Wshadow -Wnon-virtual-dtor -pedantic -Wcast-align -Wcast-qual -Wconversion -Wpointer-arith --Wunused Woverloaded-virtual -O3 dumy542.cc -o dumy542

    代码文件名“dumy542.cc”

总结

  • 在提交的代码中,2种递归形式的表现与迭代形式相同。 (尾递归)

  • 我已确认尾递归 - 两个递归代码都运行“深度”且没有堆栈溢出。 Ubuntu 15.10 默认的“堆栈”大小为 8 MB。 Demo 使用 1200 万递归深度。使用 -O0 构建代码时,代码堆栈溢出。 (SO) -O3 时,没有 SO。

感兴趣的演示

  • 任何第三个 cmd-line-param(即 'm')的存在都会启用文本输出

    ./dumy542 3 15 m - 运行 3 x 15,报告持续时间, 和3行文本输出

    ./dumy542 3 60 m - 适合我的全屏

注意 - 尝试使用“大”输出启用测试输出将触发断言,以方便您恢复。 (您可以删除它)

  • 只有 1 或 2 个命令行参数,文本输出被抑制

    ./dumy542 3 12345678 - 运行每个测试 3 倍的 12+ 百万


第一部分代码介绍了迭代代码和 2 个尾递归代码示例。所有都基于提交的代码,我已经重构了 使用 ssDiag(支持抑制文本输出)和更“水平”的显示技术。

注意 - 已添加断言(在这些示例中)以防止编译器优化代码。

测试包装(使用 C++ 计时测量)如下。

#include <chrono>
// 'compressed' chrono access --------------vvvvvvv
typedef std::chrono::high_resolution_clock  HRClk_t; // std-chrono-hi-res-clk
typedef HRClk_t::time_point                 Time_t;  // std-chrono-hi-res-clk-time-point
typedef std::chrono::nanoseconds            NS_t;    // std-chrono-nanoseconds
using   namespace std::chrono_literals;      // support suffixes like 100ms, 2s, 30us

#include <iostream>
#include <iomanip>
#include <sstream>
#include <string>
#include <cassert>

uint64_t Kount = 1; // diagnostic only


// C style C++ function by MGT
// refactored (by DMoen) to use ssDiag and 'horizontal' display
void iterate_MGT(int x, int y, std::stringstream* ssDiag )
{
   for (int i = x; i > 0; i--) // magic numbers xStop (0) & xStride (-1)
   {
      if(nullptr != ssDiag) { (*ssDiag) << "\n  i=" << i << ": "; }
      else { assert(++Kount); } // with side-effects

      for (int j = y; j > 0; j--) // magic numbers yStop (0) & yStride (-1)
      {
         if(nullptr != ssDiag) { (*ssDiag) << j << "  "; }
         else { assert(++Kount); }  // with side-effects
      }
   }
}


// C style C++ function(s) by 'rcgldr'
// refactored (by DMoen) to use ssDiag and 'horizontal' display
void recurseY(int x, int y, std::stringstream* ssDiag)
{
   if(y <= 0) return; // magic number yStop (0)

   if(nullptr != ssDiag) { (*ssDiag) << y << "  " ; }
   else                  { assert(++Kount);    }  // with side-effects

   recurseY(x, y-1, ssDiag);  // magic number y-stride (-1)
}

void recurseX(int x, int y, std::stringstream* ssDiag)
{
   if(x <= 0) return; // magic number xStop (0)

   if(nullptr != ssDiag) { (*ssDiag) << "\n  i=" << x << ":  " ; }
   else                  { assert(++Kount);    }  // with side-effects

   recurseY (x,   y, ssDiag);
   recurseX (x-1, y, ssDiag); // magic number x-stride (-1)
}



// C++ class - created from tail recursive code (submitted by 'rcgldr')
//  by DMoen to use ssDiag and 'horizontal' display
class Recursive_t
{
public:
   static void recurse (int x, int y, std::stringstream* ssDiag)
      {
         Recursive_t rcgldr(x, y, ssDiag); // ctor
         rcgldr.recurseX();                // go
      }                                    // dtor on exit

private:
   int                m_x;
   int                m_y;
   int                m_yStart;
   std::stringstream* m_ssDiag;

   enum Constraints : int  // no need for magic numbers
   {
      xStop = 0, xStride = -1,
      yStop = 0, yStride = -1,
      ConstraintsEnd
   };

   Recursive_t () = delete;  // disallow default ctor

   Recursive_t (int xStart, int yStart, std::stringstream* ssDiag)
      : m_x      (xStart)
      , m_y      (yStart)
      , m_yStart (yStart)
      , m_ssDiag (ssDiag)
      {
      }

   void recurseX() // outer loop
      {
         if(m_x <= xStop)  return;

         if(nullptr != m_ssDiag) { (*m_ssDiag) << "\n  i=" << m_x << ":  " ; }
         else                    { assert(++Kount); } // with side-effects

         recurseY();
         m_x += xStride;
         recurseX();
      }

   void recurseY()  // inner loop
      {
         if(m_y <= yStop) { m_y = m_yStart; return; }

         if(nullptr != m_ssDiag) { (*m_ssDiag) << m_y << "  " ; }
         else                    { assert(++Kount); } // with side-effects

         m_y += yStride;
         recurseY();
      }

}; // class Recursive_t

测试代码和main:

class T542_t
{
   int               m_xStart;
   int               m_yStart;

   std::string        m_rslt;
   std::stringstream* m_ssRslt;

   std::string        m_diag;
   std::stringstream* m_ssDiag;

   const uint         m_capacity;

public:


   T542_t()
      : m_xStart(0)
      , m_yStart(0)
        //
        // m_rslt - default std::string ctor ok
      , m_ssRslt (nullptr)
        // m_diag - default std::string ctor ok
      , m_ssDiag (nullptr)
      , m_capacity (1000)
      {
         m_rslt.reserve(m_capacity);
         m_ssRslt = new std::stringstream(m_rslt); // always
         //
         m_diag.reserve(m_capacity);
         // m_ssDiag only when needed

         resetTest();

         m_rslt.clear();
         assert(m_rslt.capacity() == m_capacity);
         assert(m_rslt.empty());
         assert(m_rslt.size() == 0);
      }

   ~T542_t() = default;


   int exec (int argc, char* argv[])
      {
         switch (argc)
         {

         case 2: // one parameter
         {
            m_xStart = std::stoi(std::string(argv[1]));
            m_yStart = m_xStart;
            return (test("\n  one command line parameter 'N':  duration rslts only"));   // NOTE 1
         }
         break;

         case 3: // two parameters
         {
            m_xStart = std::stoi(std::string(argv[1]));
            m_yStart = std::stoi(std::string(argv[2]));
            return (test("\n  two command line parameters, X & Y: duration rslts only"));   // NOTE 1
         } break;

         case 4: // three parameters
         {
            m_xStart = std::stoi(std::string(argv[1]));
            m_yStart = std::stoi(std::string(argv[2]));

            m_diag.clear();
            assert(m_diag.capacity() == m_capacity);

            m_ssDiag = new std::stringstream(m_diag);

            return (test("\n  three command line parameters, X & Y: duration + intermediate results"));   // NOTE 1
         } break;

         case   1:
         default :
         {
            std::cerr
               << "\n  Usage: "
               << "   argc (" << (argc-1) << ") not expected, use 1, 2, or 3 cmd line parameters.\n"
               //
               << "\n     2  (one parameter)    : test with NxN, show duration only\n"
               << "\n     3  (two parameters)   : test with MxN, show duration only\n"
               //
               << "\n     5  (three parameters) : test with MxN, show intermediate results\n"
               //
               << "\n   other                   : Usage\n"
               << std::endl;
            return 0;
         }

         } // switch

         assert(m_xStart);
         assert(m_yStart);


      } // exec (int argc, char* argv[])

private:

   int test(std::string note)
      {
         int64_t a_lim = m_xStart * m_yStart;
         assert(a_lim > 0); // wrap around possible: example:  3 x 1234567890
         std::cout << note << ",  Using  " << m_xStart << " & " << m_yStart
                   << "  (" << digiComma(std::to_string(a_lim))
                   << ")\n" << std::endl;

         if(nullptr != m_ssDiag) { // when displaying output, make sure
            assert (m_yStart <= 80);                // short line and small total
            assert((m_xStart * m_yStart) <= 2000);
         }

         if(true) // for consistent start up - ubuntu app 'load' delay? cache flush?
         {
            assert(nullptr != m_ssRslt);
            (*m_ssRslt) << "\n  \n  ::xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx ";
            testRecurse2  (); testRecurse1  (); testIterate ();
            resetTest(); // discard results
         }

         testIterate (); showTest();  resetTest();
         testRecurse1 ();  showTest();  resetTest();
         testRecurse2 ();  showTest();  resetTest();

         return 0;
      } // int test()


   void showTest()
      {
         std::cout << m_ssRslt->str() << std::endl;
         if(nullptr != m_ssDiag)
            std::cout << m_ssDiag->str() << std::endl;
      }


   void testIterate()  // C style C++ function, submitted by MGT, iterative function
      {
         assert(nullptr != m_ssRslt);
         (*m_ssRslt) << "\n  (C style C++ function)\n  ::iterate_MGT (int, int, ss&)             ";

         Time_t start = HRClk_t::now();

           ::iterate_MGT (m_xStart, m_yStart, m_ssDiag); // invoke the C++ function
         //^^ --- file scope

         auto  duration_ns = std::chrono::duration_cast<NS_t>(HRClk_t::now() - start);
         (*m_ssRslt) << "    duration:  " << std::setw(10)
                     << digiComma(std::to_string(duration_ns.count())) << " ns ";
      } // testIterate()



   void testRecurse1 () // C style C++ function, submitted by rcgldr, tail-recursive
      {
         assert(nullptr != m_ssRslt);
         (*m_ssRslt) << "\n  (C style C++ function)\n  ::recurseX (int, int, ss&)                 ";

         Time_t start = HRClk_t::now();

           ::recurseX(m_xStart, m_yStart, m_ssDiag);
         //^^ --- file scope

         auto  duration_ns = std::chrono::duration_cast<NS_t>(HRClk_t::now() - start);
         (*m_ssRslt) << "   duration:  " << std::setw(10)
                     << digiComma(std::to_string(duration_ns.count())) << " ns ";
      } // void testRecurse1 ()



   void testRecurse2 () // C++ class, tail-recursive, method
      {
         assert(nullptr != m_ssRslt);
         (*m_ssRslt) << "\n  (C++ class method)\n    Recursive_t::recurse (int, int, ss&)     ";

         Time_t start = HRClk_t::now();

           Recursive_t::recurse(m_xStart, m_yStart, m_ssDiag);
           //^^^^^^^^^ class scope

         auto  duration_ns = std::chrono::duration_cast<NS_t>(HRClk_t::now() - start);
         (*m_ssRslt) << "   duration:  " << std::setw(10)
                     << digiComma(std::to_string(duration_ns.count())) << " ns ";
      } // void testRecurse2 ()



   void resetTest() { resetRslt(); resetDiag(); Kount = 1; }

   void resetRslt() { m_rslt.clear(); // clear bufer
      if (nullptr != m_ssRslt) { m_ssRslt->str(m_rslt); m_ssRslt->clear(); } }

   void resetDiag() { m_diag.clear(); // clear buffer
      if (nullptr != m_ssDiag) { m_ssDiag->str(m_diag); m_ssDiag->clear(); } }


   // convenience - insert comma's into big numbers for readability
   std::string digiComma(std::string s)
      {  //vvvvv--sSize must be signed int of sufficient size
         int32_t sSize = static_cast<int32_t>(s.size());
         if (sSize > 3)
            for (int32_t indx = (sSize - 3); indx > 0; indx -= 3)
               s.insert(static_cast<size_t>(indx), 1, ',');
         return(s);
      }

}; // class T542_t

int main(int argc, char* argv[])
{
   T542_t  t542;
   return  t542.exec(argc, argv);
}

示例结果


  • 命令行:./dumy542 3 15 m

    三个命令行参数,X&Y:持续时间+中间结果,使用3&15(45)

    (C 风格 C++ 函数) ::iterate_MGT (int, int, ss&) 持续时间:6,319 ns

    i=3:15 14 13 12 11 10 9 8 7 6 5 4 3 2 1
    我=2:15 14 13 12 11 10 9 8 7 6 5 4 3 2 1
    我=1:15 14 13 12 11 10 9 8 7 6 5 4 3 2 1

    (C 风格 C++ 函数) ::recurseX (int, int, ss&) 持续时间:6,639 ns

    i=3:15 14 13 12 11 10 9 8 7 6 5 4 3 2 1
    我=2:15 14 13 12 11 10 9 8 7 6 5 4 3 2 1
    我=1:15 14 13 12 11 10 9 8 7 6 5 4 3 2 1

    (C++ 类方法) Recursive_t::recurse (int, int, ss&) 持续时间:6,480 ns

    i=3:15 14 13 12 11 10 9 8 7 6 5 4 3 2 1
    我=2:15 14 13 12 11 10 9 8 7 6 5 4 3 2 1
    我=1:15 14 13 12 11 10 9 8 7 6 5 4 3 2 1


  • 命令行:./dumy542 3 12345678

    两个命令行参数,X & Y:duration rslts only,使用 3 & 12345678 (37,037,034)

    (C 风格 C++ 函数) ::iterate_MGT (int, int, ss&) 持续时间:46,369,155 ns

    (C 风格 C++ 函数) ::recurseX (int, int, ss) 持续时间:44,099,411 ns

    (C++ 类方法) Recursive_t::recurse (int, int, ss&) 持续时间:42,898,124 ns

时长说明

  • 多次运行有不同的结果,通常会改变哪个函数出现得更快。这个特定的结果表明两种递归都比迭代快。

【讨论】:

  • 关于持续时间——想象一下将 1000 多个持续时间捕获到一个向量中,然后对它们进行排序。我们没有对结果进行平均,而是简单地报告每个结果的最快时间,因为最小的持续时间不能变得更小。我已经完成了这项工作……非常稳定和可重复的测量。 “最快的持续时间(强度):迭代:4,272,835 (8) recurseX: 4,272,795 (2) recurse: 4,272,795 (4)。”即每个实现的最快时间。总运行时间 13 秒,3 x 1234567 (3,703,701) 增量,*1000+ 次运行。
【解决方案5】:

快速回答:

void createTraining_2(int x, int y, int z) // 3d recursive for loop
{
    static int start_y = y;
    static int start_z = z;

    if (x < y && x != y && y != z && z != x)
    {
        // yourFunction();
    }

    if (z > 0)
    {
        createTraining_2(x, y, --z);
        return;
    }

    if (y > 0)
    {
        createTraining_2(x, --y, start_z);
        return;
    }

    if (x > 0)
    {
        createTraining_2(--x, start_y, start_z);
        return;
    }
}

【讨论】:

    【解决方案6】:

    快速回答:

    void n_for_loop(int x, int size, int arr[])
    {
        static int* arrTemp(new int[size]);
        if (x >= size)
        {
            for (int i = 0; i < size; i++)
            {
                cout << arrTemp[i] << " ";
            }
            cout << endl;
            return;
        }
        for (int i = 0; i < arr[x]; i++)
        {
            arrTemp[x] = i;
            n_for_loop(x + 1, size, arr);
        }
    }
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2013-11-29
      • 2019-04-14
      • 1970-01-01
      • 1970-01-01
      • 2013-01-04
      • 2014-09-14
      • 2016-04-24
      • 2013-07-20
      相关资源
      最近更新 更多