数据结构实验报告
实验课题 马踏棋盘
实验时间 2019.10.01
学 院 信息学院
专 业 计算机科学系
学 号
学生姓名
指导老师
指导老师意见:
摘要:
众所皆知,国际象棋中“马”的行走规则为八个方向,在这种规则下,一个“马”是否可能遍历国际象棋8*8的棋盘?如果有可能,在给定起点的情况下,有多少种可能?本实验将通过c语言程序用计算机来模拟“马”对棋盘的遍历。
关键字:
dfs,栈的递归实现
正文内容:
一、实验目的
(1) 熟练使用栈和队列解决实际问题;
(2) 了解并掌握数据结构与算法的设计方法,具备初步的独立分析和设计能力;
(3) 初步掌握软件开发过程的问题分析、系统设计、程序编码、测试等基本方法和技能; (4) 提高综合运用所学的理论知识和方法独立分析和解决问题的能力
二、实验内容
(一)问题描述:
已知国际象棋为8×8棋盘,共64个格,规则中,马按照如图所示规则移动。将马放在任意方格中,令马遍历每个方格一次且仅一次,编制非递归程序,将数字1,2,…,64按照马的行走路线依次填入一个8×8的方阵,并输出结果。
(二)问题分析
通过结合图示,我们不难发现,当马的起始位置(i,j)确定的时候,可以走到下列8个位置之一:
(i-2,j+1)、(i-1,j+2)、(i+1,j+2)、(i+2,j+1)、(i+2,j-1)、(i+1,j-2)、(i-1,j-2)、(i-2,j-1)
但是,如果(i,j)靠近棋盘的边缘,上述有些位置可能超出棋盘范围,成为不可达的位置。8个可能位置可以用一个以结点类型为基类型的一维数组DireTry [0…7]作为坐标增减量而获得。其中,数组的下标为方向。
i |
0 |
1 |
2 |
3 |
4 |
5 |
6 |
7 |
DireTry[i].x |
-2 |
-1 |
1 |
2 |
2 |
1 |
-1 |
-2 |
DireTry[i].y |
1 |
2 |
2 |
1 |
-1 |
-2 |
-2 |
-1 |
所以位于(i,j)的马可以走到新位置是在棋盘范围内的(i+ DireTry[i].x,j+ DireTry[i].y),其中i的取值是0~7。
(三)程序设计
1、需求分析
(1) 输入的形式和输入值的范围;
分开输入马的初始行坐标X和列坐标Y,X和Y的范围都是[0,7]。
(2) 输出的形式;
a)以数组下标形式输入,代表起始位置,i表示行标,j表示列标。
b)以棋盘形式输出,每一格打印马走的步数,这种方式比较直观。
2、算法思想
对整个问题,考虑采用“回溯算法”与“贪心算法”两种算法来综合解决:
(1)回溯算法思想:搜索空间是整个棋盘上的8*8个点。约束条件是不出边界且每个点只能经过一次。搜索过程是从一点(i,j)出发,按深度有限的原则,从8个方向中尝试一个可以走的点,直到走过棋盘上所有的点.当没有点可达且没有遍历完棋盘时,就要撤销该点,从该点上一点开始找出另外的一个可达点,直到遍历完整个棋盘。
(2) 贪心算法思想:探讨每次选择位置的“最佳策略”,在确定马的起始节点后,在对其子结点进行选取时,优先选择出度最小的子节点进行搜索,这是一种局部调整最优的做法。如果优先选择出度多的子结点,那出度少的子结点就会越来越多,很可能出现‘死’结点(即没有出度又不能跳过的节点),反过来如果每次都优先选择出度少的结点跳,那出度少的结点就会越来越少,这样跳成功的机会就更大一些。
a) 先求出每个坐标点的出度值,即是该坐标下一步有几个方向可以走
b) 出度值越小,则被上一点选中的可能性就越大,下一个方向八个值的选择顺序保存MAP[X][Y][K]数组中,0<=K<=7,例如MAP[X][Y][0]保存的是下一步优先选择走的方向,MAP[X][Y][7]就是最后才走的。边界的点最先走,然后再走中间.
3、具体设计
(1).主程序模块:
Void main()
{
初始化棋盘;
while(1)
{
接受命令;
处理命令;
}
执行Path(x,y);
}
(2).栈模块-实现栈功能
(3).踏遍棋盘Path函数伪码算法:
While(1)
{
若已经走了64步,则
{
打印输出结果;
若要寻找多条路径,出栈,回溯到上一步,上一步的方向加1
}
否则
{
若该点所有方向已走完:
{出栈,回溯到上一个点,上一个点要走的方向加1}
若该点所有方向未走完
{若该点未走过且在棋盘内
{入栈,已走步数加1}
否则
{下一步方向加1}
}
}
}//while
4、程序源代码(见同文件夹下文件“马踏棋盘.txt”)
三、实验结果
四、实验总结
由计算机模拟的过程可见,一个“马”能够踏遍8*8的国际象棋棋盘,且路径不唯一。本实验的实质是运用回溯算法对八叉树进行遍历,通过本次实验,对栈这种数据结构做了一次重新巩固,将回溯算法和贪心算法的做了一次实际的应用,对算法思想的理解更进一步。同时c/c++语言编程的能力也是一次锻炼。
附件 源码
//◎2019.10.2 Created By LiMin Guo. All Rights Reserved.
//本算法马踏棋盘的演示程序,实现选择下一搜索位置的局部贪心策略,有效减少回溯的次数
//并支持寻找给定起点出发的多条以至全部行走路线,以及寻找行走路线的回溯过程
#include
#include
#include
#include
#include
#include
#include
#include
#define CLS system("cls")
#define OVERFLOW -1
#define TRUE 1
#define FALSE 0
#define OK 1
#define ERROR 1
#define STACK_INIT_SIZE 10 //存储空间初始分配量
#define STACKINCREMENT 2 //存储空间分配增量
#define N 8 //8*8棋盘
typedef int Status;
typedef struct SElemType
{
int x,y,z;//x,y坐标,z为八个方向中当前方向下标
}chess;
int map[N][N][8]; //前两维为坐标,后一维为当前方向下标已按
int weight[N][N]; //各点出度
bool walked[N][N]; //标记数组,走过为1
//栈的定义部分
typedef struct SqStack
{
SElemType *base; //栈底指针
SElemType *top; //栈顶指针
int stacksize; //当前栈内元素数量
}SqStack;
inline Status InitStack(SqStack *S) //栈的初始化
{
(*S).base = (SElemType *)malloc(STACK_INIT_SIZE*sizeof(SElemType));
if (!(*S).base)
exit(OVERFLOW);
(*S).top = (*S).base;
(*S).stacksize = STACK_INIT_SIZE;
return OK;
}
inline Status StackEmpty(SqStack S)//判断是否为空栈
{
if (S.top == S.base)
return TRUE;
else
return FALSE;
}
inline Status GetTop(SqStack S, SElemType *e)//获得栈顶元素
{
if (S.top>S.base)
{
*e = *(S.top - 1);
return OK;
}
else
return ERROR;
}
inline Status SetTop(SqStack S, SElemType *e)//修改栈顶元素
{
if (S.top>S.base)
{
*(S.top - 1) = *e;
return OK;
}
else
return ERROR;
}
inline Status Push(SqStack *S, SElemType e)//推入新元素入栈
{
if ((*S).top - (*S).base >= (*S).stacksize)//栈满时追加新的分配空间
{
(*S).base = (SElemType *)realloc((*S).base, ((*S).stacksize + STACKINCREMENT)*sizeof(SElemType));
if (!(*S).base)
exit(OVERFLOW);
(*S).top = (*S).base + (*S).stacksize;
(*S).stacksize += STACKINCREMENT;
}
*((*S).top)++ = e;
return OK;
}
inline Status Pop(SqStack *S, SElemType *e)//弹出一个栈顶元素
{
if ((*S).top == (*S).base)
return ERROR;
*e = *--(*S).top;
return OK;
}
static SElemType dir[8] =//八个方向
{
{ -2, 1, 0 }, { -1, 2, 0 }, { 1, 2, 0 }, { 2, 1, 0 }, { 2, -1, 0 }, { 1, -2, 0 }, { -1, -2, 0 }, { -2, -1, 0 }
};
inline bool check(int i, int j)//检查(i,j)是否在棋盘内
{
return (i<0 || i >= 8 || j<0 || j >= 8)?0:1;
}
//先预处理出树上各点的出度
inline void setweight()
{
memset(weight,0,sizeof(weight));
for(int i=0;iweight[x2][y2])))//check函数值为零的优先放后面,
{
map[i][j][k] = d2;
map[i][j][m] = d1; //冒泡排序,按照weight权值排序,如果check值为零放在最后
}
}
}
}
}
}
inline void init(int &x,int &y)
{
for (int i = 0; i7 || y<0 || y>7)
{
printf("输入错误!\n\n");
}
else
break;
}
};
inline void Path(int xx, int yy);
int main()
{
int x, y;
init(x,y);
Path(x, y);
}
inline void Path(int xx, int yy)//核心代码
{
SqStack path;
SElemType pop_temp;
SElemType pos, dest_p; //当前点和目标点
int step = 0, x, y, s, k = 1, l = 1, d = 0;
char ch;
pos.x = xx; pos.y = yy; pos.z = 0;
walked[xx][yy] = 1;
InitStack(&path);
Push(&path, pos); //起始点入栈
while (1)
{
if (step == (N*N - 1))//返回条件
{
output(path);
printf("退出Quit扣q键 / 返回(重新设置起始点)扣b键 / 寻找下一条路径扣n键\n");
printf("第%d条路径", k);
ch = getch();
if (ch == 'b') { system("cls");main() ;}
else if (ch == 'q') exit(1);
else //寻找并打印下一条路径
{
k++;//路径数+1
Pop(&path, &pop_temp);
GetTop(path, &pos);
pos.z = pos.z + 1;//搜下一方向
SetTop(path, &pos); //换方向
step--;
continue;
}
}
if (StackEmpty(path)) break;
GetTop(path, &pos);
x = pos.x; y = pos.y;//该点坐标
d = pos.z; //该点出度
if (d >= 8) //退栈条件
{
GetTop(path, &pos);
printf("(%d %d)退栈,回溯到",pos.x,pos.y);
walked[x][y] = 0; //清除走过标记
step--; //步数减一
Pop(&path, &pop_temp); //清除该点
GetTop(path, &pos); //取之前的一个出边所连的点
printf("(%d %d)!\n",pos.x,pos.y);
pos.z = pos.z + 1;
SetTop(path, &pos); //换方向搜索
}
if (d<8)//正常未搜完
{
s = map[x][y][d];//当前搜的方向
dest_p.x = x + dir[s].x;
dest_p.y = y + dir[s].y;
if (check(dest_p.x, dest_p.y) && walked[dest_p.x][dest_p.y] == 0)
//没被搜过并且在棋盘内,入栈
{
walked[x][y] = 1; //打上标记
step++;
dest_p.z = 0;//选择第一个方向
Push(&path, dest_p);//入栈
}
else//搜过或不在棋盘内
{
pos.z = d + 1;//换方向,入栈
SetTop(path, &pos);
}
}
}
}