11 | 树的深度优先搜索(上):如何才能高效率地查字典?

你还记得迭代法中的二分查找吗?在那一讲中,我们讨论了一个查字典的例子。如果要使用二分查找,我们首先要把整个字典排个序,然后每次都通过二分的方法来缩小搜索范围。

不过在平时的生活中,咱们查字典并不是这么做的。我们都是从单词的最左边的字母开始,逐个去查找。比如查找“boy”这个单词,我们一般是这么查的。首先,在 a~z 这 26 个英文字母里找到单词的第一个字母 b,然后在 b 开头的单词里找到字母 o,最终在 bo 开头的单词里找到字母 y。

你可以看我画的这种树状图,其实就是从树顶层的根结点一直遍历到最下层的叶子结点,最终逐步构成单词前缀的过程。对应的数据结构就是前缀树(prefix tree),或者叫字典树(trie)。个人更喜欢前缀树这个名称,因为看到这个名词,这个数据结构的特征就一目了然。

那前缀树究竟该如何构建呢?有了前缀树,我们又该如何查询呢?今天,我会从图论的基本概念出发,来给你讲一下什么样的结构是树,以及如何通过树的深度优先搜索,来实现前缀树的构建和查询。

图论的一些基本概念

前缀树是一种有向树。那什么是有向树?顾名思义,有向树就是一种树,特殊的就是,它的边是有方向的。而树是没有简单回路的连通图。

如果一个图里所有的边都是有向边,那么这个图就是有向图。如果一个图里所有的边都是无向边,那么这个图就是无向图。既含有向边,又含无向边的图,称为混合图。

在有向图中,以结点 v 为出发点的边的数量,我们叫作 v 的出度。而以 v为 终点的边之数量,称为 v 的入度。在上图中,结点 v2​ 的入度是 1,出度是 2。

还有两个和有向树有关的概念,回路和连通,这里简单给你解释一下,很容易就能明白了。

结点和边的交替序列组成的就是通路。所以,通路上的任意两个结点其实就是互为连通的。如果一条通路的起始点 v1​ 和终止点 vn​ 相同,这种特殊的通路我们就叫作回路。从起始点到终止点所经过的边之数量,就是通路的长度。这里画了一张图,这里面有 1 条通路和 1 条回路,第一条非回路通路的长度是 3,第二条回路的长度是 4。

理解了图的基本概念,我们再来看树和有向树。是一种特殊的图,它是没有简单回路的连通无向图。这里的简单回路,其实就是指,除了第一个结点和最后一个结点相同外,其余结点不重复出现的回路。可以看画的这几幅图。

那么,什么是有向树呢?顾名思义,有向树是一种特殊的树,其中的边都是有向的,而且它满足以下几个条件:

有且仅有一个结点的入度为 0,这个结点被称为根;

除根以外的所有结点,入度都为 1。从树根到任一结点有且仅有一条有向通路。

除了这些基本定义,有向树还有几个重要的概念,父结点、子结点、兄弟结点、先辈结点、后辈结点、叶子结点、结点的高度(或深度)、树的高度(或深度)。这些都不难理解,画个图展示一下,你就能明白了。我把根结点的高度设置为 0,根据需要你也可以设置为 1。

前缀树的构建和查询

好了,说了这么些,你对有向树应该有了理解。接下来,我们来看,如何使用有向树来实现前缀树呢?这整个过程主要包括两个部分:构建前缀树和查询前缀树。

1. 构建前缀树

首先,我们把空字符串作为树的根。对于每个单词,其中每一个字符都代表了有向树的一个结点。而前一个字符就是后一个字符的父结点,后一个字符是前一个字符的子结点。这也意味着,每增加一个字符,其实就是在当前字符结点下面增加一个子结点,相应地,树的高度也增加了 1。

我们以单词 geek 为例,从根结点开始,第一次我增加字符 g,在根结点下增加一个“g”的结点。第二次,我在“g”结点下方增加一个“e”结点。以此类推,最终我们可以得到下面的树。

那如果这个时候,再增加一个单词,geometry 会怎样?我们继续重复这个过程,就能得到下面这个图。

到这里为止,我们已经建立了包含两个单词的前缀树。在这棵树的两个叶子结点“k”和“y”上,我们可以加上额外的信息,比如单词的解释。那么在匹配成功之后,就可以直接返回这些信息,实现字典的功能了。假设我把牛津词典里所有的英文单词都按照上述的方法处理一遍,就能构造一棵包含这个字典里所有单词的前缀树,并实现常用单词的查找和解释。

2. 查询前缀树

假设我们已经使用牛津词典,构建完了一个完整的前缀树,现在我们就能按照开篇所说的那种方式,查找任何一个单词了。从前缀树的根开始,查找下一个结点,顺着这个通路走下去,一直走到某个结点。如果这个结点及其前缀代表了一个存在的单词,而待查找的单词和这个结点及其前缀正好完全匹配,那就说明成功找到了一个单词。否则,就表示无法找到。

这里还有几种特殊情况,需要注意。

如果还没到叶子结点的时候,待查的单词就结束了。这个时候要看最后匹配上的非叶子结点是否代表一个单词;如果不是,那说明被查单词并不在字典中。

