马踏棋盘算法——递归实现(回溯法、深度优先遍历)

背景:在学习数据结构时,关于图的遍历的一个经典问题,这里采用递归的方式实现该算法,其中包含回溯法和图的深度优先遍历的思想,在参考各种递归实现该算法的基础上,完成了一版带有自己理解的详细注释版代码

开发环境:Code::Blocks

编译器:GCC

语言:C

代码:(详细注释)

/* 递归+回溯+图深度优先遍历 */
#include <stdio.h>
#include <time.h>

#define X 8
#define Y 8

int chess[X][Y];

/* 判断八个方向是否有可以走的点 */
int nextxy_1(int *x,int *y,int count)
{
    /* count表示8个方向 */
	switch(count)
	{
    case 0:
      /* 该方向没有超出边界 且 之前未走过 */
      if(*x+2<X && *y+1<Y && chess[*x+2][*y+1]==0)
      {
          /* 该方向可走则修改当前位置 */
        *x+=2;
        *y+=1;
        /* 有方向可走 则返回1 */
        return 1;
      }
      break;

    case 1:
      if(*x+2<X && *y-1>=0 && chess[*x+2][*y-1]==0)
      {
        *x+=2;
        *y-=1;
        return 1;
      }
      break;

    case 2:
      if(*x+1<X && *y-2>=0 && chess[*x+1][*y-2]==0)
      {
        *x+=1;
        *y-=2;
        return 1;
      }
      break;

    case 3:
      if(*x-1>=0 && *y-2>=0 && chess[*x-1][*y-2]==0)
      {
        *x-=1;
        *y-=2;
        return 1;
      }
      break;

    case 4:
      if(*x-2>=0 && *y-1>=0 && chess[*x-2][*y-1]==0)
      {
        *x-=2;
        *y-=1;
        return 1;
      }
      break;

    case 5:
      if(*x-2>=0 && *y+1<Y && chess[*x-2][*y+1]==0)
      {
        *x-=2;
        *y+=1;
        return 1;
      }
      break;

    case 6:
      if(*x-1>=0 && *y+2<Y && chess[*x-1][*y+2]==0)
      {
        *x-=1;
        *y+=2;
        return 1;
      }
      break;

    case 7:
      if(*x+1<X && *y+2<Y && chess[*x+1][*y+2]==0)
      {
        *x+=1;
        *y+=2;
        return 1;
      }
      break;

    default:
         /* 无方向可走 */
      break;
	}
	
	/* 8个方向均不可走 则换回0 */
	return 0;
}

/* 判断八个方向是否有可以走的点 优于nextxy_1 */
/* 这里参考网络上的一个优化:https://blog.csdn.net/ppalive_/article/details/47728475 */
/* 采用nexty_1或nextxy_2函数在整理算法理解上并不重要 */
/* 区别在于寻找下一个方向的顺序 */
int nextxy_2(int *x,int *y,int count)
{
	switch(count)
	{
    case 0:
      if(*x+2<X && *y-1>=0 && chess[*x+2][*y-1]==0)
      {
        *x+=2;
        *y-=1;
        return 1;
      }
      break;

    case 1:
      if(*x+2<X && *y+1<Y && chess[*x+2][*y+1]==0)
      {
        *x+=2;
        *y+=1;
        return 1;
      }
      break;

    case 2:
      if(*x+1<X && *y-2>=0 && chess[*x+1][*y-2]==0)
      {
        *x+=1;
        *y-=2;
        return 1;
      }
      break;

    case 3:
      if(*x+1<X && *y+2<Y && chess[*x+1][*y+2]==0)
      {
        *x+=1;
        *y+=2;
        return 1;
      }
      break;

    case 4:
      if(*x-2>=0 && *y-1>=0 && chess[*x-2][*y-1]==0)
      {
        *x-=2;
        *y-=1;
        return 1;
      }
      break;

    case 5:
      if(*x-2>=0 && *y+1<Y && chess[*x-2][*y+1]==0)
      {
        *x-=2;
        *y+=1;
        return 1;
      }
      break;

    case 6:
      if(*x-1>=0 && *y-2>=0 && chess[*x-1][*y-2]==0)
      {
        *x-=1;
        *y-=2;
        return 1;
      }
      break;

    case 7:
      if(*x-1>=0 && *y+2<Y && chess[*x-1][*y+2]==0)
      {
        *x-=1;
        *y+=2;
        return 1;
      }
      break;

    default:
      break;
  }
	return 0;
}

/* 输出计算出来的走法 */
void print()
{
	int i,j;

	for(i=0;i<X;++i)
	{
		for(j=0;j<Y;++j)
		{
		    /* tab分割 */
			printf("%2d\t",chess[i][j]);
		}
		/* enter分割 */
		printf("\n");
	}
	printf("\n");
}

