您的位置:首页 > 其它

马踏中国象棋棋盘的贪心算法

2009-06-29 18:18 190 查看
今天上午在网上看到了一篇介绍马踏棋盘的贪心算法的文章,就想照着把它实现。可是写到一半,发现原文好像是国际象棋的棋盘。对国际象棋的规则一窍不通,所以就硬着头皮按中国象棋的规则来写。还真的写出来了。

【问题描述】(国际象棋)
马的遍历问题。在8×8方格的棋盘上,从任意指定方格出发,为马寻找一条走遍棋盘每一格并且只经过一次的一条路径。

【问题描述】(中国象棋)
马的遍历问题。在9×10方格的棋盘上,从任意指定点出发,为马寻找一条走遍棋盘每一点并且每点只经过一次的一条路径。

下面是引用原文的关于贪心算法的介绍:

【贪心算法】
其实马踏棋盘的问题很早就有人提出,且早在1823年,J.C.Warnsdorff就提出了一个有名的算法。在每个结点对其子结点进行选取时,优先选择‘出口’最小的进行搜索,‘出口’的意思是在这些子结点中它们的可行子结点的个数,也就是‘孙子’结点越少的越优先跳,为什么要这样选取,这是一种局部调整最优的做法,如果优先选择出口多的子结点,那出口少的子结点就会越来越多,很可能出现‘死’结点(顾名思义就是没有出口又没有跳过的结点),这样对下面的搜索纯粹是徒劳,这样会浪费很多无用的时间,反过来如果每次都优先选择出口少的结点跳,那出口少的结点就会越来越少,这样跳成功的机会就更大一些。这种算法称为为贪心算法,也叫贪婪算法或启发示算法,它对整个求解过程的局部做最优调整,它只适用于求较优解或者部分解,而不能求最优解。这样的调整方法叫贪心策略,至于什么问题需要什么样的贪心策略是不确定的,具体问题具体分析。实验可以证明马遍历问题在运用到了上面的贪心策略之后求解速率有非常明显的提高,如果只要求出一个解甚至不用回溯就可以完成,因为在这个算法提出的时候世界上还没有计算机,这种方法完全可以用手工求出解来,其效率可想而知。
下面是我用C#实现的代码。

private int N = 9;
private int M = 8;
//方向指示
private int[] direct = { 0, 0, 0, 0, 0, 0, 0, 0 };
//棋盘每个点上的标识
public static int[,] board = new int[9, 10];
//马的规则,马走“日”字。
private int[] dx = { 2, 1, -1, -2, -2, -1, 1, 2 };
private int[] dy = { 1, 2, 2, 1, -1, -2, -2, -1 };
//取出所有的子结点的列表
private IList getChild()
{
IList list = new ArrayList();
for (int i = 0; i < 8; i++)
{
if (direct[i] == 1)
{
Hnode hnode = new Hnode();
hnode.x = x + dx[i];
hnode.y = y + dy[i];
list.Add(hnode);
}
}
return list;
}

//找出出口数数目最小的结点,放在链表的第一位。
public IList sort()
{
IList list = this.getChild();
int min = 0;
for (int i = 0; i < list.Count; i++)
{
Hnode hnode0 = (Hnode)list[min];
Hnode hnode = (Hnode)list[i];
if (hnode.ways_out() < hnode0.ways_out())
{
min = i;
}
}
if (list.Count == 0)
{
return null;
}
Hnode hnode1 = (Hnode)list[min];
list.Remove(hnode1);
list.Insert(0, hnode1);
return list;
}

//求出口数。
public int ways_out()
{
int tx, ty, childcount = 0;

if (x < 0 || y < 0 || x > M || y > N || board[x, y] > 0)
{
return -1;
}
for (int i = 0; i < 8; i++)
{
tx = this.x + dx[i];
ty = this.y + dy[i];
if (tx < 0 || ty < 0 || tx > M || ty > N)
{
continue;
}
if (board[tx, ty] == 0)
{
childcount++;
//在某个方向上有出口,该方向的指示变为1
direct[i] = 1;
}
}
return childcount;
}

//递归搜索
public void serach(Hnode hn, int count)
{
if (count >= (M + 1) * (N + 1))
{
Console.WriteLine("count={0}", count);
Console.WriteLine("完了!");
Console.ReadKey();
}
else
{
Console.WriteLine("x={0},y={1}", hn.x, hn.y);
int childcount = hn.ways_out();
// Console.WriteLine("childcount={0}", childcount);
board[hn.x, hn.y] = 1;
IList list = hn.sort();
if (list == null || list.Count == 0)
{
Console.WriteLine("count={0}", count);
Console.WriteLine("发生错误!");
return;
}
//for (int i = 0; i < list.Count; i++)
//{
//    Hnode hnode = (Hnode)list[i];
//    Console.WriteLine("x={0},y={1},childcount={2}", hnode.x, hnode.y, hnode.ways_out());
//}
serach((Hnode)list[0], count + 1);
Console.ReadKey();
}
}


以下是运行的效果:

x=0,y=0
x=2,y=1
x=0,y=2
x=1,y=0
x=3,y=1
x=5,y=0
x=7,y=1
x=8,y=3
x=7,y=5
x=8,y=7
x=7,y=9
x=5,y=8
x=3,y=9
x=1,y=8
x=0,y=6
x=1,y=4
x=2,y=6
x=0,y=7
x=1,y=9
x=3,y=8
x=5,y=9
x=7,y=8
x=8,y=6
x=6,y=7
x=8,y=8
x=6,y=9
x=4,y=8
x=2,y=9
x=0,y=8
x=2,y=7
x=1,y=5
x=0,y=3
x=1,y=1
x=3,y=0
x=2,y=2
x=0,y=1
x=2,y=0
x=1,y=2
x=0,y=4
x=2,y=3
x=4,y=2
x=3,y=4
x=4,y=6
x=2,y=5
x=1,y=3
x=0,y=5
x=1,y=7
x=0,y=9
x=2,y=8
x=4,y=9
x=3,y=7
x=1,y=6
x=2,y=4
x=3,y=6
x=5,y=7
x=4,y=5
x=3,y=3
x=4,y=1
x=6,y=0
x=8,y=1
x=6,y=2
x=7,y=0
x=8,y=2
x=7,y=4
x=6,y=6
x=5,y=4
x=3,y=5
x=4,y=7
x=6,y=8
x=8,y=9
x=7,y=7
x=5,y=6
x=4,y=4
x=6,y=5
x=5,y=3
x=3,y=2
x=4,y=0
x=6,y=1
x=8,y=0
x=7,y=2
x=8,y=4
x=7,y=6
x=5,y=5
x=6,y=3
x=5,y=1
x=4,y=3
x=6,y=4
x=8,y=5
x=7,y=3
count=90
完了!


算是做出来了。可是想想这个样子不太直观,所以我就又在winform上画了幅棋盘,然后把经过的路径都画出来。为了看清效果,就定了个时间,每隔一秒钟画一条线。因为我不会做flash,所以只能把最终的图发给大家看看。

画图的程序:

System.Collections.IList list0 = Hnode.list0;
Graphics g = this.CreateGraphics();
for (int i = 0; i < list0.Count-1; i++)
{
int[] aa = (int[])list0[i];
int[] bb = (int[])list0[i+1];
g.DrawLine(new Pen(Color.Red), new Point(150 + aa[0] * 50, 50 + aa[1] * 50), new Point(150 + bb[0] * 50, 50 + bb[1] * 50));
Thread.Sleep(1000);
}


最终的效果图如下:

内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: