Skip to content

Commit

Permalink
区分开动规和memorization,区分开递归和memorization
Browse files Browse the repository at this point in the history
  • Loading branch information
soulmachine committed Sep 13, 2013
1 parent 96b8d8a commit 7652f3b
Show file tree
Hide file tree
Showing 4 changed files with 24 additions and 8 deletions.
7 changes: 6 additions & 1 deletion C++/chapDFS.tex
Expand Up @@ -15,12 +15,17 @@ \section{深搜与递归的区别}

深搜,是逻辑意义上的算法,递归,是一种物理意义上的实现。深搜,可以用递归来实现,也可以用栈来实现;而递归,一般总是用来实现深搜。可以说,\textbf{递归一定是深搜,深搜不一定用递归}。

递归有两种加速策略,一种是\textbf{剪枝(prunning)},对中间结果进行判断,提前返回;一种是\textbf{加缓存(cache)},缓存中间结果,防止重复计算,用空间换时间,和动规中自顶向下的记忆化搜索类似。
递归有两种加速策略,一种是\textbf{剪枝(prunning)},对中间结果进行判断,提前返回;一种是\textbf{加缓存}(即memoization优化技术),缓存中间结果,防止重复计算,用空间换时间,和动规中自顶向下的记忆化搜索类似。

剪枝的手段五花八门,要具体问题具体分析,要充分观察,充分利用各种信息来剪枝,在中间节点提前返回。

加缓存,可以用数组或HashMap。维度简单的,用数组;维度复杂的,用HashMap,C++有\fn{std::map},C++ 11以后有\fn{std::unordered_map},比\fn{std::map}快。

其实,递归+缓存,就是一种 memorization 。所谓\textbf{memorization}(翻译为记忆化搜索,见第 \S \ref{sec:dp-vs-memorization}节),就是"top-down with cache"(自顶向下+缓存),它是Donald Michie 在1968年创造的术语,表示一种优化技术,在top-down 形式的程序中,使用缓存来避免重复计算,从而达到加速的目的。

\textbf{memorization不一定用递归},就像深搜不一定用递归一样,可以在迭代(iterative)中使用memorization。\textbf{递归也不一定用memorization},可以用memorization来加速,但不是必须的。只有当递归使用了缓存,它才是memorization。


\section{四色问题} %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%

\subsubsection{描述}
Expand Down
22 changes: 17 additions & 5 deletions C++/chapDynamicProgramming.tex
@@ -1,4 +1,6 @@
\chapter{动态规划}


如果一个问题具有以下两个要素:
\begindot
\item 最优子结构(optimal substructure)
Expand All @@ -18,8 +20,8 @@ \chapter{动态规划}
在第1步中,我们需要抽象出一个“状态”,在第2步中,我们要找出“状态转移方程”,然后才能
递归的定义最优解的值。第3步和第4步就是写代码实现了。

写代码实现时有两种方式,“递归(recursive)+自顶向下(top-down)+表格(memoization)”和
“自底向上(bottom-up)+表格”。自顶向下也称为记忆化搜索,自底向上也称为递推(不是递归)
写代码实现时有两种方式,“递归(recursive)+自顶向下(top-down)+表格”和
“自底向上(bottom-up)+表格”。前者属于一种 memorization (翻译为记忆化搜索),后者才是正宗的动规

动规用表格将各个子问题的最优解存起来,避免重复计算,是一种空间换时间。

Expand All @@ -31,6 +33,16 @@ \chapter{动态规划}
分治和贪心的相同点:disjoint subproblems。


\section{动规和记忆化搜索的区别} %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
\label{sec:dp-vs-memorization}

动规(dynamic programming)一定是自底向上的,记忆化搜索(memorization)一定是自顶向下的。

动规不是lazy的, memorization 是lazy的,是按需(on-demand)计算的。所以,如果所有的子问题至少会碰到一次,则动规有优势;如果有些子问题在搜索过程中不会被碰到(即有剪枝),则 memorization 有优势。更详细的解释请参考StackOverflow上的这个帖子 \myurl{http://t.cn/z80ZS6B} 。

记忆化搜索可以实现跟动规类似的功能,但它不是动规。两者的方向是反的,一个是自顶向下,一个自底向上,我们应该区分开这两个概念。本书后面提到的动规,都是指自底向上的动规。


\section{最长公共子序列} %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
\subsubsection{描述}
一个序列的子序列(subsequence)是指在该序列中删去若干(可以为0个)元素后得到的序列。
Expand Down Expand Up @@ -1203,7 +1215,7 @@ \subsubsection{代码}
int d[MAXN]; // 表格

/**
* @brief 动规,自顶向下.
* @brief 记忆化搜索.
* @param[in] i 起点
* @return 以i为起点,能达到的最长路径
*/
Expand Down Expand Up @@ -1439,7 +1451,7 @@ \subsubsection{分析}
价值均为1。求背包中物品的最小价值和最大价值。

\subsubsection{代码}
版本1,自顶向下
版本1,记忆化搜索

\begin{Codex}[label=coin_change.c]
#include<stdio.h>
Expand Down Expand Up @@ -2258,7 +2270,7 @@ \subsubsection{分析}
$$d[i][j]=a[i][j]+\max\left\{d[i+1][j], d[i+1][j+1]\right\}$$

\subsubsection{代码}
版本1,自顶向下
版本1,记忆化搜索

\begin{Codex}[label=numbers_triangle1.c]
#include<stdio.h>
Expand Down
Binary file modified C++/手写代码必备手册(C++版).pdf
Binary file not shown.
3 changes: 1 addition & 2 deletions Java/README.md
@@ -1,5 +1,4 @@
#Java版
-----------------
书的内容与C++版一摸一样,不过代码是用C++写的
书的内容与C++版一摸一样,不过代码是用Java写的。本书的代码要求 Java 6 以上

##更新记录

0 comments on commit 7652f3b

Please sign in to comment.