/* 遍历棋盘 */
int TravelChessBoard(int x,int y,int tag)
{
	int x1 = x;
	int y1 = y;
	int flag = 0;
	int count = 0;

  /* tag 标记足迹 */
	chess[x][y] = tag;

	/* 棋盘遍历完成 */
	if(tag == X*Y)
	{
	  /* 打印遍历结果 */
		print();
		return 1;
	}

	/* 走第一个方向 */
	flag = nextxy_2(&x1,&y1,count);

  /* 走不通则切换方向(8个方向都尝试) */
	while(flag == 0 && count <= 7)
	{
		++count;
		flag=nextxy_2(&x1,&y1,count);
	}
	/* 如果所有方向都走不通则跳到if(flag==0) 清除当前位置的足迹 */

	/* 如果找到能走的方向 就进行下一次递归(走下一步) */
	while(flag)
	{
	  /* 完成遍历(TravelChessBoard函数返回1) */
    /* 这里如果每一次递归都能在8个方向种找到可以走地方向,则递归会一直深入(深度优先) */
    /* 递归一直深入则相当于函数TravelChessBoard每次都只运行到这里 */
    if(TravelChessBoard(x1,y1,tag+1))
			 return 1;

    /* 一直到递归到某个深度之后,8个方向都没有可行路径,则TravelChessBoard函数返回一次0 */
    /* 这种情况下 TravelChessBoard函数将运行到这里 */
    /* 也就是说之前走的路径是无法完成遍历的 所以需要退回上一步 选择其他路径尝试(切换下一个方向) */
    /* 这里相当于是退回到了上一层的递归 以下操作读取上一层递归中的坐标 将上一层递归中的方向+1(切换方向) */
    /* 当前路径无法完成遍历(TravelChessBoard函数返回0) */
    x1=x;y1=y;
    ++count;

    /* 尝试下一个方向 */
    flag=nextxy_2(&x1,&y1,count);

    /* 方向尝试失败 则切换下一个方向进项尝试 */
    /* 这里count的条件为<7的原因是:上一次走的方向已经被证明失败 不在选择与上一次同样的方向 */
    while(flag==0 && count<7)
    {
      ++count;
      flag=nextxy_2(&x1,&y1,count);
    }
    /* 如果所有方向再次尝试失败 则再次运行到if(flag==0)语句,即将再次退出一层递归,重新选择该层的路径 */
	}

	/* 当前位置向各个方向都走不通 */
	if(flag==0)
	{
	  /* 则情况当前位置的足迹 */
		chess[x][y]=0;
	}
	return 0;
}

int main()
{
	int i,j;
	clock_t start,finish;

	/* 获取算法开始时的系统tick */
	start=clock();

	/* 初始化棋盘 */
	for(i=0;i<X;++i)
	{
		for(j=0;j<Y;++j)
			chess[i][j]=0;
	}

	/* 起始点(2,0)遍历棋盘 */
	if(!TravelChessBoard(2,0,1))
	{
    /* 若遍历失败 */
		printf("failed!\n");
	}

	/* 获取算法结束时的系统tick */
	finish=clock();

	/* 计算算法耗时 */
	printf("\n time consume:%f second\n\n",(double)(finish-start)/CLOCKS_PER_SEC);

	return 0;
}

 

——cloud over sky

 ——2020/3/11
 
 

  • 1
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
回溯步遍问题的复杂度分析需要考虑以下几个因素: 1. 棋盘的大小:设棋盘大小为 $n \times m$,则遍问题的规模为 $nm$。 2. 起始位置的选择:由于的走具有对称性,因此可以假设起始位置为 $(0, 0)$,并对其它起始位置进行对称处理。这样可以减少起始位置的选择,但并不会影响问题的复杂度。 3. 的走:每个格子可以选择的下一个格子最多有 $8$ 个,因此每个格子最多需要尝试 $8$ 次才能找到一个可行的下一个格子。 4. 剪枝优化:在实际的实现中,可以使用一些剪枝优化来减少回溯的次数,例如可以按照下一个格子能够到达的未访问过的格子数量从小到大进行排序,这样可以优先选择能够到达较少未访问过的格子的下一个格子。 根据以上因素,回溯步遍问题的复杂度可以分析如下: - 时间复杂度:设棋盘大小为 $n \times m$,则回溯步遍问题的时间复杂度为 $O(8^{nm})$,因为每个格子最多需要尝试 $8$ 次才能找到一个可行的下一个格子,而遍所有格子需要尝试的次数是指数级别的。 - 空间复杂度:回溯步遍问题的空间复杂度取决于归调用栈的深度,即最多需要保存 $nm$ 个格子的信息。因此空间复杂度为 $O(nm)$。 需要注意的是,由于回溯步遍问题的时间复杂度是指数级别的,因此对于较大的棋盘来说,回溯是无在合理的时间内得到解决的。在实际应用中,需要考虑使用其它更加高效的算法来解决步遍问题。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值