Warning: file_get_contents(/data/phpspider/zhask/data//catemap/6/cplusplus/156.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
C++ 改进递归问题解决的合理方法是什么?_C++_Algorithm_Recursion - Fatal编程技术网

C++ 改进递归问题解决的合理方法是什么?

C++ 改进递归问题解决的合理方法是什么?,c++,algorithm,recursion,C++,Algorithm,Recursion,我喜欢在TopCoder网站上解决算法问题。我可以实现大多数基本的递归问题,如回溯、dfs。。。然而,每当我遇到一个复杂的递归时,它常常会花费我好几个小时。当我检查其他程序员的解决方案时,我为自己感到羞愧。我已经编程将近5年了。我可以看到其他编程技术的显著改进,如操作字符串、图形、GUI。。。但不是递归?有谁能分享一些如何处理递归问题的经验吗?谢谢 更新 我熟悉单元测试方法。甚至在我了解单元测试之前,我经常编写一些小测试函数来查看结果是否符合我的要求。当面对递归问题时,我自然会失去这种能力。我可

我喜欢在TopCoder网站上解决算法问题。我可以实现大多数基本的递归问题,如回溯、dfs。。。然而,每当我遇到一个复杂的递归时,它常常会花费我好几个小时。当我检查其他程序员的解决方案时,我为自己感到羞愧。我已经编程将近5年了。我可以看到其他编程技术的显著改进,如操作字符串、图形、GUI。。。但不是递归?有谁能分享一些如何处理递归问题的经验吗?谢谢

更新

我熟悉单元测试方法。甚至在我了解单元测试之前,我经常编写一些小测试函数来查看结果是否符合我的要求。当面对递归问题时,我自然会失去这种能力。我可以插入几个“cout”语句来查看当前结果,但是当调用嵌套得很深时,我就无法再跟踪它了。所以大多数时候,要么我先用铅笔和纸解决它,要么我完成了(不能用常规方法,比如把它分成小块)。我觉得递归必须作为一个整体工作

致以最诚挚的问候,

我发现铅笔和纸真的很方便。将问题分解成更小的块也是一个好主意,例如使用一个非常小的数据集。您应该做的第一件事是确定基本条件,即标记递归调用结束的条件。从那里,您可以处理递归问题的主体,并使用更大的数据集对其进行测试/验证


我还想补充一点,速度并不是成为一名优秀工程师的唯一条件。工程师还可以拥有许多其他技能,包括跳出框框进行观察和思考的能力、说服他人采取特定行动的能力、分解问题并向非专业人士(利益相关者和客户)解释问题的能力等等。

问题的哪些部分需要你花费数小时

你自己没有想到的其他程序员的解决方案呢


作为一条一般性建议,请记住考虑基本情况,然后记住您认为在递归的每个级别都必须保持的不变量。错误经常出现,因为在递归调用中未正确保留不变量。

动态编程有帮助。记忆也很有帮助。

我认为最好避免递归。在大多数情况下,循环更优雅,更容易理解。循环也更有效,长循环不会因堆栈溢出错误而使程序崩溃

我发现递归是最优雅的解决方案的问题很少。通常这类问题都是关于图形或曲面上的导航。幸运的是,这个领域已经被研究到了极点,所以你可以在网上找到很多算法


在包含不同类型节点的简单图(如树)中导航时,访问者模式通常比递归简单

这是一个很好的问题

我得到的最好答案是分解:分而治之。这是一个有点棘手的C++,因为它不支持高阶函数很好,但你可以做到。最常见的例程是贴图和折叠之类的。[C++已经有一个名为std::acculate]的余码

<>你必须仔细考虑的是如何在可能的情况下构造代码以提供尾部递归。人们很快就可以识别尾声,并将其视为循环,这大大减少了大脑因到处重复而产生的超负荷

另一个好方法叫做信任。这意味着,您可以编写一个对一个您甚至还没有定义的函数的调用,并且您相信它会做您想做的事情,而无需进一步的麻烦。例如,您相信它将自底向上正确访问树的节点,即使它必须调用您当前正在编写的函数。写评论,说明前置和后置条件是什么

另一种方式是(使用这个Oracl或Haskell)的实际编程语言,然后尝试把漂亮的干净代码翻译成C++。通过这种方式,您可以更轻松地查看结构,而不会陷入内务管理细节、难看的语法、缺乏本地化等问题。一旦你把它正确,你可以把它翻译成C++。(或者您可以使用Felix为您翻译)

我说对不起的原因是。。如果你这样做,你就不想再写C++了,这会让你很难找到满意的工作。例如,在Ocaml中,只需添加列表元素(不使用折叠):

这不是尾部递归,但这是:

let addup (ls: int list) : int = 
  let rec helper ls sum = match ls with
  | [] -> sum
  | h :: t -> helper t (h+ sum)
  in
helper ls 0

上述转变是众所周知的。当您了解第二个例程的作用时,它实际上更简单。我太懒了,不能把它翻译成C++,也许你可以把它转换成代码。(光是算法的结构就足以理解语法)

我曾经参加过一个为喜欢编程的疯狂青少年举办的夏令营。他们教我们解决问题的“法式方法”(内部参考)(递归和其他)

1) 用你的老板的话来定义你的问题,并做一些有用的例子

