遍历所有下一个可能的状态
产生下一个新的状态
dfs(下一个新的状态)
回到上一个状态
例如,一组简单有序数组a = [1,2,3,4,5],求出和为5的解,当我们累计1+2+3时,发现已经超过5,后面的元素4,5不再进行累计,这用到回溯的剪枝功能,是对搜索过程中的一种优化;
1.3 DFS的定义
深度优先搜索适用于遍历或图或搜索树的算法,DFS是一个不断探查和回溯的过程。在探查的每一步,算法都有一个当前的顶点。最初的当前顶点,作为起始顶点。每一步探查过程中,首先对当前顶点V进行访问,并将该点的访问标志visited[v] = true.接着在v的所有邻接顶点中找出未被标志的过一个点,将其作为下一步的探查的当前顶点,倘若当前顶点的所有邻接顶点都被标志过,则退回一步,将前一步所访问的顶点重新取出,作为探查的当前顶点,重复上述过程,直到最初指定起始顶点的所有邻接顶点都被访问为止。
从DFS定义中不难发现,深度优先搜索算法也用到回溯,DFS与回溯关键区别在于DFS是一般应用于树和图的结构,回溯算法应用范围更广不限于固定数据结构,DFS搜索过程记录图或树的搜索完整路径,而回溯有剪枝功能在求解过程中不保留树或图的完整路径。
DFS应用题例如,力扣上的岛屿问题求解。
1.4 动态规划定义
动态规划是将大问题划分为小问题进行解决,从而一步步获取最优解的处理算法。
适合于用动态规划求解的问题,经分解得到子问题往往不是互相独立的。即下一个子阶段的求解是建立在上一个子阶段的解的基础上,进行进一步的求解。
经典背包问题,最长回文子串以及搜索算法中常见的编辑距离问题可以用动态规划解决
以上列举的问题除了利用动态规划算法解决外,也可以使用DFS算法解决,DP算法主要在于难以构造设计,DFS相对更易理解。
二、例题分析讲解(C++)
例题1 对二叉树进行先序、中序和后序三种遍历,应用深度优先搜索树的算法,采用递归和非递归两种实现方式:
#include <iostream.h>
#include "stack.h"
template <class T>
struct BintreeNode
T data;
BintreeNode<T> *leftChild, *rightChild;
BintreeNode(): leftChild(NULL), rightChild(NULL) {}
BintreeNode(T x, BintreeNode<T> *l = NULL, BintreeNode<T> *r = NULL):data(x), leftChild(l), rightChild(r) {}
// 递归方式实现先序遍历
void PreOrder(BintreeNode<T> * subTree, void (* visit)(BintreeNode<T> *p)) {
if (subTree != NULL) {
visit(subTree);
PreOrder(subTree->leftChild, visit);
PreOrder(subTree->rightChild, visit);
// 递归方式实现中序遍历
void InOrder(BintreeNode<T> * subTree, void (* visit)(BintreeNode<T> *p)) {
if (subTree != NULL) {
InOrder(subTree->leftChild, visit);
visit(subTree);
InOrder(subTree->rightChild, visit);
// 递归方式实现后序遍历
void PostOrder(BintreeNode<T> *subTree, void (* visit)(BintreeNode<T> *p)) {
if (subTree != NULL) {
PostOrder(subTree->leftChild, visit);
PostOrder(subTree->rightChild, visit);
visit(subTree);
// 非递归算法利用栈实现先序遍历
void PreOrder(void (* visit)(BintreeNode<T> *p)) {
stack<BintreeNode<T> * > S;
BintreeNode<T> *p = root;
S.Push(NULL);
while (p != NULL)
visit(p);
if (p->rightChild != NULL) S.Push(p->rightChild);
if (p->leftChild != NULL) p = p->leftChild;
else S.Pop(p);
// 非递归算法利用栈实现中序遍历
void InOrder(void (* visit)(BintreeNode<T> *p)) {
stack<BintreeNode<T> *> S;
BintreeNode<T> *p = root;
while (p != NULL || !S.IsEmpty())
while (p != NULL) {
S.Push(p);
p = p->leftChild;
if (!S.IsEmpty()) {
S.Pop(p);
visit(p);
p = p->rightChild;
struct stkNode {
BintreeNode<T> *ptr;
enum tag {L, R};
stkNode(BintreeNode<T> *N = NULL): ptr(N), tag(L) {}
// 非递归算法利用栈实现后序遍历
void PostOrder(void (* visit)(BintreeNode<T> *P)) {
stack<stkNode<T>> S;
stkNode<T> w;
BintreeNode<T> *p = root;
while (!S.IsEmpty())
while (p != NULL)
w.ptr = p;
w.tag = L;
S.Push(w);
p = p->leftChild;
int continuel = 1;
while (continuel && !S.IsEmpty()) {
S.Pop(w);
p = w.ptr;
switch(w.tag) {
case L:
w.tag = R;
S.Push(w);
continuel = 0;
p = p->rightChild;
break;
case R:
visit(p);
break;
cout << endl;
例2 回溯算法应用场景排列组合,实例如下:
全排列 (DFS)
vector<vector<int>> permute(vector<int>& nums) {
int len = nums.size();
vector<vector<int>> res;
if (len == 0) {
return res;
vector<int> path;
vector<bool> used(len);
dfs(nums, len, 0, path, used, res);
return res;
void dfs(vector<int>& nums, int len, int deepth, vector<int> path, vector<bool> used, vector<vector<int>>& res) {
if (deepth == len) {
res.emplace_back(path);
return;
for (int i = 0; i < len; i++) {
if (used[i] == true) continue;
path.emplace_back(nums[i]);
used[i] = true;
dfs(nums, len, deepth + 1, path, used, res);
path.pop_back();
used[i] = false;
组合数之和(回溯)当target<0时,提前结束本次递归再回退
void dfs(vector<int>& candidates, int target, vector<vector<int>>& ans, vector<int> combine, int idx) {
if (target < 0) {
return;
if (target == 0) {
ans.emplace_back(combine);
return;
for (int idx = pos; idx < candidates.size(); idx++) {
combine.emplace_back(candidates[idx]);
dfs(candidates, target - candidates[idx], ans, combine, idx);//注意此时idx不变,允许重复数字
combine.pop_back();//对组合进行回溯
vector<vector<int>> combinationSum(vector<int>& candidates, int target) {
vector<vector<int>> ans;
vector<int> combine;
dfs(candidates, target, ans, combine, 0);
return ans;
动态规划例题:
int minDistance(string word1, string word2) {
int n = word1.length();
int m = word2.length();
if (m*n == 0) return m+n;
vector<vector<int>>dp(n+1,vector<int>(m+1));
for (int i = 0; i < n+1; i++) {
dp[i][0] = i;
for (int j = 0; j < m+1; j++) {
dp[0][j] = j;
for (int i = 1; i < n+1; i++) {
for (int j = 1; j < m+1; j++) {
int left = dp[i-1][j] + 1;
int right = dp[i][j-1] + 1;
int left_down = dp[i-1][j-1];
if (word1[i-1] != word2[j-1]) left_down += 1;
dp[i][j] = min(left, min(right, left_down));
return dp[n][m];
深度优先遍历和回溯算法相似之处在于通过递归方式实现dfs过程中用到回溯思想,dfs适用于完成的图或树形结构,回溯应用场景更广,如排列组合上以及dfs递归场景下都有回溯算法的身影,动态规划算法适用于有重叠子问题和最优子结构性质的问题,并且记录所有子问题的结果,因此动态规划方法所耗时间往往远少于朴素解法,在数学,计算机科学,生物信息等领域有着广泛的应用,将复杂问题简化成相对简单子问题,同时每个子问题相互依赖,即下一个子问题的解依赖于上一个子问题的求解结果,动态规划难点在于方案设计的构思,有些场景下,如背包问题回溯算法也可作为动态规划的降级解决方法。