C语言回顾【经典|八皇后问题】

八皇后问题

八皇后问题,是一个古老而著名的问题,是回溯算法的典型案例。该问题是国际西洋棋棋手马克斯·贝瑟尔于1848年提出:在8×8格的国际象棋上摆放八个皇后,使其不能互相攻击,即任意两个皇后都不能处于同一行、同一列或同一斜线上,问有多少种摆法。 高斯认为有76种方案。1854年在柏林的象棋杂志上不同的作者发表了40种不同的解,后来有人用图论的方法解出92种结果。计算机发明后,有多种计算机语言可以解决此问题。


问题条件:任两个皇后都不能处于同一条横行纵行斜线上。

八皇后问题可以推广为更一般的n皇后摆放问题:这时棋盘的大小变为n1×n1,而皇后个数也变成n2。而且仅当 n2 = 1 或 n1 ≥ 4 时问题有解

C语言回顾【经典|八皇后问题】_第1张图片


解决基本思想:递归

程序基本模块:

  1. 判断模块conflict():用来判断准备下放的皇后与已经放置在棋盘上的皇后是否有冲突
  2. 递归模块queens():为准备下放的皇后遍历位置,若有符合位置(与之前皇后没冲突),则将皇后暂放该位置,并进入下一个递归。
  3. 主函数main():没什么好说的

 模块实现:

    1、判断模块conflict():

  • 假定棋盘坐标系如图:C语言回顾【经典|八皇后问题】_第2张图片
  • 设图上标注的红色块坐标为(x0,y0),可知两条对角线的直线方程可写作:y-y0=+-(x-x0)
  • 再设:下一个要放的皇后坐标为(nextx,nexty),则由条件可知,(nextx,nexty)不在上述直线中
  • 现在讨论行、列冲突问题:
  • 行:假定我们一行一行地放置皇后,则下一行必定比上一行数大,因此不存在行的问题
  • 列:很简单:nextx!=x0即可,即为(nextx-x0)!=0
  • 代码如下:
    int conflict(int* state, int nextx, int nexty)
    {//判断准备放下的后是否与之前的后有冲突
    	//state是储存之前皇后的位置数组,
    	//nextx,nexty分别代表准备放下皇后的列和行
    	//判断的条件:不同列,即(state[i] - nextx)!=0
    	//不同行:由于nexty必定大于已有的行数,则必定不同行
    	//不在对角线上,先写出已知(x0,y0)的对角线方程:
    	//y-y0=+-(x-x0);  即(nextx,nexty)不在直线上
    	//综上,条件如下:
    	for (int i = 0; i < nexty; i++){
    		if ((abs(state[i] - nextx) == (nexty - i)) || \
    			(abs(state[i] - nextx) == 0))
    			return 1;
    	}
    	return 0;
    }

     

    2、递归模块 queens():

  • 参数说明:num表示总共的皇后数,len表示已经放下的皇后数,state数组是储存已经放下的皇后位置
  • 先从最后一个皇后说起:准备下方最后一个皇后时,要执行的很明确,就是遍历位置,并判断有没有冲突,没有冲突,表示所有皇后的位置已经确定,输出state数组中存储的皇后位置,返回1,以供上层迭代判断,这里比较重要
  • 若有冲突,表示前面有皇后下错了,需要return出去,重设前面有错的皇后的位置
  • 这部分代码如下:参数说明:
    	if (num - len == 1){//判断是否到最后一个皇后,是则:
    		for (int position = 0; position < num; position++){
    			//遍历位置
    			if (!conflict(state, position, len)){//判断是否有冲突
    				state[len] = position;//没有冲突,记录位置到state
    				printf("当前state:");
    				for (int i = 0; i <= len; i++){
    					printf("%d", state[i]);//输出
    				}
    				printf("\n");
    				return 1;
    			}
    		}	
    		return 0;
    	}

     

  • 那如果准备下放的不是最后一个皇后呢?
  • 同样的,这个皇后也需要遍历位置,如果没有冲突,则暂时将这个皇后下放,并记录位置到state
  • 重点是:进入下一次迭代,即再次调用queens()模块,并判断其返回的值(与上文conflict部分相呼应),但是这次传入的参数:状态数组state已经是添加了上一个皇后的state,总皇后数num不变,但是已经下放的皇后数len+1。
  • 现在我们假定,在上一点中进入的下一次迭代,有冲突,返回了0,则我们又跳回到本层来。这表明本层皇后的位置虽然不冲突,但是是错的,需要再次改变位置,即position+1,再次利用conflict()判断有没有冲突。这一部分,可以用continue解决掉(因为前面已经有相应代码和for循环)
  • 这部分的确难理解,但是,程序到这里,基本就完了。
  • 最好的理解办法还是:下断点,一步一步看。
  • 这部分代码如下:
    int queens(int num, int*state,int len)
    {
    	if (num - len == 1){//判断是否到最后一个皇后,是则:
    		for (int position = 0; position < num; position++){
    			//遍历位置
    			if (!conflict(state, position, len)){//判断是否有冲突
    				state[len] = position;//没有冲突,记录位置到state
    				printf("当前state:");
    				for (int i = 0; i <= len; i++){
    					printf("%d", state[i]);//输出
    				}
    				printf("\n");
    				return 1;
    			}
    		}	
    		return 0;
    	}
    	else{//不是最后一个皇后则:
    		for (int position = 0; position < num; position++){
    			//遍历位置
    			if (!conflict(state, position, len)){
    				//判断是否有冲突,没有冲突则:
    				//记录新皇后位置到state
    				state[len] = position;
    				if (queens(num, state, len + 1)){
    				//进入下次递归,并判断返回值
    				}
    				else{
    				//如果下个皇后遍历全部位置都有冲突,返回到本层
    				//将本层的皇后位置+1,即继续遍历本层
    					continue;
    				}
    			}
    			else
    				//若本层当前位置有冲突,则继续遍历
    				continue;
    		}
    		return 0;
    	}
    }

