搜索算法总结

news2024/11/25 16:46:29

文章目录

  • 搜索算法
    • 1. 深度优先搜索(Depth-First-Search, DFS)
    • 2. 广度优先搜索(Breadth-first search, BFS)
    • 3. 启发式搜索策略
      • 3.1 爬山法(Hill climbing)
      • 3.2 最佳优先搜索(Best-first search)
    • Greedy BFS
    • 4. 代价一致搜索(Dijkstra)
    • 5. 分支限界(Branch and bound,简称BB)
    • 6. 动态规划(Dynamic programming)
    • 参考文章

搜索算法

被可爱的女孩子问懵了,学艺不精,翻车的我决定重新理解一下这几个算法

wiki真是个好东西,它真的我哭死

1. 深度优先搜索(Depth-First-Search, DFS)

不多说,遍历顺序如下图序号
在这里插入图片描述

2. 广度优先搜索(Breadth-first search, BFS)

也不多说,遍历顺序在图中
在这里插入图片描述

3. 启发式搜索策略

3.1 爬山法(Hill climbing)

爬山法是完完全全的贪心法,每次都鼠目寸光的选择一个当前最优解,因此只能搜索到局部的最优值。

例题: 以八数码为例
在这里插入图片描述
在这里插入图片描述
每一步都选择局部最优,最后很可能陷入一个局部最优解。
在这里插入图片描述
那么能不能优化一下呢?当它找到某个山顶时,可以给个随机数,随机一下,就有概率找到更优的解(同时也承担着找到更差解的风险)。下面以模拟退火为例,

模拟退火其实也是一种贪心算法,但是它的搜索过程引入了随机因素.。模拟退火算法以一定的概率来接受一个比当前解要差的解,因此有可能会跳出这个局部的最优解,达到全局的最优解。以上图为例,模拟退火算法在搜索到局部最优解A后,会以一定的概率接受到E的移动。也许经过几次这样的不是局部最优的移动后会到达D点,于是就跳出了局部最大值A。

3.2 最佳优先搜索(Best-first search)

首先看一下wikipedia对 Best-first search 算法的解释
在这里插入图片描述
说法不是很统一,大概是两种

  1. 可以是 A* 这样的启发式函数,既用到了从起点到当前点的评价,也用到了从当前点到目标点的估价。

  2. 可以是 greedy best-first search 基于贪心策略的优先搜索,即只用到从当前点到达目标点的估价。

这里我选择第二种来理解,wiki 给出的伪代码如下,看算法名字就很形象,贪心的 BFS

Greedy BFS

Using a greedy algorithm, expand the first successor of the parent. After a successor is generated:

使用贪心算法,将队列中的所有结点排序后,将队列中位于队首的结点出队,将其所有子节点拓展进队列

  1. If the successor’s heuristic is better than its parent, the successor is set at the front of the queue (with the parent reinserted directly behind it), and the loop restarts.

    如果后代节点的估价更优,则可以放在父节点的前面。(实现过程可以是用普通队列让所有节点出队,重新排序后入队;也可以使用优先队列(堆),这样每次优先队列的队首(堆顶)都是当前队列中估价最优的节点)

  2. Else, the successor is inserted into the queue (in a location determined by its heuristic value). The procedure will evaluate the remaining successors (if any) of the parent.

    如果新加入的节点估价不好,按照估价排序放在队列中的对应位置就行

Below is a pseudocode example of this algorithm, where queue represents a priority queue which orders nodes based on their heuristic distances from the goal. This implementation keeps track of visited nodes, and can therefore be used for undirected graphs. It can be modified to retrieve the path.

下面是该算法的伪代码示例,其中 queue 表示优先级队列,它根据节点与目标的启发式距离对节点进行排序。

procedure GBS(start, target) is:
  mark start as visited
  add start to queue
  while queue is not empty do:
    current_node ← vertex of queue with min distance to target
    remove current_node from queue
    foreach neighbor n of current_node do:
      if n not in visited then:
        if n is target:
          return n
        else:
          mark n as visited
          add n to queue
  return failure 

