转-C# A*算法实现8数或者15数问题
-、问题描述
8数或15数问题是同一个问题,其就是一个随机排列的8个或15个数在一个方正格子中移动到达规定的一个目标状态。由于只有一个空格子,故只有在空格附近的棋子可以移动。
二、算法描述
F 算法选择
此问题需要对所有可能的路径进行穷举,但是由于随着树的高度的加大,其子结点的增加宿舍剧增,所以对所有的子结点进行穷举是不大现实的。而根据当前的状态和目标状态进行对比可以用一个评估函数来评估当前状态的好坏情况。而在这里我们选择A*算法来进行求解,A*算法是一种最好优先的算法。f'(n) = g'(n) + h'(n),f'(n)是估价函数,g'(n)是起点到终点的最短路径值,这里就表示树的高度,h'(n)是n到目标的最断路经的启发值,其原理是把当前产生的状态和以前所以的状态的评估函数值进行对比,选择其中最小的评估函数继续进行下一步。这里我选择h'(n)的启发值为每个格子到达它的目标位置所需要经过的最少步数。
F 算法描述
需要说明的几点:
1. 用OpenArr表示初始节点列表(待扩展,此为一个动态数组)
2.ClosedArr保存已经访问过的结点
3.算法首先需要给出初始状态,由于随机产生的状态并不一定能够走到目标状态,因此这里采用从标准状态往回走产生一个被打乱的随机状态,这样可以保证有解。
算法实现:
1. OpenArr放置初始结点
2. 如果OpenArr为空集,则退出并给出失败信号
3. n取为OpenArr的第一个节点,并从OpenArr中删除节点n
4. 如果n为目标节点,则退出并给出成功信号
5. 否则,将产生n子节点,并对n的每个子结点n’ 进行如下操作:
1)如果n’ 不在OpenArr和ClosedArr表中,则把n’放入OpenArr表中
2)否则,如果在OpenArr表中,计算评估值,并且如果比表中的评估函数的值小,则更新表中的评估值为当前的值。
3)否则,如果在ClosedArr表中,计算评估值,并且如果比表中的评估函数的值小,则把表中的评估值更新为当前的值,并把该结点从表中删除,并在OpenArr表中加入该结点。
6、把n结点加入ClosedArr中
7、对OpenArr进行排序(根据评估函数从小到大),并转到2。
三、程序设计
算法使用C#语言来实现的。程序主要根据上面提供的广度优先算法的描述来对算法进行实现的。程序共有四个类:
StepNode类,用来描述产生的各个结点的属性以及方法等
Heuristic_15Num_Search类,算法实现类
Form1类,是界面设计的类。
这里分别提供了8数和15数问题的求解。并显示了所经历过的各个状态转移
以下主要对几个核心算法的程序实现进行说明介绍。
//0 数字上移
private void MoveUp(Position p)
{
if(p.x>=1)
{
StepNode node = new StepNode();
To(this,node);
Position p1 = new Position(p.x-1,p.y);
AddChild(node,p,p1);
}
}
// 0 数字下移
private void MoveDown(Position p)
{
if(p.x<=text_v.Length-2)
{
StepNode node = new StepNode();
To(this,node);
Position p1 = new Position(p.x+1,p.y);
AddChild(node,p,p1);
}
}
//0 数字左移
private void MoveLeft(Position p)
{
if(p.y>=1)
{
StepNode node = new StepNode();
To(this,node);
Position p1 = new Position(p.x,p.y-1);
AddChild(node,p,p1);
}
}
//0 数字右移
private void MoveRight(Position p)
{
if(p.y<=text_v.Length-2)
{
StepNode node = new StepNode();
To(this,node);
Position p1 = new Position(p.x,p.y+1);
AddChild(node,p,p1);
}
}
//计算节点的启发函数的值
private void ComputeGeuristicGeneVal()
{
int geneVal = this.Height ;
int g = 0; //启发因子 每个数据到达标准位置需要走过的最少步数
for(int i=0;i<text_v.Length;i++)
{
for(int j=0;j<text_v[0].Length;j++)
{
Position p1 = GetPosition(text_v[i][j]);
Position p2 = GetPosition(aim[i,j]);
int xd = p1.x > p2.x ? p1.x - p2.x : p2.x - p1.x ;
int yd = p1.y > p2.y ? p1.y - p2.y : p2.y - p1.y ;
g += xd + yd ;
}
}
geneVal += g;
this.Heuristic_Gene = geneVal;
}
//Heuristic_15Num_Search类
//核心算法实现部分
//循环搜索匹配
private void LoopSearch(StepNode node)
{
while(OpenArr.Count>0)
{
node = (StepNode)OpenArr[0];
OpenArr.Remove(node);
//如果是目标节点则停止搜索
if(node.IsAim())
{
SetPath(node);
return;
}
else
{
//产生子节点。
node.CreateChildren();
//对每个子节点进行检查
foreach(StepNode i in node.Children)
{
//如果不在open和close表中。
if( Contain(ClosedArr,i)==-1 && Contain(OpenArr,i)==-1 )
{
OpenArr.Add(i);
}
//如果在open表中
else if(Contain(OpenArr,i)!=-1)
{
StepNode n = (StepNode)OpenArr[Contain(OpenArr,i)];
//如果i的估计值小于open表中的估计值则替换表中的估计值
if(i.Heuristic_Gene<n.Heuristic_Gene)
{
n.Heuristic_Gene = i.Heuristic_Gene;
}
}
//如果在close中。
else
{
StepNode n = (StepNode)ClosedArr[Contain(ClosedArr,i)];
//如果i的估计值小于closed表中的估计值则替换表中的估计值
if(i.Heuristic_Gene<n.Heuristic_Gene)
{
n.Heuristic_Gene = i.Heuristic_Gene;
ClosedArr.RemoveAt(Contain(OpenArr,i));
OpenArr.Add(n);
}
}
}
//节点加入Closed表中 表示已经访问过了。
ClosedArr.Add(node);
//对节点进行排序
OpenArr.Sort(new MyComparer());
}
}
//理论上不可能出现这种情况
path = "Not Found @!";
return;
}
四、试验结果
1)8数问题搜索路径如下图
Generate 是用来随机产生一个初始状态(46) 表示从标准状态见过随机的46步后产生的一个随机的初始状态。这里46是一个随机数,由于该数越大离目标越远,搜索越复杂故将此随机数控制在0-100之间。3表示3的方正即8数问题,4表示4的方正即表示15数问题。
2)15数问题搜索路径如下图
从以上结果来看,由于使用了启发式的搜索过程,因此大大加快的搜索的速度以及能尽可能经过最少的路径最快到达目标状态,由于8数问题比较简单因此搜索速度较快,而15数问题复杂度加大,当随机产生的数接近100的时候搜索的时间迅速变慢,故算法还待改进,主要是因为随着深度的加深,OpenArr和CloseArr表中的数据迅速扩大,故可以考虑把OpenArr表中的状态数进行选择性的排除一些,比如每次把OpenArr中表的数据经过排序后可以删除最后的几个最差的状态,这样在一定程度上提高了速度但是也减低了找到最优解的几率不过这种减低是非常小的,因为被排除的已经是最差的情况了。