如果搜索到前缀树的叶子结点,但是被查单词仍有未处理的字母。由于叶子结点没有子结点,这时候,被查单词不可能在字典中。

如果搜索到一半,还没到达叶子结点,被查单词也有尚未处理的字母,但是当前被处理的字母已经无法和结点上的字符匹配了。这时候,被查单词不可能在字典中。

前缀树的构建和查询这两者在本质上其实是一致的。构建的时候,我们需要根据当前的前缀进行查询,然后才能找到合适的位置插入新的结点。而且,这两者都存在一个不断重复迭代的查找过程,我们把这种方式称为深度优先搜索(Depth First Search)。

所谓树的深度优先搜索,其实就是从树中的某个结点出发,沿着和这个结点相连的边向前走,找到下一个结点,然后以这种方式不断地发现新的结点和边,一直搜索下去,直到访问了所有和出发点连通的点、或者满足某个条件后停止。

如果到了某个点,发现和这个点直接相连的所有点都已经被访问过,那么就回退到在这个点的父结点,继续查看是否有新的点可以访问;如果没有就继续回退,一直到出发点。由于单棵树中所有的结点都是连通的,所以通过深度优先的策略可以遍历树中所有的结点,因此也被称为深度优先遍历

为了让你更容易理解,用下面这张图来展示在一棵有向树中进行深度优先搜索时,结点被访问的顺序。

其中,结点上的数字表示结点的 ID,而虚线表示遍历前进的方向,结点边上的数字表示该结点在深度优先搜索中被访问的顺序。在深度优先的策略下,我们从点 110 出发,然后发现和 110 相连的点 123,访问 123 后继续发现和 123 相连的点 162,再往后发现 162 没有出度,因此回退到 123,查看和 123 相连的另一个点 587,根据 587 的出度继续往前推进,如此类推。

把深度优先搜索,和在前缀树中查询单词的过程对比一下,你就会发现两者的逻辑是一致的。不过,使用前缀树匹配某个单词的时候,只需要沿着一条可能的通路搜索下去,而无需遍历树中所有的结点。

小结

在这一讲,从数学中图的一些基本定义入手,介绍了有向树,以及有向树的一个应用,前缀树。树在计算机领域中运用非常广泛。比如,二叉树和满二叉树。

二叉树是每个结点最多有两个子树的树结构,它可用于二叉查找树和二叉堆。二叉树甚至可以用于图示化我们之前聊过的二分迭代。

满二叉树是一棵高度为 n(高度从 1 开始计),且有 2^n-1 个结点的二叉树。在高度为 k(0<k<=n)的这一层上,结点的数量为 2^(k-1)。如果把树的根标为 0,每个结点的左子结点标为 0,每个结点的右子结点标为 1,那么把根到叶子结点的所有 0 或 1 连起来,就正好对应一个二进制数。

既然树是如此重要,那么我们该如何高效率地访问树中的结点呢?下一讲,会继续前缀树的话题,讨论如何遍历树中所有结点。

思考题

现在给你一个字典,请尝试实现其前缀树,包括树的构建和查询两个过程。这里,字典可以用字符串数组来表示,每个字符串代表一个单词。



本文深入介绍了有向树和前缀树的构建与查询过程,以及利用深度优先搜索实现快速字典查询的方法。首先从数学中图的基本定义入手,引出了有向树及其在计算机领域的广泛应用。文章详细讲解了前缀树的构建过程,并通过图示和实际例子生动地解释了前缀树的查询过程。此外,还提出了思考题,鼓励读者尝试实现前缀树并分享学习笔记。整体而言,本文内容丰富,深入浅出,适合对树结构和深度优先搜索感兴趣的读者阅读学习。 

  • 28
    点赞
  • 19
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
蒙特卡洛搜索(Monte Carlo Tree Search,MCTS)是一种用于决策制定的算法,它在许多领域中得到广泛应用,包括游戏和优化问题。在Matlab中,你可以使用以下步骤实现MCTS算法: 1. 定义游戏状态表示:根据你的具体问题,定义一个数据结构来表示游戏状态。这个数据结构应该包含游戏当前的状态信息,例如棋盘、玩家的位置等。 2. 实现选择函数:选择函数用于在搜索中选择下一个节点。它基于已有的节点评估指标选择下一个节点进行扩展,通常使用Upper Confidence Bound(UCB)公式来进行选择。 3. 实现扩展函数:扩展函数用于在搜索中扩展当前节点。它根据当前节点的状态生成所有可能的子节点,并将它们加入到搜索中。 4. 实现模拟函数:模拟函数用于模拟游戏的进行,直到达到终止状态。在每次模拟中,使用随机策略选择动作,直到游戏结束。 5. 实现回溯函数:回溯函数用于更新搜索中节点的评估指标。当一次模拟完成后,将获胜的结果反向传播到搜索的根节点,并更新每个节点的评估指标。 6. 实现主函数:主函数用于协调以上步骤,根据一定的迭代次数或时间限制进行多次搜索,最终选择最好的动作。 这只是一个简单的介绍,具体的实现细节会根据你的具体问题而有所不同。你可以根据以上步骤在Matlab中实现一个基本的MCTS算法,并根据需要进行优化和扩展。希望对你有所帮助!

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值