可以看到,每个节点都会被 visited 标记,即每个节点只能访问一次,每次都是贪心的选择下一步的最优解,不回头更新之前的搜索过的结果,可能会陷入局部最优。

例题: 以八数码为例,使用优先队列(小顶堆)
在这里插入图片描述
贪心的选择队列中的元素做搜索

  • 开始根节点入队,与目标情况差距为3,即1,2,8不在正确的位置上
[3(一层一号)]
  • 根节点出队,将其子节点放入优先队列
[3(二层一号), 3(二层二号), 4(二层三号), 4(二层四号)]
  • 取出 3(二层一号) ,将其子节点放入优先队列
[3(二层二号), 3(三层层二号), 4(二层三号), 4(二层四号), 4(三层一号)]
  • 取出 3(二层二号) ,将其子节点放入优先队列
[2(三层三号), 3(三层层二号), 4(二层三号), 4(二层四号), 4(三层一号), 4(三层四号)]
  • 取出 2(三层三号), 将其子节点放入优先队列
[1(四层一号), 3(三层层二号), 4(二层三号), 4(二层四号), 4(三层一号), 4(三层四号)]
  • 取出 1(四层一号), 在其子节点中找到目标节点, 0(五层一号),找到可行解,搜索停止

可以看到 Best-first search 并没有剪枝操作,只是贪心的选择搜索的下一个节点,找到的可能是局部最优解。

4. 代价一致搜索(Dijkstra)

代价一致搜索其实就是 Dijkstra 其搜索过程如下图所示

在这里插入图片描述

Best-first search 不一样的地方:

  • 这里寻找点,用到的是从起点当前点的距离
  • 这里限制每个节点的访问次数,找到一个更优的解,就可以去更新所有已经搜索过的路径长度,最后获得全局最优解。

伪代码(Pseudocode)如下

dist[u] 是从源点到顶点 u 的当前距离。

Graph.Edges(u, v) 返回连接两个邻居节点 u 和 v 的边的长度(即之间的距离)

 1  function Dijkstra(Graph, source):
 2      
 3      for each vertex v in Graph.Vertices:
 4          dist[v] ← INFINITY
 5          prev[v] ← UNDEFINED
 6          add v to Q
 7      dist[source]0
 8      
 9      while Q is not empty:
10          u ← vertex in Q with min dist[u]
11          remove u from Q
12          
13          for each neighbor v of u still in Q:
14              alt ← dist[u] + Graph.Edges(u, v)
15              if alt < dist[v]:
16                  dist[v] ← alt
17                  prev[v] ← u
18
19      return dist[], prev[]

可以看到,一个节点可以多次入队,可以反复更新之前搜索过的路径,最后获得全局最优解。

5. 分支限界(Branch and bound,简称BB)

分支限界法常以广度优先或以最小耗费(最大效益)优先的方式搜索问题的解空间树。在分支限界法中,每一个活结点(没被访问过的节点)只有一次机会成为扩展结点。活结点一旦成为扩展结点,就一次性产生其所有儿子结点。在这些儿子结点中,导致不可行解或导致非最优解的儿子结点被舍弃,其余儿子结点被加入活结点表中。此后,从活结点表中取下一结点成为当前扩展结点,并重复上述结点扩展过程。这个过程一直持续到找到所需的解或活结点表为空时为止。