2)进行观察、考虑边缘情况、约束(如:“算法必须是最坏O(n log n)))

3) 决定如何解决这个问题:图论,动态规划(递归),组合经济学

从这里开始,递归特定:

4) 确定“子问题”,从约束中猜出可能有多少子问题,并利用这些子问题进行猜测,通常会有所帮助。最终,一个子问题会“点击”你的脑袋

5) 选择自下而上或自上而下的算法

6) 密码

在整个步骤中,在第6步之前,所有内容都应该用漂亮的笔写在纸上。在编程比赛中,那些立即开始攻丝的人往往表现不佳
let addup (ls: int list) : int = 
  let rec helper ls sum = match ls with
  | [] -> sum
  | h :: t -> helper t (h+ sum)
  in
helper ls 0
#include <iostream>
#include <functional>
#include <map>

#include <time.h>

//maahcros
#define TIME(__x) init=clock(); __x; final=clock()-init; std::cout << "time:"<<(double)final / ((double)CLOCKS_PER_SEC)<<std::endl;
#define TIME_INIT  clock_t init, final;
void sleep(unsigned int mseconds) { clock_t goal = mseconds + clock(); while (goal > clock()); }

//the original memoize
template <typename ReturnType, typename... Args>
std::function<ReturnType (Args...)> memoize(std::function<ReturnType (Args...)> func)
{
    std::map<std::tuple<Args...>, ReturnType> cache;
    return ([=](Args... args) mutable  {
        std::tuple<Args...> t(args...);
        if (cache.find(t) == cache.end()) {
            cache[t] = func(args...);
        }
        return cache[t];
    });
}

/// wrapped factorial
struct factorial_class {

    /// the original factorial renamed into _factorial
    int _factorial(int n) {
        if (n==0) return 1;
        else {
         std::cout<<" calculating factorial("<<n<<"-1)*"<<n<<std::endl;
         sleep(100);
         return factorial(n-1)*n;
        }
    }

    /// the trick function :)
    int factorial(int n) {
        if (memo) return (*memo)(n);
        return _factorial(n);
    }

    /// we're not a function, but a function object
    int operator()(int n) {
        return _factorial(n);
    }

    /// the trick holder
    std::function<int(int)>* memo;

    factorial_class() { memo=0; }
};

int main()
{
 TIME_INIT
    auto fact=factorial_class(); //virgin wrapper
    auto factorial = memoize( (std::function<int(int)>(fact) ) ); //memoize with the virgin wrapper copy
    fact.memo=&factorial; //spoilt wrapper
    factorial = memoize( (std::function<int(int)>(fact) ) ); //a new memoize with the spoilt wrapper copy

    TIME ( std::cout<<"factorial(3)="<<factorial(3)<<std::endl; ) // 3 calculations
    TIME ( std::cout<<"factorial(4)="<<factorial(4)<<std::endl; ) // 1 calculation
    TIME ( std::cout<<"factorial(6)="<<factorial(6)<<std::endl; ) // 2 calculations
    TIME ( std::cout<<"factorial(5)="<<factorial(5)<<std::endl; ) // 0 calculations

    TIME ( std::cout<<"factorial(12)="<<factorial(12)<<std::endl; )
    TIME ( std::cout<<"factorial(8)="<<factorial(8)<<std::endl;  )
    return 0;
}