1. 二分搜索

详见笔者博文:二分搜索的那些事儿,非常全面

2. 矩阵二分搜索

(1) 矩阵每行递增,且下一行第一个元素大于上一个最后一个元素

(2) 矩阵每行递增,且每列也递增

3. DFS 深度优先搜索

适用场景:

(1) 输入数据:如果是 递归数据结构(如单链表、二叉树),则一定可以使用DFS

(2) 求解目标:必须走到最深处(例如二叉树,必须走到叶子节点)才能得到一个解,这种情况一般适合用DFS

思考步骤:

(1) DFS最常见的3个问题:求可行解的总数、求任一个可行解、求所有可行解

(a) 如果是 求可行解总数,则不需要 数组path[] 来存储 搜索路径

(b) 如果是 求可行解本身,则需要一个 数组path[] 来存储 搜索路径序列

DFS在搜索过程中 始终只有一条搜索路径,一直搜索到绝境再回溯继续搜索,因此只需要一个数组就可以了

BFS需要存储 扩展过程中的搜索路径,在没有找到答案之前 所有路径都不能放弃

(2) 只要求任一可行解? 要求所有可行解?

如果只需要一个可行解,找到一个即可返回

如果要求所有可行解,找到一个可行解之后,必须继续扩展,直到遍历完

BFS一般只要求一个解,如果用BFS要求所有解,就需要扩展到所有叶子节点,相当于在内存中有指数级的存储空间

(3) 如何表示状态?

一个状态需要存储哪些必要的信息,才能够正确的扩展到下一步状态.

DFS一般使用函数参数的方法,因为DFS一般有递归操作,扩展下一状态只需要修改 递归函数的函数参数即可

BFS一般使用struct结构体存储所有信息,struct里的字段与DFS中的函数参数字段一一对应

(4) 如何扩展状态?

对于二叉树:扩展左子树、右子树

对于图、矩阵:题目告知,比如 只能向右或向下走, 比如 上下左右四个方向均可扩展

(5) 如何判重?

(a) 是否需要判重?

如果 状态转换图是 一棵树,则不需要判重,树的所有子树均分离,不存在重叠子问题,因此二叉树的所有DFS都不需要判重

如果 状态转换图是 DAG(有向无环图),则需要判重,因此 所有的BFS都需要判重

(b) 怎样判重?

(6) 搜索的终止条件是什么?

终止条件是 不能继续扩展的末端结点

对于树:叶子节点

对于图:出度为0的节点

(7) 收敛条件是什么?

为了判断是否到达收敛条件,DFS一般需要在递归函数接口里 用一个参数记录当前状态(cur变量) 或者 距离目标还有多远(gap变量)

如果是 求一个解,直接返回这个解,即path路径数组

如果是 求所有解,则把 这个解path数组复制到 解集合中 (一般利用 c++ vector中的push_back函数,push时采用的是copy构造函数)

(8) 如何加速?

(a) 剪枝:图的DFS中需要挖掘各种信息,包括搜索边界、值大小关系等

(b) 缓存:状态转换图是DAG ==> 存在重叠子问题 ==> 字问题的解会被重复利用

如果输入结构是 二叉树,不存在重叠子问题,不需要缓存

一般使用c++11的 std::unordered_map来缓存,或者使用一个二维数组 std::vector<std::vector<int>>

DFS模板:

数据结构为树(二叉树)的DFS模板(不需要判重和缓存):

/** DFS模板
* @param[in] input :对于二叉树一般为root指针,对于图一般是输入矩阵即二维数组
* @param[in] path:当前搜索路径,也是中间结果,一般为一维vector
* @param[in] cur or gap:标记当前位置或距离目标的距离
* @param[out] result:存放最终结果,一般是二维vector,每一维为path数组
*/ void dfs(type input, std::vector<int>& path, int cur or gap, std::vector<std::vector<int>>& result) {
if (数据非法) return; // 终止条件,对于二叉树即input为空,对于图即 搜索边界越界
if (cur == input.size() or gap == ) { // 收敛条件
result.push_back(path);
}
// 执行所有扩展路径
path.push_back(); // 执行动作,修改path
// 扩展动作一般有多个,对于二叉树就是 input->left和input->right,对于图可能就是 y-1,y+1,x-1,x+1(上下左右)
dfs(input, path, cur + or gap - , result);
path.pop_back(); // 恢复path
}

例题: 二叉树路径和问题

数据结构为图的DFS模板(需要判重):