首先对比一下回溯法分支限界法

  • 回溯法:一种基于深度优先搜索的剪枝策略

    • 回溯法的求解目标是找出解空间树中满足约束条件的所有解

    • 利用深度优先搜索的方法,当搜到一个显然不合理的结果时,没必要继续深入搜索,这时利用回溯回退到其父节点,寻找下一个值得继续搜索的节点。如此可以剪去一些不必要的搜索,同时可以找到所有可能的结果。

  • 分支限界法:常基于广度优先搜索的剪枝策略

    • 分支限界法的求解目标是找出满足约束条件的一个解,或是在满足约束条件的解中找出在某种意义下的最优解。
    • 利用广度优先搜索,首先根据已知的条件确定到达目标节点的代价上界,并估计当前可达拓展节点的代价下界,如果可拓展节点的代价下界超出到达目标节点的代价上界时,该节点肯定不是最优解了,抛弃之,即剪枝。
    • 实现方法可以有很多,原理差不多,一般选用优先队列,比较简单好理解
      • 队列式(FIFO)分支限界法:按照队列先进先出(FIFO)原则选取下一个节点为扩展节点。
      • LC(最小代价)分支限界法:采用优先队列作为活结点表
      • 栈式(LIFO)分支限界法:按照栈的存储方式排序拓展节点

例题:以单源最短路为例来解释分支界限法的计算过程:求从S->T的最短距离(括号内为从起点到达当前点的路径长度)
在这里插入图片描述

  • 首先根节点(出发点)进入优先队列
[S(0)]
  • 根节点出队,拓展根节点,将其可达的三个子节点入队
    在这里插入图片描述
[A(2), B(3), C(4)]:此时S->A的代价最小,所以优先队列队首(堆顶)是A节点
  • 队首A节点出队,拓展A节点,其子节点入队
    在这里插入图片描述
    由于每个节点只能拓展一次,当出现两个相同可拓展节点时,即将出现重复的搜索,此时进行对重复搜索的剪枝,选择代价最优的拓展,这里舍弃路径较长的 B(5) ,即更新 B 节点的代价下界为 3。(当出现代价小于 3 的 B 节点,则更新 B 节点的最小下界,并替换 B 节点,否则丢弃新找到的节点)
[B(3), C(4), D(9), E(4)]
  • 取出此时代价最小的节点 B 做拓展
    在这里插入图片描述
    E 节点的下界此时为 4 ,剪枝舍弃新的节点 E(12)
[C(4), D(9), E(4), F(5)]
  • 取出此时代价最小的节点 C 做拓展
    在这里插入图片描述
    F 节点的下界此时为 5 ,剪枝舍弃新的节点 F(6)
[D(9), E(4), F(5)]
  • 取出此时代价最小的节点 E 做拓展
    在这里插入图片描述
[D(9), F(5), H(7)]
  • 取出此时代价最小的节点 F 做拓展
    在这里插入图片描述
    更新 H 下界为 6,舍弃原来的 H(7)
[D(9), H(6), I(6)]
  • 取出此时代价最小的节点 H 做拓展
    在这里插入图片描述
    这里贪心的找到了第一个可行解,记录从起点到达终点的代价上界为 8 。(此时如果再出现下界小于上界的节点,则直接抛弃,这部分是舍弃不必要搜索的剪枝)
    在这里插入图片描述
    这里把 D(9) 分支剪掉,因为 D 此时的代价下界已是 9,超过起点到达目标点的上界,不可能找到更优的解,直接舍弃该分支。
[I(6)]
  • 取出此时代价最小的节点 I 做拓展
    在这里插入图片描述
    找到一个更优的可行解,更新起点到终点的上界为 7

此时优先队列为空,搜索结束,最短路径长为 7

6. 动态规划(Dynamic programming)

动态规划在查找有很多重叠子问题的情况的最优解时有效。它将问题重新组合成子问题。为了避免多次解决这些子问题,它们的结果都逐渐被计算并被保存,从简单的问题直到整个问题都被解决。因此,动态规划保存递归时的结果,因而不会在解决同样的问题时花费时间。

动态规划只能应用于有最优子结构的问题。最优子结构的意思是局部最优解能决定全局最优解(对有些问题这个要求并不能完全满足,故有时需要引入一定的近似)。简单地说,问题能够分解成子问题来解决。

例题1(无向图): 以下图为例,求解 S->T 的最短路径
在这里插入图片描述

首先考虑如何拆解问题,

从一个点到达另一个点只有两种方式:

  • 直接到达
  • 通过其他点中转到达

