一、试探回溯法(N皇后问题)

 一、试探回溯法概念

在介绍试探回溯法的概念之前,先简要介绍一个简短的古希腊神话故事,邪恶的半人半牛藏身于一个复杂的迷宫,很难找到它并杀死它,就是能成功杀死它怎么回来也是个难事。不过,在公主阿里阿德涅的帮助下,英雄忒修斯还是想到办法并消灭了怪物,并轻轻松松地走出了迷宫,他是怎么做到的呢?

其实,忒修斯使用的法宝很简单,就是一团普通的绳,他将绳子的一段系在迷宫的入口上,然后用手拿着另一端进入迷宫,在此后不断检查各个角落的过程中,线团或收或放,跟随着他辗转于曲折的迷宫中,确保它不会迷路。此外,为保证搜索过的角落不会重复,忒修斯在他已经检查过的而且确保不会有怪物的地方用粉笔做好记号,下次不会再去。

故事讲完了,古希腊神话故事中往往蕴含着很多哲理,这个故事也不例外,其实忒修斯的高招,与现代计算机中求解一些问题的试探回溯算法异曲同工,其中忒修斯探索未知角落就是试探,而发现该角落是错误的的并留下记号返回,这个过程是回溯,也可以叫做剪枝

试探:从长度上逐步向目标解靠近的尝试。

回溯(剪枝):做为解的局部特征,特征前缀在试探的过程中一旦被发现与目标解不合,则收缩到此前一步的长度,然后继续试探下一可能的组合。

应用:试探回溯法可解决若干元素为达到某种结果的最优排序方式问题,如N皇后问题和迷宫问题。

二、N皇后问题

世界历史上曾经出现一个伟大的罗马共和时期,处于权力平衡的目的,当时的政治理论家波利比奥斯指出:“事涉每个人的权利,绝不应该让任何权利大的压过其它力量,使他人无法立足于平等条件与之抗辩的地步。”这类似著名的N皇后问题,即在NXN格的国际象棋上摆放N个皇后,使其不能互相攻击,即任意两个皇后都不能处于同一行、同一列或同一斜线上,请问有多少中摆法,并将每种摆法打印出来。图1所示即是摆法的一种。

一、试探回溯法(N皇后问题)_第1张图片

对于N皇后问题,若采用普通穷举算法,则所有可能为n!=O(n^n)的复杂度,让人难以接受,故采用试探回溯法,可以尽早地发现不可能的情况进行回溯(剪枝),从而大大提高求解效率。

思路:N个皇后,N*N的方阵,已知queen不能同行,那么每行肯定都有一个皇后,那么问题转变成求N个y在满足皇后问题这一条件下的所有排序可能。

(1) 首先构造描述queen的类,其中重要的是判断queen之前是否冲突的==运算符。

#pragma once

class queen
{
public:
	//皇后的坐标(x,y)
	int x, y;

	//构造函数
	queen(int xx = 0, int yy = 0) :x(xx), y(yy) {}
	//析构函数
	~queen(){}

	//成员函数
	bool operator==(const queen& q) const  //重载判等运算符,判断皇后间是否冲突(同行、同列、正对角、反对角)
	{
		if (x == q.x || y == q.y) return true;    //同行或者同列
		if ((x - q.x) == (y - q.y)) return true;  //正对角
		if((x-q.x)==(q.y-y)) return true;         //反对角
		return false;
	}
	bool operator!=(const queen& q) const
	{
		return !(this->operator==(q));
	}
};

(2) 求解算法

int placeQueen(int N)    //试探回溯法解决N皇后问题
{
	stack solu;   //缓存当前解的路径                                y
	queen q(0, 0);       //第一个queen放在(0,0)
	int nSolu = 0;

	while (q.x >= 0)     //如果没尝试完所有情况则继续(迭代的最后会一直回溯到空)
	{
		while ((solu.size() < N))  //未形成全解,则继续
		{
			while ((q.y < N) && (solu.find(q)))   //横向遍历y的所有可能(当y没出界且当前queen的试探位置和已放置的queen冲突)
			{
				q.y++;
			}//横向遍历(y)结束
			if (q.y < N) //未出界则试探成功
			{
				solu.push(q);   //生长当前解
				q.x++;			//继续下一行(一行一个queen)
				q.y = 0;
			}
			else //出界,则上一个queen的位置导致无解,回溯(剪枝)
			{
				if (solu.empty())  //已经无试探点可弹,则确认问题无解
				{
					q.x = -1;
					cout << "======所有情况遍历完毕======" << endl;
					break;
				}

				q = solu.pop();   //尝试上个无解试探点的下一列
				q.y++;
			}
		}
		//出解
		if (solu.size() < N)
			cout << "无其他解" << endl;
		else
		{
			nSolu++;
			cout << "第" << nSolu << "个解: " << endl;
			q = solu.pop();   //返回上层继续寻找
			q.y++;
		}

	}
	return nSolu;
}

 

你可能感兴趣的:(算法)