AI中的几种搜索算法---Tabu搜索算法
引言
Tabu相对于启发式算法家族中其他成员,要简单易懂的多。关于启发式算法的基本概念可以参见笔者的《AI中的几种搜索算法---A*搜索算法》。这里就不多介绍了。
一、Tabu搜索算法的基本介绍
1.局部最优和全局最优
局部最优(Local Optimums)和全局最优(Global Optimums)这对概念在搜索中,经常被提到。所以在这里,先对其做一个简单介绍。
我们假设一个搜索算法,将访问的结点(cur_node)与当前最优结点(best_node)进行比较,如果cur_node优于best_node,则进行替换;否则继续访问下一个结点。
如果用上述的算法进行搜索,我们得到的结果便是局部最优。
在一个搜索领域中,有一个结点A,是这个领域中的最优结点,我们访问过A之后,因为在A的邻接结点中找不到更好的结点,所以我们的搜索会在A的周围不停地“徘徊”,无法去探索新的搜索领域。这样我们就陷入了局部最优的泥沼。
这里Tabu搜索算法,降低对邻接结点的要求,使其逃离了局部最优。
2.Tabu List
Tabu List是一个“先进先出”(First In ,First Out)的队列,保存了已经被“估值”的结点,这样就会避免重复估值,并且防止了因为挑选了同一个最优邻接结点而使算法陷入局部的死循环。
我们可以为Tabu List设定一个容量限制,如果当Tabu List满了之后,将会把最旧的结点给移除。
3.Tabu算法的流程
大致描述一下这个流程:
1. 首先便是初始化工作,创建tabu list,然后获得初始结点(随机获得)。
2. 对初始结点进行估值,得到它的“消耗”,设置为当前最小消耗best_cost,并将初始结点设置为当前最优结点best_node,然后将初始结点放入tabu list中。
3. 遍历当前最优结点的邻接结点,并挑选不在tabu list中的最优邻接结点(best_neighbor)。
4. 如果tabu list容量已满,则移除其中最旧的结点。
5. 将best_neighbor放入tabu list中。
6. 将best_neighbor与best_node进行比较。如果best_neighbor优于best_node,则进行替换。
7. 判断best_node是否是目标结点,如果是,结束算法;否则继续步骤3。
下面便是一个伪代码的流程:
Tabu_list = CreateTabuList();
CurNode = RandNde();
Cacaulate(CurNode);
BestNode = CurNode;
Tabu_List.push(Best_Node);
While(true)
{
CurNode= best_neighbor_not_ontabulist(CurNode);
If(Tabu_List.IsFull())Tabu_List.deQueue();
Cacaulate(CurNode);
If(CurNode.cost< BestNode.cost)
BestNode = CurNode;
If(BestNode.cost== 0)
Break;
}
二、N皇后问题
NQueen问题一直是算法界的一个经典案例。
1.N皇后问题简介
N皇后问题,是在一个N*N的方格正方形中,要放入N个棋子,并且保证每一个棋子的水平方向、竖直方向和斜方向上都没有第二个棋子。
解决N皇后问题的算法有很多,这里我们就用刚介绍的tabu算法进行解决。之后我会专门写一篇N皇后问题,用不同算法进行求解,并比较不同算法在这个问题上的性能。
2.代码
这里附上Tabu解决NQueen问题的核心算法代码。
其中NQBoard是一个自定义的结构体,其中包含一个数组,表示棋盘。
randBoard这个函数可以随机获得一个已经放入了N个皇后的初始棋。calucateConflict这个函数可以计算棋盘中皇后的冲突量,就是算法中的cost。getBestNeighbot能获得当前棋盘不在tabulist中的最后邻接结点。
有兴趣的读者可以去Tabu搜索算法解决N皇后问题
int tabu_nqueens(NQBoard & solution,int nQueens)
{
NQBoard neighbor;
neighbor.board = newint[nQueens];
memset(neighbor.board,0,sizeof(int)*nQueens);
randBoard(solution,nQueens);
memcpy(neighbor.board,solution.board,sizeof(int)*nQueens);
int bestCost= calucateConflict(solution,nQueens);
int depth =0;
tabulist.enQueue(solution);
//
while(true)
{
intcurCost = getBestNeighbor(neighbor,nQueens);
if(!tabulist.isOnList(neighbor))
{
//
//在nQueens超过十的时候,可以考虑去掉以下代码
/*if (tabulist.isFull())
{
tabulist.deQueue();
}*/
//
tabulist.enQueue(neighbor);
tabulist.enQueue(neighbor);
}
if(curCost<= bestCost)
{
bestCost = curCost;
memcpy(solution.board,neighbor.board,sizeof(int)*nQueens);
}
if(bestCost== 0)
break;
++depth;
}
delete[]neighbor.board;
return depth;
}
3.程序运行截图
三、总结
当nQueen变得超过14的时候,程序找出结果变很慢,随着nQueen增长,速度越发慢。
这个算法的程序我写的还不够完善,如果好心的读者找到程序中不合理的地方,可以来这里留言,随时欢迎。
如果有兴趣的可以留言,一起交流一下算法学习的心得。
接下来我会发表介绍几种搜索算法。
声明:本文章是笔者整理资料所得原创文章,如转载需注明出处,谢谢。