想知道 S->T 的最短距离,对于他们路径上某个点 x ,只需要知道 S->X 的最小距离和 X->T 的最小距离,即可推出经过点 X 的情况下,S->T 的最短距离。

邻接矩阵:dp[i][j] 表示 i->j 的路径距离

状态转移方程如下:取当前的最短路径和经过 X 中转之后的最短路径中的最小值

dp[S][T] = min(dp[S][T], dp[S][X] + dp[X][T]);

根据上图可以的到的邻接矩阵如下

  • 0 表示 节点到自己距离为 0
  • 空表示 dist[i][j] = infi, j 两点之间不可达,距离为无穷大
  • 其余数字代表 dist[i][j]i->j 的边长距离
i\jSABCDEFt
S034
A3045
B4045
C40
D4502
E204
F403
t30

动态规划过程如下:

首先初始化所有的 dp[i][j] 为当前邻接矩阵中的值

从目标结论出发,逐步递推,就可将问题转化到我们已知的初始值上,即 dp[S][A]dp[S][D]

S->t : dp[S][t] = min(dp[S][t], dp[S][K] + dp[K][t]); K 可能是剩余节点中任何一个可能到达 t 的节点

S->F : dp[S][F] = min(dp[S][F], dp[S][K] + dp[K][F]); K 可能是剩余节点中任何一个可能到达 F 的节点

S->E : dp[S][E] = min(dp[S][E], dp[S][K] + dp[K][E]); K 可能是剩余节点中任何一个可能到达 E 的节点

S->A : dp[S][A] = min(dp[S][A], dp[S][K] + dp[K][A]); K 只能是 S , 此时得到 dp[S][A] = 3;

S->D : dp[S][D] = min(dp[S][D], dp[S][K] + dp[K][D]); K 只能是 S , 此时得到 dp[S][D] = 4;

那么我们从下向上递推,就能通过逐步更新到达前面节点的最小距离,得到我们要的答案 dp[S][T]

for (int t = 0; t < n; t++) { // 按邻接表中的顺序给搜索树编号
	for (int k = 0; k < t; k++) {
		dp[0][t] = min(dp[0][t], dp[0][k] + dp[k][t]);
	}
}

递推的过程可以看作从前往后建立一颗搜索树(不能抵达待更新路径终点的节点没有画出),下图的 g = dp[0][i]
在这里插入图片描述
有多条路径能够到达某个节点时,在动态规划的过程中,已经将最短路径做了更新,之后每次用到的都是当前已知路径中最短的路径,相当对那些较长的路径做了剪枝,保留最短路径而删去之前找到的较长路径,不从这些路径继续向下寻找。

例题2(有向图,): 一张来自阿里云社区的博客动图

如果拓展表中有多条到达某一公共节点的路径时,只保留耗散值最小的路径,其余删去。

下图橙色箭头就是在拓展节点,当拓展到的节点已经有可达路径时,判断新的路径和之前的路径哪个耗散值最小(即路径长度最小),保留最小长度的路径,其余路径删去,之后的拓展,基于这个最短的路径值继续拓展。

在这里插入图片描述
邻接矩阵:dist[i][j] 表示 i->j 的路径距离

dp[i] 表示从起点到达节点 i 的最小距离

状态转移方程如下:取当前的最短路径和经过 X 中转之后的最短路径中的最小值

dp[T] = min(dp[T], dp[X] + dist[X][T]);

动态规划核心代码

for (j = 1; j < n; j++) { // 首先以第一个点作为终点,从前往后递推,不断把终点后移直到终点,其之前的节点的路径已经都算出来了,可作为动态规划的条件
    for (i = j - 1; i >= 0; i--) {
        if (dist[i][j] + dp[i] < dp[j]) {
            dp[j] = dist[i][j] + dp[i];
        }
    }
}
return dp[n];

参考文章

Depth-First-Search

Breadth-first search

Hill climbing

Best-first search

Dijkstra’s algorithm

Branch and bound

Dynamic programming

七七八八百

快懂百科

阿里云社区

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/578773.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【嵌入式环境下linux内核及驱动学习笔记-(13-中断管理)】

