最近一直在学习Deep Frist Search,也在leetcode上练习了不少题目。从最开始的懵懂,到现在遇到问题基本有了思路。依然清晰的记得今年2月份刚开始刷题的时做subsets的那个吃力劲,脑子就是转不过来到底该如何的递归,甚至试过使用debugger一步步的来看看堆栈到底是如何调用和返回的。经过了几个月的训练后,答题有了一些心得,记录下来,作为总结。

  递归函数的整体结构:

  返回值。进入入递归函数的第一件事就是写出递归终止的条件。通常递归终止的条件分为两类:

  (1)有一个深度的标准,例如N-Queen问题,当我们递归的检测完第N个皇后,就到达来递归返回的条件,此时,将此次递归的结果作为一个解决方案存储。

  (2)到达一个序列(数组/字符串)vector/string.size()的位置,例如subsets,permutation和word break ii的问题,当传递的参数已经无法索引数组/字符串的值时退出递归。

  是否进行prune的操作。这个操作并不是一定会存在的。当需要prune的时候,通常是遇到了效率问题。当输入问题的规模非常大,存储之前已经做过检测或者得出结果的递归位置会大大降低递归的工作量。这个时候我们通常会利用一些数据结构。最简单的就是一个数组,复杂一点的会是一个hash table(word break ii in leetcode)。

  本层的递归处理以及下一层次的递归调用。这一部分是递归的核心,如果这一部分模型处理正确,递归的调用就成功了90%(很像如果能写出动态规划的状态方程的感觉)。

  返回值。最后的return,连同递归的处理和调用,具体内容会这下面详细阐述。

  递归函数的类型:

  总的来说DFS的函数分为两种类型,有返回值和没有返回值。递归的层次都是通过一个参数传递到函数内部,这两种函数的编写有区别,虽然都是DFS,但思维方式却是不一样。

  对于无返回值的DFS函数,通常我们是将需要获取的值作为引用类型的参数传递到函数内部,当到达DFS函数终止的条件时我们获取想要得到的值,例如N-Queen,subsets, permutations等经典的问题。这是一种从上向下的思维模式。这种函数适用于求所有可能的解的问题。通常是先对该层的问题进行处理,然后再进入下一个层次的递归。当到达递归终止的条件时存储结果。然后再逐层的返回,通常会恢复该层这遍历之前的状态,为的是下一个递归。

以下为这种递归的模板:

void DFS_no_return(vector<TYPE>& solution_set, TYPE& solution, int idx){
// DFS terminate condition
// Normally there are two major categories
// One is for some number, already reach this depth according to the question, stop
// get to the end of an array, stop
if(idx == n || idx == (vector/string).size()){
solution_set.push_back(solution);
return;
}
// very seldom only when our program met some efficiency problem
// do prune to reduce the workload
// no need do DFS if we already get the value of current element, just return
if(hash_table[input] != hash_table.end()){
return;
} for(int i = ; i < n; i++){
if(visited[i] == ){
      // do something for current level process
visited[i] = ;
// for next level DFS
DFS_no_return(solution_set, solution, idx + );
// return from lower level DFS and restore the status for next DFS
visited[i] = ;
}
}
return;
}

对于有返回值的DFS来说,我们需要获得的值是递归函数的返回值。与无返回值的递归函数相比,最大的一个区别是该类递归函数在递归调用返回之后处理该层的递归值。将从低层次递归返回的值结合本层的值处理,然后再返回给上一层递归函数调用。这种问题的思路是由上到下,再由下向上。通常是将一个复杂的问题逐层的分解成小问题,等到无法再分则返回,再将小问题拼装,然后逐层向上,返回大问题作为问题的解。通常在使用分治的思想时会使用有返回值的深度递归函数,这个应用在树中非常普遍。

以下为有返回值的递归函数的模板:

TYPE DFS_with_return(int idx){
// DFS terminate condition
// Normally there are two major categories
// One is for some number, already reach this depth according to the question, stop
// get to the end of an array, stop
if(idx == n || idx == (vector/string).size()){
return //"";
}
// very seldom only when our program met some efficiency problem
// do prune to reduce the workload
// no need do DFS if we already get the value of current element, just return
if(hash_table[input] != hash_table.end()){
return hash_table[input];
} TYPE ans;
for(int i = ; i < n; i++){
if(visited[i] == ){
// normally, here should to add/concatenate the value in current level with the value returned from lower level
ans += DFS_no_return(idx + );
}
}
// if we need prune
// keep current value here
hash_table[input] = ans;
return ans;
}

总结:

  1. 递归函数的整体构造:
  2. 递归函数的分类:
    • 无返回值:适用于求所有可能的解。

      • 是一种由上而下,将大问题分解,在每一层提取出需要处理的小问题,然后向下递归,遇到递归终止的条件时,就得到一个问题的解,此时返回。
    • 有返回值:使用与求个数/数量的问题。
      • 是一种从上向下,将大问题分解到无法再分,然后再将分解后的小问题返回,逐层进行加工处理,然后再返回给上一层调用。

