经典搜索算法总结-程序员宅基地

技术标签: 算法  算法与数据结构  

前言

搜索问题是在解决各类问题时不可避免的重点难点,很多问题的求解过程都可以转变为搜索问题。比如,对于以下罗马尼亚问题,希望找到一条路径使得从城市 Arad 到城市 Bucuresti 的路径最短,这就是一个经典的搜索问题,在数据结构课程中,我们都知道使用 Dijkstra 算法来求得最优解,受《人工智能 一种现代化的方法》这本书的影响,本博客则以此为基石通过更一般的方法来讨论通用的搜索问题的求解思路。
在这里插入图片描述


0x1 搜索问题的形式化

一个搜索问题可以通过五个元素给出形式化的定义:

  • 初始状态。状态用 s 表述,初始状态也是状态的一种,上述罗马尼亚问题的初始状态可以描述为 In(Arad)
  • 可能的行动。行动用 a 表述,对于一个特殊状态 sACTIONS(s) 返回在状态 s 下可以执行的动作集合。例如,在状态 In(Arad) 下可能的行动为: {Go(Sibiu), Go(Timisoara), Go(Zerind)}
  • 转移模型。转移模型用 RESULT(s, a) 表述,表示在状态 s 下执行行动 a 后达到的状态。通常也用术语后继状态来表示从一给定状态出发通过单步行动可以到达的状态集合。例如:RESULT(In(Arad), Go(Zerind)) = In(Zerind)
  • 目标测试。确定给定的状态是不是目标状态。在罗马尼亚问题中,目标状态集是一个单元素集合 {In(Bucuresti)}
  • 路径代价。采用行动 a 从状态 s 走到状态 s’ 所需要的单步代价表述为 c(s,a,s'),路径代价就代表从初始状态到目标状态的单步代价总和。

通过形式化的定义搜索问题,则搜索问题的解描述为一组让初始状态转变为目标状态的行动序列,而搜索问题的最优解描述为使得路径代价最小的一组让初始状态转变为目标状态的行动序列。


0x2 树搜索和图搜索

在对问题进行形式化后,需要对问题求解,而一个解就是一个行动序列,所以搜索算法的工作的就是考虑各种可能的行动序列。可能的行动序列从搜索树中根结点的初始状态出发,连线表示行动,结点对应问题的状态空间中的状态。

树搜索算法
例如在罗马尼亚问题中,从父结点 In(Arad) 出发得到三个子结点:In(Sibiu)In(Timisoara)In(Zerind),接下来需要继续从这三种子结点中选择其一继续考虑,假设我们选择 In(Sibiu) ,则检查它是否是目标状态,如果不是目标状态,则继续扩展它得到四个状态:In(Arad)In(Fagaras)In(Oradea)In(Rimnicu Vikea)。继续按此方式拓展 In(Timisoara)In(Zerind) 。如果未找到目标状态,则继续对叶子结点作同样的操作,如下图:
在这里插入图片描述

此时引入两个概念:
frontier 结点:frontier 结点是指所有还没有扩展的搜索结点
explored 结点:explored 结点是指所有已经扩展的搜索结点

注意到在拓展 In(Sibiu) 的时候,将 In(Arad) 结点也加入了 frontier 集合,而此时 In(Arad) 结点已经是拓展过的结点,已经加入了 explored 集合。同时在扩展 In(Zerind) 结点的时候,将 In(Oradea) 结点也加入了 frontier 集合,而此时 In(Oradea) 结点本身已经在 frontier 集合中了,这个特别也是树搜索的精髓所在。树搜索的算法伪代码如下:
在这里插入图片描述
图搜索算法
对于上述罗马尼亚问题,采用图搜索算法生成的树如下:
在这里插入图片描述
在图搜索算法中,在扩展的时候如果遇到已经在 explored 集合中的结点或已经在 frontier 集合中的结点,则不将该结点拓展进 frontier 集合,这也是其与树搜索算法的主要区别。图搜索的算法伪代码如下:
在这里插入图片描述


0x3 搜索算法的评估

通常一个搜索算法用三个指标来进行评估:

  • 完备性:当问题有解时,这个算法是否可以保证找到解?
  • 最优性:当问题有解时,这个算法是否可以找到使得路径代价最小的解?
  • 复杂性:时空复杂度。

在搜索算法中,是什么决定了搜索策略?
由于搜索算法重复的从 frontier 集合中选择和移除一个结点,并且拓展该结点并且将拓展结点插入到 frontier 集合中。如果每次从 frontier 集合的固定位置选取结点,则搜索策略由结点在 frontier 中的顺序决定


0x4 盲目搜索策略

盲目搜索算法不会考虑结点离目标有多远,除了问题定义中提供的状态信息外没有任何其他附加信息!

0x4.1 宽度优先搜索算法BFS

宽度优先搜索算法(图搜索)的思路就和数据结构中的 BFS 是一个东西。其首先在 frontier 集合中找到最先进入该集合的结点,然后将该结点加入到 explored 集合,同时拓展该节点得到他的每个子结点,对于每个子结点,如果该子结点不在 frontier 集合和 explored 集合中且不是要找到目标结点,则将子结点加入到 frontier 集合中,如此循环直到 frontier 集合为空或者找到目标结点为止。BFS(图搜索)算法伪代码如下:
在这里插入图片描述
显然提前测试可以减少遍历的结点数,如果等到从 frontier 集合中弹出时再测试,那么需要拓展很多不必要的结点。通过将 frontier 集合设计成队列,采用先进先出的策略则可以实现 BFS。
BFS 算法评估(设分支因子为 b,最浅的目标结点的深度为 d):

  • 完备性:当树的分支因子 b 是有限的,则算法是完备的。
  • 最优性:如果路径代价是基于结点深度的非递减函数,则算法是最优的,否则不具备最优性。
  • 复杂性:显然时空复杂度均为 O(bd)

0x4.2 一致代价搜索算法UCS

一致代价搜索算法(图搜索)的思想就类似于 Dijkstra 算法。其首先在 frontier 集合中找到当前代价最低的结点,判断该结点是否是目标结点,如果是,则返回解,否则将该结点加入到 explored 集合中,然后拓展该结点得到他的每个子结点。对于每个子结点,如果该子结点不在 frontier 集合和 explored 集合中,则将该子结点加入到 frontier 集合中;否则如果该子结点在 frontier 集合中,但是其代价比已经在 frontier 集合中的低,则用新生成的子结点代替原来 frontier 集合的子结点。UCS(图搜索)算法伪代码如下:
在这里插入图片描述
UCS 在结点被拓展时才进行目标检测,这是与 BFS 最大的区别,因为第一次生成的目标的结点可能在次优路径上。通过将 frontier 集合设计成优先级队列,则可以实现 UCS。
UCS 算法评估(设分支因子为 b,某个小的正值 ξ,最优解的代价 C*):

  • 完备性:如果存在零代价的行动则可能陷入死循环,但如果每一步的代价都大于等于 ξ,则算法是完备的。
  • 最优性:显然具有最优性
  • 复杂性:时空复杂度均为 O(b^(1+ ⌊ C ∗ / ξ ⌋ \lfloor C^*/ξ \rfloor C/ξ))

0x4.3 深度优先搜索算法DFS

深度优先搜索算法(图搜索)的思路类似于数据结构中的DFS。其基本思想是首先在 frontier 集合中找到最后进入该集合的结点,然后将该结点加入到 explored 集合,同时拓展该节点得到他的每个子结点,对于每个子结点,如果该子结点不在 frontier 集合和 explored 集合中且不是要找到目标结点,则将子结点加入到 frontier 集合中,如此循环直到 frontier 集合为空或者找到目标结点为止。DFS 和 BFS 最大的区别在于对 frontier 集合中结点的选取,BFS 中对结点的选取符合先进先出的策略,而 DFS 中对结点的选取则符合先进后出的策略,所以 DFS 可以用栈来实现其 frontier 集合。DFS 算法有一个很重要的特别就是其及其依赖采取的是树搜索还是图搜索,因为树搜索每次都可能会加入重复的结点,这样就会导致在两个重复的结点之间无线循环。
DFS 算法评估(设分支因子为 b,最浅的目标结点的深度为 d,最深的叶子结点为 m):

  • 完备性:对于树搜索,DFS 总是不完备的;而对于图搜索,DFS 在有限状态空间下是完备的,而在无限状态空间下是不完备的。
  • 最优性:显然不是最优的
  • 复杂性:时间复杂度为 O(bm),空间复杂度为 O(bm),可见其空间复杂度比上述两种搜索小很多,这也是深度优先搜索最大的优势。

0x4.4 深度受限搜索DLS

对于无限状态空间深度优先搜索算法总是会失败,而这个问题可以通过对深搜设定界限 l 来避免,也就是说在深搜的,当深度达到了 l ,则采取舍弃更深结点的策略。其算法可以采用基于递归的方式来实现,算法的伪代码如下:
在这里插入图片描述
DLS 算法评估(设分支因子为 b,限定的深度为 l,最浅的目标结点的深度为 d):

  • 完备性:显然当 l < d 的时候,DLS 则总是不完备的。
  • 最优性:即使当 l > d 的时候,根据 DFS 算法,DLS 也不是最优的。
  • 复杂性:时间复杂度为 O(bl),空间复杂度为 O(bl)

0x4.5 迭代加深搜索IDS

在 DLS 算法中,我们无法选取一个合适的深度 l 来求解,这就是 IDS 的动机,在未找到解之前不断的增加 l ,迭代的使用 DLS 算法,当 l 达到最浅的目标结点的深度 d 时,则可以求出解。其算法伪代码如下:
在这里插入图片描述
IDS 算法评估(设分支因子为 b,限定的深度为 l,最浅的目标结点的深度为 d):

  • 完备性:当分支因子是有限的的时候,IDS 是完备的
  • 最优性:类似于宽度优先搜索算法,如果路径代价是基于结点深度的非递减函数,则算法是最优的。
  • 复杂性:时间复杂度为 O(bd),空间复杂度为 O(bd)

0x4.6 双向搜索算法

双向搜索算法思想则是同时运行两个搜索——一个从初始状态向前搜索同时另外一个从目标状态向后搜索——希望他们在中间某个结点相遇,此时搜索停止。理由是,比如两边都采用宽度优先搜索,则显然 bd/2+bd/2 要远小于 bd

0x4.7 六种搜索算法的对比

在这里插入图片描述


0x5 启发式搜索策略

在盲目搜索策略中,往往不考虑结点离目标有多远,也就是说,在搜索的过程中,并不考虑目标的状态对当前搜索行为的影响,而仅仅依赖于当前的状态;比如在罗马尼亚问题中,从城市 Arad 到城市 Bucuresti 的搜索,仅仅只考虑在当前状态下可能导致的代价,该策略虽然能搜索到最优解,但是在搜索过程中可能会有很多不必要的操作,因为搜索的结果只关注那条最优的路径,但是实际在搜索的过程中可能会搜索到与最优路径无关的结点。而启发式搜索策略则不一样,他会考虑目标状态对当前状态的影响,这样做使得在搜索的过程中更大的可能搜索和最优路径相关的状态,减少不必要的搜索,增加搜索的效率,比如说在罗马尼亚问题中,可以在考虑代价的同时,还考虑当前结点离目标结点的直线距离来选取下一个要拓展的结点,而这个直线距离就作为启发式的信息来帮助搜索的决策,以此更快地搜索到最优路径。
在启发式搜索中,通常用 g(n) 表示结点 n 当前的代价,用 h(n) 表示启发函数,一般来说 h(n) = 结点 n 到目标结点的最小代价路径的代价估计值(比如在罗马尼亚问题中可以用 AradBucuresti 的直线距离来作为从 AradBucuresti 的最小代价路径的代价估计值),用 f(n) 作为选择结点的代价评估值,f(n)=g(n)+h(n) ,当 f(n) 越小,则代表结点越优;启发式搜索对扩展结点的选择同时依赖于 g(n) 和 h(n),对于 h(n) 较小的结点,我们可以认为该结点会让我们离目标结点更近,所以 h(n) 作为启发式的信息能帮助我们在搜索的时候做出决策。

0x5.1 贪婪最佳优先搜索GBFS

贪婪最佳优先搜索算法试图扩展离目标最近的结点,理由是这样可能可以很快找到解,因此,它只用启发式信息,即 f(n)=h(n)。还是对于上述的罗马尼亚问题,通过 GBFS 算法求解的路径如下,可见该算法并不是最优的。
在这里插入图片描述
GBFS 算法评估(设分支因子为 b,最深的叶子结点为 m):

  • 完备性:当状态空间是无限的时候,GBFS 是不完备的;当状态空间是有限的,图搜索的 GBFS 算法是完备的,而树搜索的 GBFS 算法是不完备的。
  • 最优性:显然不是最优的。
  • 复杂性:时间复杂度为 O(bm),空间复杂度为 O(bm),因为要保存所有的结点在内存中。

0x5.2 A*搜索

在 GBFS 算法中,仅仅采用启发式作为评估值,导致不能求到最优解,A* 搜索算法同时考虑到代价,以期解决 GBFS 算法中的问题,因此 A* 搜索采用的代价评估值 f(n) 为 h(n) 与 g(n) 的和,其算法伪代码的实现如下:
在这里插入图片描述
A* 算法一定能找到最优的路径吗?考虑如下的例子:
在这里插入图片描述
假设从 S 走到 G ,那么在 S 结点扩展后,frontier 里面剩下 A(其代价估计 f(A) 为 7)和 G (其代价估计 f(G) 为 5),那么根据 A* 算法,会从 frontier 里面选取 G,此时得到解,但可以发现在这种情况下显然求到的不是最优解,这是因为在 A 结点出其启发式的值过于高,明显的大于其到 G 结点的真是代价,导致产生不准确的估计,所以我们的思路就是降低 A 结点的 h 到一个合适的值,在此处若 h(A) 比 4 小的话则可以根据 A* 算法求到最有解。此处引入一个新的概念——可采纳性

  • 可采纳性:假设 h*(N) 是从 N 结点到目标结点的最优路径的代价, 当启发式函数 h(N) 满足以下不等式的时候,我们称该 h(N) 是可采纳的 (admissible) ——0 ≤ \le h(N) ≤ \le h*(N)

在引入可采纳性后,A* 算法在树搜索和图搜索下都具有最优性吗?同样考虑以下的例子:
在这里插入图片描述
该问题的启发式函数是可采纳的,假设从 A 走到 E,可以发现在树搜索的情况下找到最优解,而在图搜索的情况下却没有找到最优解。这是因为图搜索在遇到已经加入到 explored 集合的结点的时候,会直接不考虑该结点,导致可能不能修正之前扩展带来的错误,比如在上例中,树搜索在扩展右边的 D 结点后,在扩展 B 结点的时候将左边的 D 结点加入到了 frontier 里面,从而有机会修正一开始扩展的右边的 D 结点,使得算法找到最优解;而图搜索由于在第一次扩展 D 结点后,其加入到 explored 结点,导致第二次遇到估计代价更低的 D 结点时,直接将其抛弃而不加入到 frontier 结点,所以会找不到最优解。从这里可以看出,在图搜索算法中,如果结点被加入到了 explored 集合,则算法认为当前在 explored 集合中的路径一定会是最优路径的子路径(类似于 UCS 算法),其实并不是,这就导致无法像树搜索那行修正路径导致无法找到最优解。但是,如果给启发式函数加入比可采纳性更强的约束,会不会使得图搜索也是最优的呢?此处引入一个新的概念——一致性

  • 一致性:如果对于每个结点 N 和通过任一行动 a 生成的 N 的每个后继结点 N’,从结点 N 到达目标的估计代价不大于从 N 到 N’ 的单步代价与从 N’ 到达目标的估计代价之和,我们称启发式函数 h(N) 具有一致性 。即满足不等式 —— h(N) ≤ \le c(N, a, N’)+h(N’) 。显然一致性会使得启发式函数更小,也就说一致的启发式函数一定是可采纳的。

当启发式函数满足一致性的时候,则 A* 图搜索算法是最优的。

A* 算法评估(设 C* 是最优解路径的代价值,分支因子为 b,最浅的目标结点的深度为 d):

  • 完备性: A* 算法会扩展所有代价评估值 f(n) 小于 C* 的结点,但不会扩展代价评估值 f(n) 大于 C* 的结点,所以当代价小于等于 C* 的结点是有穷的,则 A* 算法是完备的。
  • 最优性:A* 算法的最优性依赖于其是树搜索还是图搜索,启发式函数是否是可采纳的,一致的。如下图:

在这里插入图片描述

  • 复杂性:在所有的可以找到最优解的搜索算法里面,A* 搜索的效率是最高的,因为这些算法里面他们都会扩展所有代价评估值 f(n) 小于 C* 的结点,但是 A* 算法一定不会扩展代价评估值 f(n) 大于 C* 的结点,这样使得 A* 算法访问的结点更少,求解的速度更快。时间复杂度和空间复杂度为 O(bd)

由以上讨论可知,A* 算法对于任何给定的一致的启发式函数都是效率最优的,所以设计 A* 算法,最关键的就是设计启发式函数 h(n) 。

既然让启发式函数值更小,会让 A* 算法更可能达到一致性,那是不是启发式函数值越小越好呢?显然不是,对于两个一致的启发式函数 h1 和 h2 ,若 h1(N) > h2(N) ,我们则称 h1 更准确,采用 h1 会使得求解过程更快。也就是说我们期望的启发式函数,是在满足一致性的前提下,越大越好!!具体的设计启发式函数的时候可以从松弛问题出发,也可以从子问题出发,也可以从学习的角度出发,具体细节的在此处就不讨论了(逃),可以参考由我们任课老师翻译的 AI 原理神书 —— 《人工智能 一种现代的方法》!

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/qq_21746331/article/details/117405789

智能推荐

OpenSessionInViewFilter与Write operations are not allowed in read-only mode 独门问题解决之道_opensessioninview的时候数据库onlyread-程序员宅基地

文章浏览阅读647次。博主大三在实验室项目中使用 Spring 整合 Hibernate, 在懒加载的情况下,会出现一个异常,但为了查询效率,又需要利用懒加载用于关联表,查阅了网上资料说是在web.xml文件中加入一个过滤器OpenSessionInViewFilter如下方式 OpenSessionInViewFilter org.springframework.orm.hibe_opensessioninview的时候数据库onlyread

pdb模式下键用户_oracle12c 查询pdb模式下创建的用户-程序员宅基地

文章浏览阅读634次。,参考https://www.cnblogs.com/siyunianhua/p/4004361.html,模式切换为PDB1)查看版本SQL> select * from v$version;BANNER CON_ID------------------------------..._oracle12c 查询pdb模式下创建的用户

Android初级开发第八讲--ListView学习以及特性介绍-程序员宅基地

文章浏览阅读117次。博客出自:http://blog.csdn.net/liuxian13183,转载注明出处! All Rights Reserved !一般,安卓程序中要实现列表数据会用到ListView和GridView,相似度很高,今天主要拿前者来讲述。咱们先看代码private List<Object> data; private Context..._讲一下listview的特点??

这些Vue框架用起来很舒服_ionic vue 目前开发成熟吗-程序员宅基地

文章浏览阅读8.6k次,点赞63次,收藏289次。VUE3框架网站????导读????Vuestic????Ant Design Vue????Element+????Ionic????Naive UI????PrimeVUE????Wave UI最重要的事????导读最近再写web项目的时候找了一些个不错的VUE3的框架,好了我们废话不多说来看看这些优秀的资源怎么用我就不说了。里面的导航介绍都有而且都很详细。来吧我们开始今天的肥学。????Vuestic链接:传送门这款可以说是very beautiful了,哎呀忘了给大家贴一个动态_ionic vue 目前开发成熟吗

java中token原理及生成使用机制_java中token用法-程序员宅基地

文章浏览阅读7.1k次,点赞2次,收藏19次。常用认证机制介绍1、HTTP Basic AuthHTTP Basic Auth简单点说明就是每次请求API时都提供用户的username和password,简言之,Basic Auth是配合RESTful API 使用的最简单的认证方式,只需提供用户名密码即可,但由于有把用户名密码暴露给第三方客户端的风险,在生产环境下被使用的越来越少。因此,在开发对外开放的RESTful API时,尽量避免采用HTTP Basic Auth这种认证方法的优点是简单,容易理解。缺点有:不安全:认证身份信_java中token用法

前言技术之Oauth2全方面介绍-程序员宅基地

文章浏览阅读3.8k次,点赞9次,收藏21次。一、Oauth2基本概念1、定义Oauth2是目前最流行的授权机制,用来授权第三方应用,获取用户数据2、场景带入1、外卖场景一户人家住在一个大型居住小区,小区有门禁系统,进入小区需要输入密码,这户人家经常性点外卖,必须让外卖人员进入到小区,如果把密码告诉外卖人员,这样的话,他就和户主拥有了一样的权限,这样安全隐患太大,给了外面人员密码之后,为了安全,必须要进行修改密码,这就很麻烦。这时候Oauth2就诞生了,外卖人员的职责只是送货,没必要知道小区密码..._oauth2

随便推点

Pocsuite3--编写破壳CVE-2014-6271_Shellshock的POC-程序员宅基地

文章浏览阅读21次。前言编写破壳CVE-2014-6271_Shellshock的POC,把公开出来的路径封装起来,作为Pocsuite3的验证POC情况1:网站无法访问,返回失败情况2:网站可以访问,无漏洞情况3:网站可以访问,有漏洞优先获取网站本身的cgi路径,如果没有,就用自带的cgi路径测试。代码15分钟,35个站点#!/usr/bin/env python# coding: utf-8...

java创建image类_关于java:如何使用文件作为参数创建BufferedImage新类-程序员宅基地

文章浏览阅读653次。我想创建一个新类,因为我想创建一些新方法,该方法扩展了BufferedImage并接受一个文件(一个bmp图片)作为参数。就像这里发生的事情:BufferedImage image = ImageIO.read(new File(dir +"coffeecup.png"));浏览文档时,我发现BufferedImage类具有两个构造函数:公共BufferedImage(ColorModel cm,..._hutool创建文件 bufferedimage

eclipse中配置Tomcat_在eclpise里面怎么关闭tomcat服务器-程序员宅基地

文章浏览阅读2.5w次,点赞79次,收藏433次。将Tomcat服务器整合到Eclipse工具中,可以通过Eclipse启动、关闭tomcat服务器,更重要的是,可以非常方便的将在Eclipse中创建的Web项目发布到Tomcat服务器中运行。文章目录在这里插入图片描述方式一:在window偏好设置中配置Tomcat方式二:在创建Web项目时配置Tomcat三、将整合到Eclipse中的tomcat从Eclipse中删除四、在Eclipse中创建Server及移除Server五、tomcat右键选项介绍六、tomcat启动失败常见原因方式一:在w._在eclpise里面怎么关闭tomcat服务器

计算机网络学习-001_在如图所示的交换网络中,所有交换机都启用了 stp 协议。swa 被选为了根桥。根据图-程序员宅基地

文章浏览阅读780次。计算机网络学习001以下工作于OSI 参考模型数据链路层的设备是______。(选择一项或多项)A. 广域网交换机 B. 路由器 C. 中继器 D. 集线器Answer: A注: 交换机为L2层设备,路由器为L3层,集线器和中继器为L1层设备下列有关光纤的说法中哪些是错误的?A. 多模光纤可传输不同波长不同入射角度的光 B. 多模光纤的纤芯较细C. 采用多模光纤时,信号的最大传输距离比单模光纤长 D. 多模光纤的成本比单模光纤低Answer: BC注: 多模光纤_在如图所示的交换网络中,所有交换机都启用了 stp 协议。swa 被选为了根桥。根据图

Methacrylate-PEG-Rhodamine,Methacrylate-PEG-RB高分子相关知识的分享-程序员宅基地

文章浏览阅读50次。Methacrylate-PEG-Rhodamine,Methacrylate-PEG-RB的分子量:Methacrylate-PEG-Rhodamine 1k,Methacrylate-PEG-RB 2000,甲基丙烯酸酯-聚乙二醇-罗丹明 3400,甲基丙烯酸酯-聚乙二醇-罗丹明 MW:5000,Methacrylate-PEG-Rhodamine 10k,甲基丙烯酸酯-聚乙二醇-罗丹明 20000.罗丹明-聚乙二醇-炔基,MW:3400,Rhodamine-PEG-Alkyne。

转载 | 防止页面图片过大出现横滚动条问题-程序员宅基地

文章浏览阅读696次。- JS方法 -就是一小段JS加到页面中就可以,下面把这段代码贴出来:1 jQuery(document).ready(function () { 2 jQuery("body").attr("style","overflow-x:hidden"); 3 });- CSS方法 -一、防止图片撑破DIV方法一原始处..._图片超过屏幕高度出现滑轮怎么办

推荐文章

热门文章

相关标签