目录 1、中断基本概念2、ARM体系中断系统2.1 ARM具有的七种异常模式与中断的关系2.2 ARM多核环境下的中断2.3 exynos4412(contex A9)的中断 3、中断处理程序架构4、 中断接口编程4.1 中断接口函数4.1.1 request_irq4.1.2 free_irq4.1.3 irqreturn_t4.1.4 irq_handler_t 中断处理…

C语言初阶之函数介绍及练习

函数介绍及练习 1.函数是什么&#xff1f;2.C语言中函数的分类&#xff1a;2.1 库函数2.2 自定义函数 3. 函数的参数3.1 实际参数&#xff08;实参&#xff09;&#xff1a;3.2 形式参数&#xff08;形参&#xff09;&#xff1a; 4.函数的调用4.1 传值调用4.2 传址调用 5. 函数…

真相只有一个——谁是凶手

谁是凶手 1.题目描述2. 解题思路3.代码展示 所属专栏&#xff1a;脑筋急转弯❤️ &#x1f680; >博主首页&#xff1a;初阳785❤️ &#x1f680; >代码托管&#xff1a;chuyang785❤️ &#x1f680; >感谢大家的支持&#xff0c;您的点赞和关注是对我最大的支持&am…

漫游计算机系统

1.信息就是位 上下文 那么什么是信息呢&#xff1f; 在计算机系统中&#xff0c;所有的信息——包括磁盘文件、内存中的程序、内存中存放的用户数据以及网络上传送的数据。本质上是一串比特位。 那么又要了解什么是比特了&#xff0c;比特&#xff08;bit)就是二进制&#xff…

基于标准库函数的STM32的freertos的移植(一)——github源码压缩包下载

由于freertos官网将freertos内核与freertos工程分别进行版本管理&#xff0c;因此下载freertos需要将参考工程和内核分别下载。由于采用ST公司提供的标准库函数进行因此还需要下载标准库函数&#xff0c;然后进行移植配置。具体流程如下详细描述&#xff1a; 1.首先在github的…

git Husky

虽然我们已经要求项目使用eslint了&#xff0c;但是不能保证组员提交代码之前都将eslint中的问题解决掉了&#xff1a; 也就是我们希望保证代码仓库中的代码都是符合eslint规范的&#xff1b; 那么我们需要在组员执行 git commit 命令的时候对其进行校验&#xff0c;如果不符合…

centos7安装docker 并创建mysql

Docker 分为 CE 和 EE 两大版本。CE 即社区版&#xff08;免费&#xff0c;支持周期 7 个月&#xff09;&#xff0c;EE 即企业版&#xff0c;强调安全&#xff0c;付费使用&#xff0c;支持周期 24 个月。 Docker CE 分为 stable test 和 nightly 三个更新频道。 官方网站上有…

关于强电与弱的的介绍

强电&#xff1f;弱电&#xff1f;傻傻分不清楚&#xff0c;今天海翎光电的小编为大家系统的介绍一下强电与弱电。 什么是强电&#xff1f; &#xff08;1&#xff09;供配电系统&#xff1a;供配电系统包括负荷分级、供电措施、负荷力矩、电网谐波限值、用电指标、负荷所需要…

MySQL数据库修改root账户密码

博主今天登录数据库遇到了一个问题&#xff0c;通过这篇文章&#xff08;http://t.csdn.cn/58ECT&#xff09;解决了。文中关于修改root账户密码的部分&#xff0c;博主觉得有必要写一篇文章总结下。 第一步&#xff1a;用管理员账户打开CMD 第二步&#xff1a;开启mysql服务 …

dubbo源码阅读: dubbo的xml文件如何解析的?

dubbo源码阅读&#xff1a; dubbo的xml文件如何解析的&#xff1f; DubboNamespaceHandlerspring 的接口 NamespaceHandlerspring 的抽象类 NamespaceHandlerSupport学以致用 <?xml version"1.0" encoding"UTF-8"?> <beans xmlns:xsi"http…

