经典回溯法:N皇后问题、马周游问题

回溯法是一种选优搜索法,很多情况下思路和图的深度遍历很相似。下面主要分享两道比较典型的回溯法问题。
一 .N皇后问题(题目来源:SOJ)
Description
Given N queens on an N*N chess board, find the number of ways of placing these queens so that they will not attack each other.

Use backtracking to solve the problem.

Input
There are multiple cases. For each case, there is only one line given the number of queens N (1<=N<=9).

Output
For each case, output the number of all possible placements of queens.

Sample Input
4
Sample Output
2
题目大意:在一个N*N的棋盘下摆放N个皇后并使其不能互相攻击(皇后可攻击同行、同列、对角线),问一共有多少种摆放方法?
思路:每行摆放一个皇后。从第一行开始遍历,能摆放下就看下一行。用一个数组来存储第i行摆放的皇后的列数。

#include<iostream>
#include<stdlib.h>
#include<cmath>
#include<string.h>
using namespace std;
int counter = 0;
int n;
int a[10];

bool can_place(int row,int col) {//判断此位置能否摆放
	if (row == 0) return true;
	else {
		for (int i = 0; i < row; ++i) {
			if (a[i] == col) return false;
			if (abs(i - row) == abs(a[i] - col)) return false;//对角线
		}
		return true;
	}
}

void backtrack(int t) {
	if (t ==n ) {//摆满了n行,说明这种摆法符合要求
		counter++;
		return;
	}
	else {
		for (int i = 0; i < n; ++i) {
			if (can_place(t, i)) {
				a[t] = i;
				backtrack(t + 1);//第t行可以摆放,判断第t+1行
			}
		}
	}
}

int queue(int input) {
	n = input;
	counter = 0;
	memset(a, -1, sizeof(a));
	backtrack(0);
	return counter;
}

int main() {
	int n;
	while (cin >> n && n) {
		cout << queue(n) << endl;
	}
	return 0;
}

二.马周游问题(题目来源:SOJ1153)
   在一个8*8的棋盘中的某个位置有一只马,如果它走63步正好经过除起点外的其他位置各一次,这样一种走法则称马的周游路线,试设计一个算法,从给定的起点出发,找出它的一条周游路线。
为了便于表示一个棋盘,我们按照从上到下,从左到右对棋盘的方格编号,如下所示:
1   2   3   4  5   6   7   8
9   10 11 12 13 14 15 16
17 18 19 20 21 22 23 24
25 26 27 28 29 30 31 32
33 34 35 36 37 38 39 40
41 42 43 44 45 46 47 48
49 50 51 52 53 54 55 56
57 58 59 60 61 62 63 64

马的走法是“日”字形路线,例如当马在位置3的时候,它可以到达9、18、20、13。但是规定马是不能跳出棋盘外的,例如从位置1只能到达18和11。
Input
输入有若干行。每行一个整数N(1<=N<=64),表示马的起点。最后一行用-1表示结束,不用处理。

Output
对输入的每一个起点,求一条周游线路。对应地输出一行,有64个整数,从起点开始按顺序给出马每次经过的棋盘方格的编号。相邻的数字用一个空格分开。

Sample Input
4
-1

Sample Output(一组输出中间没有换行)
4 10 25 42 57 51 61 55 40 23 8 14 24 7 13 3 9 26 41 58 52 62 56 39 54 64 47 32 15 5 11 1 18 33 50 60 43 49 59 53 63 48 31 16 6 12 2 17 34 44 38 21 27 37 22 28 45 35 20 30 36 46 29 19

思路:
     我们可以使用深度优先搜索,枚举所有可能的马行走路线,直到找到一条能完成马周游的路线,即回溯法。我们将棋盘上点的坐标和点的编号一一对应,比如点(0,5)对应6,点(4,3)对应36。注意(x,y)的取值范围:0<=x<=7,0<=y<=7。对于一个点,接下来有可能有八种走法,我们可以使用for循环遍历每一种走法。每一种走法中,我们递归地往下走,需要注意的是,在单次循环结束前一定要恢复状态:把访问标记设为false,将已走步数减一,这样才不会影响下一次走动。
     但是, 由于棋盘是8*8大小,我们遍历所有路线很可能会超时。思考可以发现,程序耗时的最主要原因是一条路走不通,我们得回到上一步重新走。并且,如果在某一格子有很多可能的走法(最多八种),但这都是行不通的,那么我们走到这一点就会浪费很多的时间。因此采取以下策略:优先访问下一步可行格子少的格子。实现办法很简单,只需要在作for循环之前根据可行格子数目对将要访问的点进行排序即可。

#include<iostream>
#include<string.h>
#include<algorithm>
#include<vector>

using namespace std;
int path[64];
bool is_visited[65];//初始化为false
bool finished;
struct pos {
	int x;
	int y;
	pos(int x1, int y1) {
		x = x1;
		y = y1;
	}
};

int get_x(pos position) {
	return position.x * 8 + 1 + position.y;
}

pos dir[8] = { pos(1,-2),pos(1,2),pos(2,1),pos(2,-1),pos(-1,2),pos(-1,-2),pos(-2,-1),pos(-2,1) };

int count_step(pos p) {
	int count = 0;
	for (int i = 0; i < 8; ++i) {
		pos next(p.x + dir[i].x, p.y + dir[i].y);
		if (next.x >= 0 && next.x <= 7 && next.y >= 0 && next.y <= 7)
			count++;
	}
	return count;
}

bool cmp(pos p1, pos p2) {
	return count_step(p1) < count_step(p2);
}

void solve(int count, pos now) {
	if (finished) return;
	if (count == 64) {
		finished = true;
		for (int i = 0; i < 63; ++i)
			cout << path[i] << " ";
		cout << get_x(now) << endl;
		return;
	}
	else {
		vector<pos> tmp;
		for (int i = 0; i < 8; ++i) {
			pos p(now.x + dir[i].x, now.y + dir[i].y);
			tmp.push_back(p);
		}
		sort(tmp.begin(), tmp.end(), cmp);
		for (int i = 0; i < 8; ++i) {
			pos p(tmp[i].x, tmp[i].y);
			if (!is_visited[get_x(p)] && p.x >= 0 && p.x <= 7 && p.y >= 0 && p.y <= 7) {
				path[count] = get_x(p);
				is_visited[get_x(p)] = true;
				count++;
				solve(count, p);
				count--;
				is_visited[get_x(p)] = false;
			}
		}
	}

}

int main() {
	int n;
	while (cin >> n && n > 0) {
		memset(is_visited, false, sizeof(is_visited));
		pos start((n - 1) / 8, (n - 1) % 8);
		path[0] = n;
		finished = false;
		is_visited[n] = true;
		solve(1, start);
	}
	return 0;
}

在使用回溯算法时,如果有时候对时间要求比较严格,我们就需要改进算法。先访问可能情况少的位置,判断此路不通后耗费的期望时间也比较少。总体来看,这样可以尽可能地做到时间的节约,是一种很好的思路。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值