全部代码:

  1. 理解版:
    #include
    #include
    int conflict(int* state, int nextx, int nexty)
    {//判断准备放下的后是否与之前的后有冲突
    	//state是储存之前皇后的位置数组,
    	//nextx,nexty分别代表准备放下皇后的列和行
    	//判断的条件:不同列,即(state[i] - nextx)!=0
    	//不同行:由于nexty必定大于已有的行数,则必定不同行
    	//不在对角线上,先写出已知(x0,y0)的对角线方程:
    	//y-y0=+-(x-x0);  即(nextx,nexty)不在直线上
    	//综上,条件如下:
    	for (int i = 0; i < nexty; i++){
    		if ((abs(state[i] - nextx) == (nexty - i)) || \
    			(abs(state[i] - nextx) == 0))
    			return 1;
    	}
    	return 0;
    }
    int queens(int num, int*state,int len)
    {
    	if (num - len == 1){//判断是否到最后一个皇后,是则:
    		for (int position = 0; position < num; position++){
    			//遍历位置
    			if (!conflict(state, position, len)){//判断是否有冲突
    				state[len] = position;//没有冲突,记录位置到state
    				printf("当前state:");
    				for (int i = 0; i <= len; i++){
    					printf("%d", state[i]);//输出
    				}
    				printf("\n");
    				return 1;
    			}
    		}	
    		return 0;
    	}
    	else{//不是最后一个皇后则:
    		for (int position = 0; position < num; position++){
    			//遍历位置
    			if (!conflict(state, position, len)){
    				//判断是否有冲突,没有冲突则:
    				//记录新皇后位置到state
    				state[len] = position;
    				if (queens(num, state, len + 1)){
    				//进入下次递归,并判断返回值
    				}
    				else{
    				//如果下个皇后遍历全部位置都有冲突,返回到本层
    				//将本层的皇后位置+1,即继续遍历本层
    					continue;
    				}
    			}
    			else
    				//若本层当前位置有冲突,则继续遍历
    				continue;
    		}
    		return 0;
    	}
    }
    void main()
    {
    	int *state[8];
    	queens(8, state, 0);
    	system("pause");
    }
    

    2.精简版:

    #include
    #include
    int conflict(int *state,int nextx,int nexty)
    {                                                                               
        for(int i=0;i

    运行结果:四个皇后:    八个皇后:C语言回顾【经典|八皇后问题】_第3张图片

 

你可能感兴趣的:(C语言回顾)