征文 | 吸引铁粉?成为CSDN明星!

&#x1f497;wei_shuo的个人主页 &#x1f4ab;wei_shuo的学习社区 &#x1f310;Hello World &#xff01; 征文 | 吸引铁粉&#xff1f;成为CSDN明星&#xff01; 导读 当今数字时代&#xff0c;社交媒体和在线社区成为了人们交流和分享的主要平台之一&#xff0c;CSDN就是其…

前沿重器[34] | Prompt设计——LLMs落地的版本答案

前沿重器 栏目主要给大家分享各种大厂、顶会的论文和分享&#xff0c;从中抽取关键精华的部分和大家分享&#xff0c;和大家一起把握前沿技术。具体介绍&#xff1a;仓颉专项&#xff1a;飞机大炮我都会&#xff0c;利器心法我还有。&#xff08;算起来&#xff0c;专项启动已经…

31 Vue 表单输入绑定的实现

前言 这是最近的碰到的那个 和响应式相关的问题 特定的操作之后响应式对象不“响应“了 引起的一系列的文章 主要记录的是 vue 的相关实现机制 呵呵 理解本文需要 vue 的使用基础, js 的使用基础 测试用例 测试用例如下, 一个简单的 v-model 的使用 问题的调试 这里 …

尝试在UNet的不同位置添加SE模块

目录 &#xff08;1&#xff09;se-unet01&#xff08;在卷积后&#xff0c;下采样前&#xff0c;添加SE模块&#xff09; &#xff08;2&#xff09;se-unet02&#xff08;在卷积后&#xff0c;上采样前&#xff0c;添加SE模块&#xff09; &#xff08;3&#xff09;se-un…

Qt基础之三十六:异常处理

本文将介绍如何在Qt中使用try...catch和调试dump文件来处理异常。 Qt版本5.12.6 一.使用try...catch 一段简单的捕获异常的代码,新建一个控制台工程,pro文件不用修改 #include <QCoreApplication> #include <QDebug>int main(int argc, char *argv[]) {QCoreA…

历届蓝桥杯青少年编程比赛 计算思维题真题解析【已更新5套 持续更新中】

一、计算思维组考试范围 计算思维组面向小学生&#xff08;7-12 岁&#xff0c;约 1-6 年级&#xff09;&#xff0c;通过设计多个角度的考核题目、层次科学的试卷组合、线上限时的考试形式&#xff0c;更加精确地考查学生的计算能力、反应能力、思维与分析能力&#xff0c;使…

注解实现自动装配

要使用注解须知&#xff1a; 1.导入约束 context约束 2.配置注解的支持 官方配置文件 <?xml version"1.0" encoding"UTF-8"?> <beans xmlns"http://www.springframework.org/schema/beans"xmlns:xsi"http://www.w3.org/2001/…

Flume实践

1 NetCat方式 ]# ./bin/flume-ng agent --conf conf--conf-file ./conf/flume_netcat.conf --name a1 -Dflume.root.loggerINFO,console [rootmaster ~]# yum -y intalll telnet 发数据&#xff1a; ]# telnet master 44444 数据接收&#xff0c;是在终端上接收的&#xff0…

[创业之路-70] :聊天的最高境界因场景不同而不同

一、聊天的最高境界因场景不同而不同。 销售式聊天&#xff1a; 聊天的最高境界不是真相&#xff0c;而是拨动对方的心弦&#xff0c;让对方心理爽&#xff0c;让对方舒心。聊天的结果是你买单。 消费式聊天&#xff1a; 聊天的最高境界不是真相&#xff0c;而是让自己心理爽…

Java面试集锦

1. html与jsp区别&#xff1f; 答&#xff1a;HTML是文本标记语言&#xff0c;它是静态页面&#xff1b;JSP页面是有JSP容器执行该页面的Java代码部分然后实时生成动态页面&#xff0c;可动态更新页面上的内容。 在jsp中用<%%>就可以写Java代码了&#xff0c;而html没有…