/** DFS模板
* @param[in] input :对于二叉树一般为root指针,对于图一般是输入矩阵即二维数组
* @param[in] path:当前搜索路径,也是中间结果,一般为一维vector
* @param[in] cur or gap:标记当前位置或距离目标的距离
* @param[in] visited:用于判重的二维数组
* @param[out] result:存放最终结果,一般是二维vector,每一维为path数组
*/ void dfs(type input, std::vector<int>& path, int cur or gap, std::vector<std::vector<bool>> visited, std::vector<std::vector<int>>& result) {
if (数据非法) return; // 终止条件,对于二叉树即input为空,对于图即 搜索边界越界
if (cur == input.size() or gap == ) { // 收敛条件
result.push_back(path);
}
if(visited[x][y] == true) return; // 判重
// 执行所有扩展路径
visited[x][y] = true;
path.push_back(); // 执行动作,修改path
// 扩展动作一般有多个,对于二叉树就是 input->left和input->right,对于图就是 y-1,y+1,x-1,x+1(上下左右)
dfs(input, path, cur + or gap - , visited, result);
path.pop(); // 恢复path
visited[x][y] = false;
}

例题:在字符矩阵中查找单词

矩阵右下走的路径总数(可能有障碍)

4. BFS 广度优先搜索

适用场景:求最短搜索路径

/** BFS模板
* param[in] state_t:状态,如整数、字符串、数组等
* param[in] start:起点
* parma[in] grid:输入矩阵数据
* return 从起点到目标状态的一条最短路径
*/ std::vector<state_t> bfs(state_t start, std::vector<std::vector<int>>& grid) {
std::queue<state_t> que; // 队列
std::unordered_set<state_t> visited; // 判重,也可以直接使用 二维vector bool found = false;
que.push(start);
visited.insert(start);
while (!que.empty()) {
state_t state = que.front();
que.pop();
if (state为目标状态) {
found = true;
break;
}
// 扩展状态,对于二叉树即左右子树,对于图可能就是上下左右四个方向
std::vector<state_t> stateVec = state_extend(state); // 扩展状态必须考虑 搜索边界越界时的剪枝visited的判重
for (auto iter = stateVec.begin(); iter != stateVec.end(); ++iter) {
state_t curState = *iter;
if (curState为目标状态) {
found = true;
break;
}
// curState不满足目标状态,则入队
que.push(curState);
visited.insert(start);
}
}
if (found) {
return generate vector<state_t>;
} else {
return vector<state_t>();
}
}

例题:二叉树层次遍历    单词接龙

5. 综合

走迷宫问题

迷宫矩阵中元素全为0或1,0代表通路,1代表障碍,每次可以上下左右四个方向走,现在要求:

求 出发点到终点的所有可行路径?

求 出发点到终点的最短路径?

前面讲过,最短问题一般使用BFS,可不可以使用DFS呢? 当然可以

记住:DFS可以求出出所有的可行解,所有的解都求出来了,最短的路径当然可以确定了,只是这是DFS的效率明显比BFS低

现在我们使用 DFS 和 BFS 求第二个问题

(1) DFS求解,每次求得一个可行路径时,我们就与前一个可行解做出大小判断,最后结果即为最短路径

int dx[] = {-, , , }; // x --> row
int dy[] = {, , -, }; // y --> col int minStep = INT_MAX; // 最终结果 void dfs(std::vector<std::vector<int>>& maze, const std::vector<int>& start, const std::vector<int>& end, int curX, int curY, int step) {
if (curX == end.at() && curY == end.at()) { // 求得可行解
minStep = std::min(minStep, step); // 更新最小解
return;
} for (int i = ; i < ; ++i) { // 上下左右4个方向进行搜素扩展
int nextX = curX + dx[i];
int nextY = curY + dy[i];
if (nextX < || nextX >= maze.size()) continue; // 搜索边界剪枝
if (nextY < || nextY >= maze.at().size()) continue; // 搜索边界剪枝
if (maze.at(nextX).at(nextY) == ) { // 必须是通路,即判重
maze.at(nextX).at(nextY) = 1; // 标记已访问过

dfs(maze, start, end, nextX, nextY, step + ); // 执行搜索扩展
maze.at(nextX).at(nextY) = 0; // 回溯操作
}
}
}

(2) BFS求解,使用一个结构体保存坐标状态,使用一个队列辅助BFS操作

int dx[] = {-, , , }; // x --> row
int dy[] = {, , -, }; // y --> col
int minStep = ; // 最终结果
// 保存坐标状态
struct node {
int x;
int y;
node(int xPos, int yPos) : x(xPos), y(yPos) { }
}; std::queue<node> que; int minStep(std::vector<std::vector<int>>& maze, const std::vector<int>& start, const std::vector<int>& end) {
que.push(node(start.at(), start.at()));
maze.at(start.at(0)).at(start.at(1)) = 1; //标记已访问过
while (!que.empty()) {
node curNode = que.front();
que.pop();
if (curNode.x == end.at() && curNode.y == end.at()) { // 求得可行解,BFS一定是最短
return minStep;
} for (int i = ; i < ; ++i) { // 上下左右4个方向进行搜索扩展
int nextX = curNode.x + dx[i];
int nextY = curNode.y + dy[i];
if (nextX >= && nextX < maze.size() && nextY >= && nextY < maze.at().size() && maze.at(nextX).at(nextY) == 0) {
que.push(node(nextX, nextY));
maze.at(nextX).at(nextY) = 1; // 标记已访问过
}
}
++minStep; // 增大每一层搜索的距离
}
return ; // 存在没有可行路径的可能
}

