算法进化历程之“n皇后问题”

巧若拙(欢迎转载,但请注明出处:http://blog.csdn.net/qiaoruozhuo

题目来源于国际象棋的玩法,因为皇后所在的位置可以纵向、横向、两个斜向四个方向的“捕捉”,也就是说不存在两个皇后同行或同列,或在同一斜线上。而N皇后问题就是如何布置N个皇后在N*N棋盘里使不存在两个皇后在同行同列和同一斜线上。

解决N皇后问题的最好最著名的算法就是回溯法。根据不同的数据结构和判断棋盘中某位置是否可以摆放皇后的判断方法,我们有多种实现该算法的方式。

方法一:

使用二维数组map[MAX][MAX]存储棋盘,初始化棋盘各元素值为0,皇后所在位置元素值为1。摆放结束后,输出棋盘。

本算法采用递归方式遍历棋盘中每一个位置,利用函数Place(introw, int col, int n),判断map[row][col]位置是否可以放棋子。

代码如下:

void Queen_1(int row, int n)//n皇后问题主递归函数
{
	int col;

	if (row == n)//全部摆上
	{
		count++;
		printf("第%d盘:\n", count);
		Print(n);
		return ;
	} 

	for (col=0; col<n; col++)
	{
		if (Place(row, col, n))
		{
			map[row][col] = 1;
			Queen_1(row+1, n);
			map[row][col] = 0;
		}
	}
}

void Print(int n)//输出一个解
{
	int i, j;

	for (i=0; i<n; i++)
	{
		for (j=0; j<n; j++)
		{
			if (map[i][j] == 1)
				printf("%c", 2);
			else
				printf("#");
		}
		printf("\n");
	}
	printf("\n");
} 

int Place(int row, int col, int n)//判断map[row][col]位置是否可以放棋子
{
	int i;

	for (i=0; i<=row; i++)
	{
		if (map[i][col] == 1)//同一列
			return 0;
		if (col >= i && map[row-i][col-i] == 1)//左斜线
			return 0;
		if (col+i<n && map[row-i][col+i] == 1)//右斜线
			return 0;
	}

	return 1;
}

方法二:

方法一中的Place()函数是根据已摆棋子的情况,来判断当前位置是否可以放棋子,这种方法很常见。除此之外,还可以修改当前棋子所能影响的位置的值(我的方法是使该处值增2),表示该位置不能再摆放新的棋子。待撤销当前棋子后,再把对应影响位置的值改回来。

代码如下:

void Queen_2(int row, int n)//n皇后问题主递归函数
{
	int i, j;

	if (row == n)//全部摆上
	{
		count++;
		printf("第%d盘:\n", count);
		Print(n);
		return ;
	} 

	for (i=0; i<n; i++)
	{
		if (map[row][i] == 0)
		{
			map[row][i] = 1;

			for(j=1; j<n-row; j++)//标记不可放子处
			{
				map[row+j][i] += 2;
				if (i >= j)  //左斜线
					map[row+j][i-j] += 2;
				if (i+j < n)  //右斜线
					map[row+j][i+j] += 2;
			}

			Queen_2(row+1, n);

			for(j=1; j<n-row; j++)//还原
			{
				map[row+j][i] -= 2;
				if (i >= j)  //左斜线
					map[row+j][i-j] -= 2;
				if (i+j < n)  //右斜线
					map[row+j][i+j] -= 2;
			}
			map[row][i] = 0;
		}
	}
}

方法三:

前面两种方法都是采用了二维数组来存储棋盘信息,其实用一维数组也能存储完整的棋盘信息。我们设board[row] = col;表示第row行的皇后摆在第col列。

同样采用递归方式的回溯算法,但Place()函数和Print()函数的写法都略有不同。

代码如下:

void Queen_3(int row, int n)//n皇后问题主递归函数
{
	int col, i, j;

	if (row == n)//全部摆上
	{
		count++;
		printf("第%d盘:\n", count);
		Print_2(n);
		return ;
	} 

	for (col=0; col<n; col++)
	{
		board[row] = col;
		if (Place_2(row))
		{
			Queen_3(row+1, n);
		}
	}
}

int Place_2(int row)//判断当前棋局是否满足条件
{
	int i;

	for (i=0; i<row; i++)
	{
		if (board[i] == board[row])//同一列
			return 0;
		if (board[i] < board[row] && (row-i) == (board[row]-board[i]))//左斜线
			return 0;
		if (board[i] > board[row] && (row-i) == (board[i]-board[row]))//右斜线
			return 0;
	}

	return 1;
}

void Print_2(int n)//输出一个解
{
	int i, j;

	for (i=0; i<n; i++)
	{
		for (j=0; j<n; j++)
		{
			if (j == board[i])
				printf("%c", 2);
			else
				printf("#");
		}
		printf("\n");
	}
	printf("\n");
}

方法四:

有递归方式的回溯算法,自然就有对应的非递归方法,我们可以采用深度优先搜索的通用转换方法,把递归方式转换为非递归方式。(关于非递归转换,我的博客中已经整理的大量例子,感兴趣的网友可以到http://blog.csdn.net/qiaoruozhuo查看“非递归优化”文章分类)

代码如下:

void Queen_4(int n)//n皇后问题非递归函数
{
	int row, col, i, j;

	row = 0;
	board[0] = -1;
	while (row >= 0)
	{
		board[row]++;
		if (row < n)
		{
			if (board[row] < n && Place_2(row))//初始化下一行
				board[++row] = -1;
			else if(board[row] == n)//返回上一行
				row--;
		}
		else
		{
			count++;
			printf("第%d盘:\n", count);
			Print_2(n);
			row--;
		}
	}
}

解决n皇后问题的算法还有很多,我在另外一篇文章《优化的n皇后问题》中,介绍了采用输出对称图形的方法,该算法思路源自方法四,但只考虑第一行的皇后摆在棋盘左侧的情况(若n为奇数则单独计算第一行皇后在正中间的情况),得到一个解后,直接输出它的对称解,这样只需要一半的计算量。

还有人是使用位运算来判断某位置是否可以摆放棋子,效率很高,大家可以去研究一下。

时间: 2024-09-21 03:22:19

算法进化历程之“n皇后问题”的相关文章

算法进化历程之相亲数

巧若拙(欢迎转载,但请注明出处:http://blog.csdn.net/qiaoruozhuo) 题目来自于编程论坛"吴健飞飞"的提问:求4位数以内的相亲数对 2500年前数学大师毕达哥拉斯发现,220与284两数之间存在下列奇妙的联系: 220的真因数之和为1+2+4+5+10+11+20+22+44+55+110=284 284的真因数之和为1+2+4+71+142=220 毕达哥拉斯把这样的数对a,b称为相亲数:a的真因数(小于本身的因数)之和为b,而b的真因数之和为a. 版主

算法进化历程之“根据二叉树的先序和中序序列输出后序序列”

巧若拙(欢迎转载,但请注明出处:http://blog.csdn.net/qiaoruozhuo) 前不久在看到一个作业"根据二叉树的先序和中序序列输出后序序列",当时我参考<数据结构与算法(C语言)习题集>上的做法,先根据先中序序列确定一颗二叉树,然后后序遍历二叉树输出后序序列. 函数采用了递归算法,利用函数传入的先序和中序序列的左右边界,确定要处理的序列段,生成相应的二叉树. 基本思路是,把该段先序序列的第一个元素作为当前二叉树的根结点,然后在中序序列找到根结点.根结点

以太网进化历程半景-从10Mbps到1Tbps

继Netfilter conntrack,Linux Bridge之后又是一个半景,依然如故,我不会在文中罗列技术规范和细节,仅仅是希望本文可以帮助人们理解以太网到底是什么,为什么如此成功. 0.动机,愿景以及声明 前端时间帮朋友解决一个编码问题,碰到了全双工这个概念,正好写了一个程序,实现了类似CDMA那种沃尔什编码,即从一个混合信号中分离中自己要的那部分,然而代码是好写的,往线缆上一放就全乱了,这是电学原理导致的.就这么说吧,请看下图: 请问P点的电压是多少?很简单的一个问题,是不是?是的.

C++回溯算法Demo:以4皇后问题为例

回溯算法实际上是构造一棵推理树,并由树的叶子节点反向输出历史步骤: 其中,树的构建过程较为复杂:一种简化的方法是使用链表连接和构造各个节点的关系: 以4皇后问题为例,采用C++ vector容器--避免使用指针(当然换成了整数来代替指针表达对象的位置),解决了该问题.整体算法思路清晰,便于理解. 见代码:与书中不同,此代码实际输出的是所有4皇后问题的不同走法 //title:4皇后问题的回溯算法求解 //Demo: 1)回溯算法实现4皇后问题:2)难点:树形结构的表达:3)用线性容器表达树形结构

算法-回溯法初探-n皇后问题

问题描述: 这周的数据结构作业要求写一个程序判断输入为n的所有皇后的情况, 皇后大致就是在一个n*n的棋盘上所有不同行列及不同对角线的格子排列 提示用书本上求解迷宫时用到的回溯法,也就是用到一个栈来保存当前满足的皇后,若进行不下去则回溯 采用C语言实现 代码: 1,文件 BetterQueen.h 里面主要定义了一些程序要用到的数据结构和函数接口 #ifndef BETTERQUEEN_H_INCLUDED #define BETTERQUEEN_H_INCLUDED #include <std

回溯算法-C#语言解决八皇后问题的写法与优化

结合问题说方案,首先先说问题: 八皇后问题:在8X8格的国际象棋上摆放八个皇后,使其不能互相攻击,即任意两个皇后都不能处于同一行.同一列或同一斜线上,问有多少种摆法. 嗯,这个问题已经被使用各种语言解答一万遍了,大多还是回溯法解决的. 关于回溯算法:个人理解为就是优化的穷举算法,穷举算法是指列出所有的可能情况,而回溯算法则是试探发现问题"剪枝"回退到上个节点,换一条路,能够大大提高求解效率. 具体到8皇后问题上来说,需要考虑以下几点: 1)将8个皇后定义为8行中的相对位置来标识,考虑增

回声消除中的自适应算法发展历程

传统的IIR和FIR滤波器在处理输入信号的过程中滤波器的参数固定,当环境发生变化时,滤波器无法实现原先设定的目标.自适应滤波器能够根据自身的状态和环境变化调整滤波器的权重. 自适应滤波器理论 $x(n)$是输入信号,$y(n)$是输出信号,$d(n)$是期望信号或参考信号,$e(n)=d(n)-y(n)$为误差信号.根据自适应算法和误差信号$e(n)$调整滤波器系数. 自适应滤波器类型.可以分为两大类:非线性自适应滤波器.线性自适应滤波器.非线性自适应滤波器包括基于神经网络的自适应滤波器及Vol

redis锁的进化历程

日常工作中总是会有高并发的场景,需要实现锁机制来保证序列性,接下来我们一步一步实现一个 单机Redis下完全可靠的Redis锁(ps: 如果是Redis集群的话,就存在主从切换锁失效的问题,解决这个问题的话就比较麻烦了,这里不做讨论,现有的解决方案有redlock,大家可以看下它的实现原理) Redis锁 第一版(php实现): //加锁 public function lock($key) { $redisConnect = Redis::connection(); $v = $redisCo

1. Introduction ——进化算法

本系列博客开始介绍进化算法. 知识内容来源于本人在硕士阶段听的课程以及阅读的文献书籍.算是听课笔记或是读书笔记吧. 博主在国外读的书,可能就中英文混杂了. Outline 什么是进化算法 能够解决什么样的问题 进化算法的重要组成部分 八皇后问题(实例) 1. 什么是进化算法 遗传算法(GA)是模拟生物进化过程的计算模型,是自然遗传学与计算机科学相互结合的新的计算方法. <图片来源于,Frank Neumann, The University of Adelaide> 2. 能够解决什么样的问题