AI 系列-3:搜索-从迷宫求解看人工智能关键技术
在人工智能研究的早期,科学家们一直在寻觅一套通用的方法,以攻克各式各样的智能难题。搜索技术便在这样的探索中应运而生。它就像是一把万能钥匙,能帮助我们解决众多人工智能问题。
从本质上讲,搜索技术是许多智能任务的共性部分。就如同我们研究清楚了轮子的特性,就能将其运用到不同的交通工具上解决运输问题;弄明白了一元一次方程的解法,就能在多个领域解决相关数学问题一样。
而掌握搜索技术的规律,能让我们从根本上解决一系列相关问题,而不仅仅是孤立地处理某一个问题。
走迷宫:简单例子背后的大学问
走迷宫看似是个简单的游戏,却蕴含着丰富的算法原理。迷宫具有简单直观的特点,这使得它成为研究搜索算法的绝佳范例。一方面,它便于我们观察和分析;另一方面,很多其他复杂问题其实都可以类比为走迷宫问题。只要能解决走迷宫的问题,就有可能解决与之类似的其他问题。
对于一个迷宫,我们要关注的关键信息是起始点、分岔点、端点和终点,以及这些点之间的连通关系。可以用非常简洁的方式在计算机中记录迷宫:
- 用一个文件,每行记录一个节点的名字来呈现所有节点;
- 再用另一个文件,每行记录一条通路(即通路连接的两个节点)。
这种记录方式,就像是给计算机绘制了一张迷宫的 “ 地图 “,让它能够理解迷宫的结构。就拿下图举例,即使图 a 和图 c 的迷宫差别再大,将其端点取出绘制成图,可以发现底层都是同一张地图 b。
搜索算法大揭秘
宽度优先搜索(Breadth - First search)
宽度优先搜索算法就像是一个有条不紊的探索者。以迷宫为例,从出发点开始,它会先探索与起始点相连的所有节点,然后再一层一层地向外扩展。
比如在一个简单迷宫中,从 S 点出发,它会先考虑能到达的 A 点和 C 点,然后继续探索 A 点和 C 点能通向的其他节点,依次类推,直到找到目标节点 G。
这个过程就像一棵倒长的树,从树根(起始点)开始,一层一层地生长(探索节点),节点旁边的数字代表着搜索的顺序。它的优点是只要起始点和目标点连通,就一定能找到通路,并且能找到最优解(即最短路径)。然而,它也有缺点,在搜索过程中需要记录大量的分岔节点,这使得搜索速度相对较慢。
下面拿一个图举例,看看宽度优先搜索算法是如何进行的:
深度优先搜索(Depth - First search)
深度优先搜索算法则更像是一个勇往直前的冒险者。它从起始点开始,会沿着一个方向、一条路一直走下去,直到遇到死胡同(无路可走的节点)。这时,它会返回到最接近死胡同的分岔点,然后继续探索其他分支。
在实际走迷宫中,我们沿着左手边的墙一直走,遇到死胡同就折返的方法,其实就是深度优先搜索算法的体现。这种算法不需要记录太多的分岔节点,但它的不足之处在于不能保证找到的通路是最优解,有时候可能会走一些冤枉路,甚至走到一条最远的通路。
为了方便大家理解,我们还是以画图的方式来呈现(我们从结点 1 开始遍历):
从结点 1 的第一个相邻结点即结点 2 开始遍历,先遍历哪个点一般取决于存储边的时候是以什么方式进行。
以相同的方式继续沿结点的第一个相邻结点即结点 2 遍历,就这样一直往前递归遍历直至无法继续向前。
同上,往结点 6 进行遍历。
同上,往结点 4 进行遍历。
这里需要注意,结点 4 与结点 3 是相连的,所以可能会遍历到结点 3 ,但是结点 3 已经遍历过了,故直接跳到下一个结点即结点 5 进行遍历。
此时发现与结点 5 相邻的所有结点都已经遍历过了,故遍历结束。
其他搜索算法
除了宽度优先搜索和深度优先搜索,还有等费用方法、爬山法、A 算法和 A* 算法等。
其中,A* 算法堪称算法界的明星。当遇到分岔点时,A* 算法会对每一个可能的分岔后的路径计算一个启发函数。通过这个函数值,算法能够知道往哪个方向走更容易到达目标节点,从而避免去尝试那些绕远的路径。
A* 算法凭借其高效性和准确性,已经成为计算机科学中的基础算法,并在实际中得到广泛应用。
算法性能
当我们运用这些搜索算法来解决迷宫问题时,不禁会思考它们的性能究竟如何。这主要涉及三个关键问题:
- 能否找到解:如果起始点和目标点之间存在通路,那么上述算法通常都能找到这条通路;若不存在通路,算法也会给出相应提示。
- 是否为最优解:宽度优先搜索算法、等费用搜索算法和 A* 算法能够保证找到的通路是最短的最优解,而深度优先搜索算法则无法保证。
- 执行速度:宽度优先搜索算法虽然能找到最优解,但由于需要尝试搜索大量的节点,所以执行速度相对较慢。
用搜索算法解决实际问题
在日常生活中,我们从一个地方去另一个地方,可以用一些软件(如百度地图、高德地图)来提供一条路径。这样的路径可以通过搜索的算法给出,下面再举一些搜索算法可以解决的游戏的例子。
独粒钻石游戏:简洁规则背后的复杂挑战
独粒钻石游戏是一个充满挑战的游戏。棋盘上黑色的棋子只能沿着横竖线隔着另一枚棋子跳到空着的位置,被跨过的棋子会被拿走。游戏的目标是从初始状态开始,经过若干步走棋,最后在棋盘上只留下一枚棋子。
在运用搜索算法玩这个游戏时,首先要明确如何表示游戏状态。棋盘上哪些位置有棋子、哪些位置是空的,构成了一个状态。每移动一枚棋子,棋盘状态就会发生变化,而走棋规则决定了不同状态(节点)之间的连接关系,且这些边是带方向的,因为反向走棋不被允许。
确定了节点和连接,就可以使用常见的搜索算法,如宽度优先搜索算法、深度优先搜索算法等,来寻找从初始状态到目标状态(棋盘只剩一枚棋子)的路径。令人惊喜的是,搜索算法在现代计算机上完成这个搜索过程非常迅速,通常在 1 秒以内。
华容道:” 捉放曹 “ 的算法演绎
华容道,也叫 “ 捉放曹 “,是一款广受欢迎的游戏。游戏开始时,积木块有一个初始布局,玩家需要通过向上下左右的空白位置滑动大小不同的积木块,将最大的积木块移动到指定位置。
使用搜索算法玩华容道时,第一步是确定游戏状态的表示方法。任何时刻积木块的布局都可看作一种状态(节点),根据积木移动规则,一个状态通过移动一块积木变成另一个状态,这两个状态对应的节点之间就有一条无方向的边连接。
确定表示后,就能运用之前提到的搜索算法寻找从初始状态到目标状态的路径,也就是找到积木的移动顺序。计算机利用搜索算法完成这个搜索过程同样很快,一般在台式机或笔记本电脑上小于 1 秒。不过,这个游戏难度不小,每次移动积木后都有多种后续选择,分岔很多,且通常要走几十步才能完成,这也正是它的魅力所在。
重排九宫:数字的有序之旅
重排九宫游戏是将数字为 1 - 8 的 8 张扑克牌随机摆放在 3×3 的九宫格上,其中有一个空格。玩家可以将空格位置上下左右的扑克牌向空格移动,目标是通过移动扑克牌使其变为特定的有序状态。
同样,先把任何时刻扑克牌的布局看作一个状态(节点),依据扑克牌移动规则确定节点之间的连接,这些边也是无方向的,这样就可以用搜索算法求解。
此外,还可以通过设计特殊函数来优化搜索过程。比如设计函数 P(n) 表示各张牌与其目标位置的距离总和,函数 S(n) 计算牌局的逆序数得分以及中心有无牌的得分,函数 g(n) 记录从初始状态到当前牌局移动牌的次数。
通过这三个函数得到指导算法运行的函数 f(n) = g(n) + h(n) (其中 h(n) = P(n) + 3S(n) ,h(n) 为启发函数) 。在游戏过程中,对每种移牌选择后的牌局计算其 f(n) ,选择得分最小的移牌方式,反复这个过程直至达到目标状态。这种启发式搜索利用启发函数引导算法向目标方向移动,大大提高了搜索效率。
下棋游戏:博弈中的算法智慧
在两人对垒的棋类游戏,如跳棋、中国象棋、国际象棋、围棋等(这里不包括一些特殊规则的棋类,如掷骰子决定走棋、一方棋子对方不知道的情况 )中,搜索算法也发挥着重要作用。
下棋时,每个棋局状态可看作一个节点,走棋规则建立了节点之间的连接。以中国象棋为例,开局时棋手就有多种走棋选择,且双方轮流走棋,每一步都面临众多分岔。
当分岔特别多,使用一般搜索算法寻找好的走棋选择会比较困难。此时需要考虑棋类的特殊性来优化算法。比如中国象棋开盘拱卒通常不是好选择,可去掉这类不必要分支来提高搜索速度。
此外,下棋双方都会选择对自己最有利、对对手最不利的走棋方式,这就引出了最大最小搜索(min - max search)算法。该算法会对每个棋局状态给出估值,代表对某棋手的有利程度,棋手走棋时选择估值最优的走法。当然,如何准确对棋局估值是一个关键且具有挑战性的问题。
算法运行速度与计算量:搜索算法的关键问题
在上面的介绍中,我们领略了搜索算法在各类游戏中的巧妙应用,但当我们深入探索其实际运用时,会发现算法运行速度与计算量是无法回避的关键问题,它们如同两座大山,横亘在搜索算法高效解决实际问题的道路上。
计算量:搜索算法的 “ 阿喀琉斯之踵 “
搜索算法能否有效解决实际问题,很大程度上取决于其运行时间。以独粒钻石、华容道、重排九宫这些游戏为例,搜索算法执行起来迅速流畅,但面对象棋、围棋这类复杂棋类时,情况就截然不同。
围棋棋盘为 19×19 ,开局时 “ 分岔 “ 多达 361 个,对手落子选择也有 360 个,选择空间极为庞大。这种情况下,算法要处理的节点众多,分岔繁杂,计算量呈指数级攀升,运行时间大幅延长。
为了更直观地理解计算量对算法的影响,我们通过几个例子来分析。
找出一群人中最年长的人
比如找出一群人中最年长的人,算法计算量与人数 n 呈线性关系,计算复杂度函数为 O (n) ,随着人数增加,计算时间增长相对缓慢。
1 | for (i=0; i<n; i++) |
找出一群人中相同生日的人
而找出一群人中相同生日的人,算法计算量则在 n² 量级,计算时间增长速度明显加快。
1 | for (i=0; i<n; i++) |
再看一群人的组合问题,若考虑每个人工作状态有三种可能(全时工作、半时工作、不工作 ),可能性总数为 3ⁿ ,计算量呈指数级增长,当人数为 40 时,计算所需时间竟达 3855 世纪,这就是 “ 指数爆炸 “(exponential explosion)或 “ 组合爆炸 “ 带来的惊人效应。
指数函数使得计算时间急剧增长,远超人们预期,成为搜索算法面临的棘手难题。
| 复杂度 | 人数/人 | 10 | 20 | 30 | 40 |
|---|---|---|---|---|---|
| $n$ | 0.00001s | 0.00002s | 0.00003s | 0.00004s | |
| $n^2$ | 0.0001s | 0.0004s | 0.0009s | 0.0016s | |
| $2^n$ | 0.001s | 1.0s | 17.9min | 12.7d | |
| $3^n$ | 0.059s | 58min | 6.5a | 3855 世纪 |
应对策略:从硬件到算法的全方位探索
面对计算量过大的困境,我们自然会想到提升计算机硬件性能。确实,更快的计算机能在一定程度上提高算法运行速度,但这并非万能钥匙。
以两个算法为例,算法 A 只需 1000 次加法运算,算法 B 却需要 1000 万次,即便计算机性能提升,算法 B 的巨大计算量仍会导致漫长的运行时间。所以,单纯依靠硬件升级无法彻底解决问题。
| 规模 | 速度为 x 的计算机 | 速度为 100x 的计算机 | 速度为 x 1000 的计算机 |
|---|---|---|---|
| $n$ | N_1 | 100N_1 | 1000N_1 |
| $n^2$ | N_2 | 10N_2 | 32.6N_2 |
| $2^n$ | N_3 | N_3+6.64 | N_3+9.97 |
| $3^n$ | N_4 | N_4+4.19 | N_4+6.19 |
另一种思路是设计多项式时间算法,避免指数时间算法带来的 “指数爆炸”。但许多人工智能问题复杂异常,若要确保找到全局最优解,往往需要遍历所有可能性,这就不可避免地陷入指数时间算法。
于是,研究人员开始寻求次优解,设计出虽非最优但能在可接受时间内完成运算的多项式时间算法,在解的质量和运行时间上寻找平衡点。
Tips:总的来说,指数爆炸是一个根本性的难题,人工智能很多研究都卡在这个地方。
使用知识:人工智能的一个原则
启发函数:知识赋能搜索算法
减少算法计算量的一个重要方法是利用知识,在搜索算法设计中,这种知识体现为启发式信息(heuristic information) ,其载体便是启发函数。
以重排九宫问题为例,函数 h(n) 包含了游戏的特殊信息,引导搜索方向,大幅减少了算法需要搜索的节点数量,提升了执行效率。
设计启发函数有两种途径:设计一个启发函数 or 学习一个启发函数
- 一是传统方法,要求研究人员深入了解问题,成为相关领域专家,通过反复研究和请教专家来设计函数,如 IBM 团队研发国际象棋程序 “深蓝” 时邀请国际象棋大师参与讨论。
- 二是在深度学习时代,让算法自主学习启发函数。通过获取大量数据,如程序反复试玩游戏或人为提供数据,再借助学习算法从数据中总结规律,从而得到启发函数。
这两种方法分别体现了人工智能使用知识和使用数据的原则。
智能的再思考:搜索算法与人类智慧的碰撞
当人们深入了解搜索算法流程后,常质疑其是否真正具备智能。毕竟,搜索算法看似 “机械死板”,与人类玩游戏时的 “灵光一闪” 截然不同。但搜索算法能凭借严谨的逻辑和对各种可能性的系统考量,成功攻克复杂智力游戏。
这不禁让我们思考智能的本质,也为我们在面对困难任务时提供了启示:遵循条理、流程和范式,如同搜索算法一般,或许就能完成看似难以企及的目标。无论是工程建设还是企业生产,有条理地实施计划、寻找合适方法,都有助于问题的解决。
Tips:最重要的还是要学习通过搜索算法解决实际问题的思想!
搜索算法在计算量的挑战下不断寻求突破,启发函数的运用为其注入新的活力,而关于智能的探讨也将伴随其发展持续深入。在未来,搜索算法有望在更多复杂场景中展现强大能力,为人工智能的发展开辟更广阔的道路。


















