骑士游历程序的开发
1. 基本介绍
骑士游历问题是一个古老而著名的问题,它最初是由大数学家Euler提出的,问题是这样的:国际象棋中的棋子(叫作骑士)在一个空棋盘内移动,问它能否经过64格中的每一格且只经过一次?(骑士按L行移动,即在某方向前进两格接着在与原方向垂直的方向上前进一格)
本程序实现 骑士游历问题的求解,并能够演示起始位置在棋盘上任何位置的游历问题的实现。程序采用动态的图形演示,使算法的描述更形象、更生动。
程序采用Applet来编制整个程序。
2. 程序的基本组成
程序运行的界面主要包括6个按钮:
(1)Info: 帮助信息。
(2)Start: 开始游历。
(3)Stop: 停止游历。
(4)Exit: 退出游历。
(5)NextTour: 重新开始一个新的游历。
(6)NextMoving: 骑士要走的下一步。
3. 程序结构说明
本程序由3个类组成。其中KnightsTour是主类,或者说是控制类,AccessibleSquare类主要是算法实现,MyPanel实现图形化显示结果。
4. 程序效果显示图
整个程序界面由三部分组成,上方是由Info、Start、Stop和Exit四个按钮组成的工具栏,可以执行相应的操作;中间是骑士游历效果显示图,动态演示骑士游历过程;最下边一排是NextTour和NextMoving两个按钮,单击NextTour按钮,可重新开始一个新的游历,单击NextMoving按钮,可显示骑士要走的下一步。
5. 程序源代码及其分析说明
5.1 对算法的实现类,采用启发式算法
(1)先把8个可能走的方向用两个数组(horizonta[]和vertical[])表示出来,选择走哪个方向就在原坐标上进行相应的加法,表示骑士走到了一个新的位置。
(2)由于程序采用启发式算法,应考察每一方格的可到达性。使用数组accessibiling[]表示可到达数,并当骑士游历时,程序动态修正剩余格子的可到达数。
public class AccessibleSquares {
//骑士8个方向走L形状所需的x坐标和y坐标的变化量
private static int horizontal [] = {2, 1, -1, -2, -2, -1, 1, 2};
private static int vertical [] = {-1, -2, -2, -1, 1, 2, 2, 1};
private int xpos []; //骑士所处x轴坐标
private int ypos []; //骑士所处y轴坐标
private int accessibility [];
private int ownxpos, ownypos;
private int ownAccessibility;
private int arrayPos;
private int countAccessibility;
public AccessibleSquares(int x, int y) //构造函数
{
int testXPos;
int testYPos;
xpos = new int [8];
ypos = new int [8];
accessibility = new int [8];
arrayPos = 0;
ownxpos = x;
ownypos = y;
ownAccessibility = KnightsTour.access [x] [y];
for(int i = 0; i < horizontal.length; i++)
{//8个可能的方向
testXPos = x + horizontal [i];
testYPos = y + horizontal [i];
if( (testXPos >= 0) & (testXPos < 8) & (testYPos >= 0) & (testYPos < 8) ) //如果测试位置在棋盘以内
{
xpos [arrayPos] = testXPos;
ypos [arrayPos] = testYPos;
accessibility [arrayPos] = KnightsTour.access [testXPos] [testYPos];
//因为accessibility [arrayPos] = 0 表明格子已经被占据
if(accessibility [arrayPos] > 0)
arrayPos ++;
}
}
countAccessibility = arrayPos;
if(countAccessibility > 0)
{
sortAll();
}
arrayPos = -1;
}
public boolean hasMoreAccessibility() //arrayPos + 1 指向下一个可行的位置
{
if( (arrayPos + 1) < countAccessibility)
{
return true;
}
else
{
return false;
}
}
public AccessibleSquares nextAccessible()
{
arrayPos ++;
return this;
}
public AccessibleSquares accessibleAt(int pos)
{
if( (pos >= 0) & (pos < countAccessibility) )
arrayPos = pos;
return this;
}
public int getXpos()
{
return xpos [arrayPos];
}
public int getYpos()
{
return ypos [arrayPos];
}
public int getAccessibility()
{
return accessibility [arrayPos];
}
public int getTotalAccessible()
{
return countAccessibility;
}
//冒泡排序法。注意:虽然冒泡排序编程简单,但算法开销较大,对比较大的数组排序更是如此。
//如果遇到大型数组的排序,应该选择效率更高的排序方法
private void sortAll()
{
for(int begin = 0; begin < countAccessibility - 1; begin ++)
{
for(int i = begin + 1; i < countAccessibility; i++)
{
if(accessibility [begin] > accessibility [i])
{
swapAll(begin, i);
}
}
}
}
//交换两个数
private void swapAll(int i, int j)
{
int temp;
temp = xpos [i];
xpos [i] = xpos [j];
xpos [j] = temp;
temp = ypos [i];
ypos [i] = ypos [j];
ypos [j] = temp;
temp = accessibility [i];
accessibility [i] = accessibility [j];
accessibility [j] = temp;
}
//进行移动操作
public void domoving()
{
for(int i = 0; i < countAccessibility; i++)
{
KnightsTour.access [xpos [i]] [ypos [i]] --;
}
KnightsTour.access [ownxpos] [ownypos] = 0;
}
//撤销移动操作
public void undomoving()
{
for(int i = 0; i< countAccessibility; i++)
{
KnightsTour.access [xpos [i]] [ypos [i]] ++;
}
KnightsTour.access [ownxpos] [ownypos] = ownAccessibility;
}
}
5.3 画图类的设计开发
MyPanel类就是画图类。首先用两种不同颜色的色块(WHITE和BLACK)显示出棋盘,还有其他两种方块(WKNIGHT和BKNIGHT),这两种方块上面有骑士,但是颜色不一样。在骑士游历的过程中不断用后来两种有骑士的方块代替前两种方块,其中需要注意的是保持棋盘颜色的一致性。
其次就是要显示骑士起始的位置、刚走过的步的位置,用边框的不同来加以区别,采用函数g.setColor(Color.green)(刚走过的步显示为绿色)和g.setColor(Color.blue)(当前步显示为蓝色)实现。这个类的对象在主类KnightsTour中被实例化。
画图类的源代码如下,为显示骑士游历的整个过程,采用public void paintComponent(Graphics g)函数画出图形。
import javax.swing.*;
import java.awt.*;
public class MyPanel extends JPanel {
public static final int WHITE = 0;
public static final int BLACK = 1;
public static final int WKNIGHT =2;
public static final int BKNIGHT = 3;
private int chessboard [] [];
private int xrecord [];
private int yrecord [];
private int displayCount;
private int lastxpos, lastypos, nextxpos, nextypos;
ImageIcon images [];
private boolean start;
public MyPanel() {
initvariance();
}//MyPanel的构造函数
public MyPanel(int [] newxrecord, int [] newyrecord) {
initvariance();
initboard(newxrecord, newyrecord);
}//重载构造函数
//注意:当类中定义了构造方法,但没有定义无参数的构造方法,这是如果采用无参数的构造方法
//去初始化对象会产生编译错误,因为只要类中定义了构造方法,系统就不会提供默认的构造方法。
public void initvariance() {
chessboard = new int [8] [8];
xrecord = new int [64];
yrecord = new int [64];
images = new ImageIcon [4];
images [0] = new ImageIcon("white.jpg");
images [1] = new ImageIcon("black.jpg");
images [2] = new ImageIcon("wknight.jpg");
images [3] = new ImageIcon("bknight.jpg");
}
public void initboard(int [] newxrecord, int [] newyrecord) {
start = true;
displayCount = -1;
for(int row = 0; row < 8; row ++) {
for(int column = 0; column < 8; column ++) {
//白的用0表示,黑的用1表示
chessboard [row] [column] = (row + column) % 2;
}
}
for(int row = 0; row < newxrecord.length; row ++) {
xrecord [row] = newxrecord [row];
yrecord [row] = newyrecord [row];
}
displayCount = 0;
chessboard [xrecord [displayCount]] [yrecord [displayCount]] += 2;
}
public void showNext() {
if(displayCount < xrecord.length - 1) {
displayCount ++;
chessboard [xrecord [displayCount]] [yrecord [displayCount]] += 2;
repaint();
}
}
public void paintComponent(Graphics g) {
for(int row = 0; row < 8; row ++) {
for(int column = 0; column < 8; column ++) {
images [chessboard [row] [column]].paintIcon(this, g, 40 * row, 40 * column);
}
}
if(displayCount > 0) {
lastxpos = xrecord [displayCount - 1];
lastypos = yrecord [displayCount - 1];
nextxpos = xrecord [displayCount];
nextypos = yrecord [displayCount];
g.setColor(Color.green);
g.drawRect(40 * xrecord [displayCount - 1] + 2, 40 * yrecord [displayCount - 1] + 2, 36, 36);
g.setColor(Color.green); //刚走过的步显示为绿色
g.drawRect(40 * xrecord [displayCount] + 2, 40 * yrecord [displayCount] + 2, 36, 36);
g.setColor(Color.blue); //当前步显示为蓝色
g.drawRect(40 * Math.min(nextxpos, lastxpos), 40 * Math.min(nextypos, lastypos), 40 * (Math.abs(nextxpos - lastxpos) + 1), 40 * (Math.abs(nextypos - lastypos) + 1));
}
g.setColor(Color.red);
g.drawRect(40 * xrecord [0] + 2, 40 * yrecord [0] + 2, 36, 36);
}
}
5.3 主调用程序的设计和开发
KnightsTour类是控制类,它完成对算法类和画图类的调用。由于java的GUI编程是事件驱动的,因此在类KnightsTour中,通过监听前面介绍的几个Button的事件响应,完成调用过程。具体代码如下:
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
public class KnightsTour extends JApplet{
//初始位置位于某个位置的可到达数采用二维数组表示
public static int access [] [] = {
{2, 3, 4, 4, 4, 4, 3, 2},
{3, 4, 6, 6, 6, 6, 4, 3},
{4, 6, 8, 8, 8, 8, 6, 4},
{4, 6, 8, 8, 8, 8, 6, 4},
{4, 6, 8, 8, 8, 8, 6, 4},
{4, 6, 8, 8, 8, 8, 6, 4},
{3, 4, 6, 6, 6, 6, 4, 3},
{2, 3, 4, 4, 4, 4, 3, 2}
};
public static int accessbak [] [] = arrayCopy(access);
int countMoving = -1;
int tourXpos [] = new int [64];
int tourYpos [] = new int [64];
private int recordXpos [] [];
private int recordYpos [] [];
private int recordCount = -1;
private int startx;
private int starty;
private boolean success = false;
MyPanel myPanel; //声明MyPanel的一个对象
public void tour(int xpos, int ypos) {
countMoving ++;
//如果64个格子都被走过,则返回
if(countMoving == 63) {
tourXpos [countMoving] = xpos;
tourYpos [countMoving] = ypos;
success = true;
countMoving --;
return ;
}
AccessibleSquares nextSquare = new AccessibleSquares(xpos, ypos);
while(nextSquare.hasMoreAccessibility()) {
//开始移动
nextSquare.domoving();
//把这一步记录下来
tourXpos [countMoving] = xpos;
tourYpos [countMoving] = ypos;
//尝试下一步的移动
nextSquare.nextAccessible();
tour(nextSquare.getXpos(), nextSquare.getYpos());
//如果64格全走过就返回
if(success) {
countMoving --;
return ;
}
//如果失败,则从起始位置重新开始
nextSquare.undomoving();
}
countMoving --;
}
public static int [] arrayCopy(int array1 []) {
int [] array2 = new int [array1.length];
for(int row = 0; row < array1.length; row ++) {
array2 [row] = array1 [row];
}
return array2;
}
//复制数组函数
public static int [] [] arrayCopy(int array1 [] []){
int [] [] array2 = new int [array1.length] [array1 [0].length];
for(int row = 0; row < array1.length; row ++) {
for(int column = 0; column < array1 [0].length; column ++) {
array2 [row] [column] = array1 [row] [column];
}
}
return array2;
}
//初始化数组函数
public void initialArray(int chessboard [] []) {
for(int row = 0; row < 8; row ++) {
for(int column = 0; column < 8; column ++) {
chessboard [row] [column] = 0;
}
}
}
public void init() {
recordCount = -1;
recordXpos = new int [64] [64];
recordYpos = new int [64] [64];
for(int row = 0; row < 8; row ++) {
for(int column = 0; column < 8; column ++) {
success = false;
countMoving = -1;
startx = row;
starty = column;
access = arrayCopy(accessbak);
tour(row, column);
recordCount ++;
recordXpos [recordCount] = arrayCopy(tourXpos);
recordYpos [recordCount] = arrayCopy(tourYpos);
}
}
recordCount = 0;
myPanel = new MyPanel(recordXpos [0], recordYpos [0]);
JPanel buttonPanel = new JPanel();
JButton nextMoving = new JButton("Next Moving"); //新建Next Moving 按钮
JButton nextTour = new JButton("Next Tour"); //新建Next Tour 按钮
buttonPanel.add(nextTour);
buttonPanel.add(nextMoving);
getContentPane().add(buttonPanel, BorderLayout.SOUTH);
getContentPane().add(myPanel);
nextMoving.addActionListener(
//匿名内部类,定义了actionPerformed函数,调用showNext函数响应NextMoving Button事件
new ActionListener() {
public void actionPerformed(ActionEvent e) {
myPanel.showNext();
}
}
);
nextTour.addActionListener(
//内部类,定义了actionPerformed函数,响应NextTour Button事件
//在方法中定义的内部类可以访问定义它的外部类对象中的实例变量和方法,以及该方法中的所有final变量
new ActionListener() {
public void actionPerformed(ActionEvent e) {
if(recordCount < recordXpos.length - 1) {
recordCount --;
}else {
recordCount = 0;
}
myPanel.initboard(recordXpos [recordCount], recordYpos [recordCount]);
myPanel.repaint();
}
}
);
}
public void paint(Graphics g) {
super.paint(g);
}
}