DFS template and summary的更多相关文章

  1. Binary Search Tree DFS Template

    Two methods: 1. Traverse 2. Divide & Conquer // Traverse: usually do not have return value publi ...

  2. C#设计模式系列:模板方法模式(Template Method)

    你去银行取款的时候,银行会给你一张取款单,这张取款单就是一个模板,它把公共的内容提取到模板中,只留下部分让用户来填写.在软件系统中,将多个类的共有内容提取到一个模板中的思想便是模板方法模式的思想. 模 ...

  3. 设计模式系列13:模板方法模式(Template Method Pattern)

    定义 定义一个操作中的算法的骨架,而将一些步骤延迟到子类中.模板方法使得子类可以不改变一个算法的结构即可重定义该算法的某些特定步骤.    --<设计模式GoF> UML类图 使用场景 有 ...

  4. 【DP】【CF31E】 TV Game

    传送门 Description 给你一个长度为\(2n\)的数字,每次可以从左侧选一个数字,加入连接到一个数字\(A\)或另一个数字\(B\)后面.\(A,B\)初始为\(0\).\(A\)与\(B\ ...

  5. t4 加载文件到解决方案

    Use EnvDTE add/remove multiple files to project By admin | décembre 17, 2013 Un commentaire Project ...

  6. winrt 上的翻书特效组件 源码分享 转载请说明

    http://blog.csdn.net/wangrenzhu2011/article/details/10207413 (转) [TemplatePart(Name = A_PARTNAME, Ty ...

  7. Jmeter html 报告中添加90% line time

    转载南风_real博客园:http://www.cnblogs.com/jaychang/p/5784882.html 首先上效果图: 其次明白几个原理: 90% Line的意思是:一组数由小到大进行 ...

  8. C# 使用C/S模式操作小票机打印

    此方式适用于市场上大多数的小票机 佳博.POS58 等,不适用于有些标签打印机 比如斑马打印机等 直接贴代码: private FileStream fs = null; [DllImport(&qu ...

  9. 【JMeter】ant+jmeter生成html报告

    源博文来自于  http://my.oschina.net/hellotest/blog/517518 主要应用于接口的回归或者性能的简单查看功能.操作为先在jmeter中写好测试计划,保存为jmx文 ...

随机推荐

  1. foreach的学习使用归纳

    1: 实现对双向链表的遍历使用 LinkedList<string> llary = new LinkedList<string>(); llary.AddLast (&quo ...

  2. C#委托的用法 在C#中我想在一个方法中调用另一个按钮的事件,怎样来实现?

    最开始我也不清楚,后来我是这样想了. 1.事件和委托不是一个概念,你如果是调用control的事件,可以直接在其对应的事件eventhandler上attach自己的事件方法就好了如:this.But ...

  3. 动态添加出来append的元素加事件

    $("body").on("click","#box span",function(){里面执行的东西}) span  是动态添加出来的

  4. 技术杂记之:vi使用入门

    对于Linux的初次使用者来说,进入Linux非图形界面后,不知道怎么创建文本(甚至于在图形界面,也找不到创建文本的菜单).其实,每一个Linux的发行版本,都包含了一个最简单.也是最基础的文本编辑器 ...

  5. Linux下Eclipse连接小米手机真机调试

    以前用Ubuntu 12.04可以真机调试,连上手机就可以了,上次看pear os 好看于是下了个玩玩(界面风格像mac 买不起,仿得起),这次想开发安卓发现真机调试不了了...于是乎各种找资料,各种 ...

  6. js阻塞ui进程涉及的知识点整理

    项目进行中遇到了同步ajax阻塞ui线程阻塞的问题,原因是执行两个同步ajax请求为一次完整的方法,因业务需求需要循环执行这个方法,检查后台返回的数据正确,但是由于ajax请求时间过长,考虑增加遮罩层 ...

  7. Node.js——优先从缓存加载

    main中执行require操作,目的是获取接口对象,所以多次引用b,并不会重复执行模块内部的输入输出,因为缓存中已经存在

  8. ButterKnife 在父类 点击事件没反应的解决方案

    在用继承的方式实现butterKnife的封装的时候遇到问题, butterKnife就在baseActivity中绑定的,但是父类中公共控件点击事件无效.找了半天原因,原来是子类和父类定义的点击方法 ...

  9. H3C AR28-31路由器组网实验

    接线图 可以发现PC1和PC2不在一个网段上,如果不靠路由器就不可能ping,所以要用路由器组网 接线步骤 串行线连接路由器1与路由器2 以太网线连路由器以太网口 与 交换机接口 计算机网线连交换机口 ...

  10. Windows live writer 2012 测试

    升级到win10,居然Windows live writer不能用了,装了好久就是装不上去,wlsetup-web.exe 在线安装失败,wlsetup-all.exe离线安装也失败了. 安装Blog ...