数据结构___马踏棋盘详尽实现+报告+通俗易懂注释

时间:2022-07-26
本文章向大家介绍数据结构___马踏棋盘详尽实现+报告+通俗易懂注释,主要内容包括其使用实例、应用技巧、基本知识点总结和需要注意事项,具有一定的参考价值,需要的朋友可以参考一下。

摘要:

众所皆知,国际象棋中“马”的行走规则为八个方向,在这种规则下,一个“马”是否可能遍历国际象棋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<malloc.h>
#include<limits.h>
#include<stdio.h>
#include<math.h>
#include<process.h>
#include<conio.h>
#include<dos.h>
#include<memory.h>
#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;i<N;i++)
    {
        for(int j=0;j<N;j++)
        {
            for(int k=0;k<N;k++)
            {
                if(check(i+dir[k].x,j+dir[k].y))weight[i][j]++;
            }
        }
    }
}
inline void output(SqStack &a)
{
	SqStack b;//a是压入的栈,b是输出的栈
	int c[8][8] = { 0 } ;//初始先置零
	int j, k = N*N, l = 1, x, y;
	InitStack(&b);
	SElemType e;
	while (!StackEmpty(a))
	{
		Pop(&a, &e);
		c[e.x][e.y] = k;
		k--;//编号减一
		Push(&b, e);//存入b
	}
	GetTop(b, &e);
	x = e.x; y = e.y;
	printf("起始坐标:(%d,%d)n", x, y);
	while (!StackEmpty(b))
	{
		Pop(&b, &e);
		printf("(%d,%d)", e.x, e.y);
		Push(&a, e);
	}
	printf("n棋盘表示:       0  1  2  3  4  5  6  7n");
	printf("              ┌──┬──┬──┬──┬──┬──┬──┬──┬n");
	printf("             0");

	int s = 0;
	for (s = 0; s < 7; s++)
	{
		for (j = 0; j <= 7; j++)
		{
			if (c[s][j] < 10) printf("│ %d", c[s][j]); else
				printf("│%d", c[s][j]);
		}printf("│n");
		printf("              ");
		printf("├──┼──┼──┼──┼──┼──┼──┼──┼n");
		printf("             %d", s+1);
	}

	for (j = 0; j <= 7; j++)
	{
		if (c[s][j]<10) printf("│ %d", c[s][j]);
		else
			printf("│%d", c[s][j]);
	}printf("│n");
	printf("              ");
	printf("└──┴──┴──┴──┴──┴──┴──┴──┴n");

}

void setmap()//贪心算法的核心,八个方向的优先搜索顺序按照各自的出度排序,出度小的先搜索
{
	for (int i = 0; i<N; i++)
	{
		for (int j = 0; j<N; j++)
		{
			for (int k = 0; k<8; k++)
				map[i][j][k] = k;//先按默认顺序
			for (int k = 0; k<8; k++)
			{
				for (int m = k + 1; m<8; m++)
				{
					int d1 = map[i][j][k],x1 = i + dir[d1].x,y1 = j + dir[d1].y,d2 = map[i][j][m],x2 = i + dir[d2].x,y2 = j + dir[d2].y,l1 = check(x1, y1),l2 = check(x2, y2);
					if ((l1 == 0 && l2) || (l1&&l2 && (weight[x1][y1]>weight[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; i<N; i++)
		for (int j = 0; j<N; j++)
			walked[i][j] = 0;
	setweight();
	setmap();
	while (1)
	{
		printf("输入马的行、列坐标 Y (from 0 to 7):");
		scanf("%d%d", &x,&y);
		if (x<0 || x>7 || y<0 || y>7)
		{
			printf("输入错误!nn");
		}
		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);
			}
		}
	}
}