我要好offer之 搜索算法大总结的更多相关文章

  1. 我要好offer之 二叉树大总结

    一. 二叉树定义 二叉树具有天然的递归特性,凡是二叉树相关题,首先应该联想到递归 struct BinTreeNode { BinTreeNode* left; BinTreeNode* right; ...

  2. 我要好offer之 网络大总结

    1. TCP协议的状态机 TCP一共定义了11种状态,这些状态可以使用 netstat 命令查看 @左耳朵耗子 tcp系列教程: 上篇 下篇 2. TCP建立连接3次握手.释放连接4次握手 TCP包头 ...

  3. 我要好offer之 C++大总结

    0. Google C++编程规范 英文版:http://google-styleguide.googlecode.com/svn/trunk/cppguide.xml 中文版:http://zh-g ...

  4. 我要好offer之 链表大总结

    单链表是一种递归结构,可以将单链表看作特殊的二叉树(我把它叫做一叉树) 单链表的定义: /** * Definition for singly-linked list. * struct ListNo ...

  5. 我要好offer之 系统基础大总结

    1. APUE Unix环境高级编程 (1) Unix基础知识: 内核->系统调用->shell和库函数->应用软件 (2) 文件I/O:read函数返回值.进程的文件描述符表.文件 ...

  6. 我要好offer之 概率题大总结

    1. 利用等概率Rand5生成等概率Rand3 Rand5生成等概率Rand3 这个题目可以扩展为:利用等概率RandM生成等概率RandN (M > N) 这里,我们首先明白一个简单的知识点: ...

  7. 我要好offer之 排序算法大总结

    1. 插入排序 (1) 直接插入排序 void StraightInsertionSort(std::vector<int>& num) { || num.size() == ) ...

  8. 我要好offer之 字符串相关大总结

    1. str*系列手写代码 a. 一定要注意末尾'\0'的处理,切记切记 b. 一定要对输入做有效性判断,多用断言就是了 int Strlen(const char* str) { assert(st ...

  9. HDU 1203 I NEED A OFFER (01背包&&概率dp)

    M - I NEED A OFFER! Time Limit:1000MS     Memory Limit:32768KB     64bit IO Format:%I64d & %I64u ...

随机推荐

  1. Bootstrap 徽章(Badges)

    本章将讲解Bootstrap徽章(Badges),徽章与标签相似,主要的区别是徽章的圆角比较圆滑. 徽章(Badges)主要用于突出显示新的或未读的项,如果使用徽章,只需要把<span clas ...

  2. cocos2dx for lua 简单的翻牌动画

    local x = 20 local y = display.height/2 for i = 1,16 do--创建16张 local cardFg = display.newSprite(&quo ...

  3. 2d游戏中的射线与矩形检测碰撞

    cc.exports.LineCollideRect(startLine,endLine,rect)--向量与矩形检测碰撞 --获取矩形的四个顶点位置 local p = {cc.p(rect.x,r ...

  4. bash编程之case语句,函数

    bash脚本编程:之case语句   条件测试: 0: 成功 1-255: 失败   命令: [ expression ] [[ expression ]] test expression   exP ...

  5. 洛谷 P5015 标题统计

    第一道题很简单,标签:字符串.模拟. 只需要一个判断去除空格就对了: if(a[i]!=' ' && a[i]!='\n') v++; code: #include<iostre ...

  6. 【Shiro】调用doGetAuthenticationInfo进行认证成功之后,isAuthenticated是false的问题。

    使用@Configuration配置shiro无状态登录时出现的问题,在subject.login之后当前线程重新绑定了一个假定subject,isAuthenticated. 这里自定义的访问拦截器 ...

  7. 第二课:PHP 安装

    PHP 安装 您需要做什么? 为了开始使用 PHP,您可以: 找一个支持 PHP 和 MySQL 的 Web 主机 在您自己的 PC 机上安装 Web 服务器,然后安装 PHP 和 MySQL 使用支 ...

  8. 用\r做出进度条

    在做ftp作业的时候,需要做一个上传和下载的进度条,做的时候发现用\r很容易就能做出来 def show_progress(self, has, total): rate = float(has) / ...

  9. python2与python3的bytes问题

    >>> s = '编程' >>> print s 编程 >>> s '\xe7\xbc\x96\xe7\xa8\x8b' >>> ...

  10. NSNotificationCenter的用法

    作用:NSNotificationCenter是专门供程序中不同类间的消息通信而设置的. 注册通知:即要在什么地方接受消息 [[NSNotificationCenter defaultCenter]  ...