Sicily 1153. 马的周游问题

题目链接在此

用1152的解法做果断TLE,然后优化了一下,采用了类似启发式搜索的做法,答案秒出。


数据结构与算法思想

1 数据结构 

①用结构体记录每个位置,其中包含其坐标:

struct pos {

int row;

int col;

};

 

②用数组存储是否访问过某位置:

bool visited[65];

 

③用数组记录“周游”的顺序:

int printSeq[65];

 

2)算法思想

①使用DFS深度优先搜索算法)。

②在搜多的过程中采用“启发式搜索”,以减少回溯次数。

 

详细解题思路 

①DFS:从起点开始,搜索下一步可访问且未被访问的位置,直到所有位置都已经被访问过。

②启发式搜索:获取当前位置的下一步可移动至的所有位置,将这些所有位置排序,按这个顺序接着进行DFS。排序的标准:按这些位置的下一步可移动至的所有位置的多少,由少到多排序。

 

逐步求精算法描述

本题解法的关键在于深度优先搜索算法:

①初始化:将起点标记为已访问,周游顺序数组的第一项记为起点的数字标号。   同时,访问计数器设为2(目的是方便记录周游顺序);

a.若访问计数器为65,则说明周游完成,输出答案并跳出步骤②。

  b.找出当前位置下一步能访问且未被访问的所有位置,并按“启发式搜索”中描 述的排序标准给这些位置进行排序。

   c.将步骤b中已排序的每个位置(记为next)逐一访问,即:

I.将位置next记为已访问;

II.周游顺序数组的第counter(访问计数器)项记为next的数字标号;

III.访问计数器自增1;

IV.nextDFS,即从步骤a开始循环

V.回溯:将位置next记为未访问;

VI.访问计数器自减1;


源代码与注释:

#include<iostream>
#include<vector>
#include<algorithm>

using namespace std;

struct pos {
	int row;
	int col;
	  //  结构体构造函数
	pos() {}
	pos(int r, int c) : row(r), col(c) {}
	  //  结构体自定义函数,两位置横纵坐标互相相加
	pos plus(pos a) { 
		return pos(a.row + this->row, a.col + this->col);
	}
};
bool visited[65];  //  访问数组
int printSeq[65];  //  周游顺序

const struct pos available[] = { pos(1, -2), pos(2, -1), pos(2, 1), pos(1, 2),
pos(-1, 2), pos(-2, 1), pos(-2, -1), pos(-1, -2) };  //  马可移动的8个方向的坐标

  //  函数:将位置的数字标号解析成横纵坐标,构造pos结构体返回
struct pos one_to_two_dim(int n) {
	int r = (n - 1) / 8 + 1;
	int c = (n - 1) % 8 + 1;
	return pos(r, c);
}

  //  函数:将位置的横纵坐标解析,
  //  若合法(横纵坐标都在[1,8]之间)返回其对应的数字标号,否则返回0
int two_to_one_dim(struct pos p) {
	if (p.row >= 1 && p.row <= 8 && p.col >= 1 && p.col <= 8)
		return (p.row - 1) * 8 + p.col;
	else
		return 0;
}

  //  函数:接收位置的数字标号,找出其下一步能访问且未被访问的位置的数字标号,
  //  存于一个vector中返回
void canMove(int n, vector<int>& result) {
	for (int i = 0; i < 8; i++) {
		struct pos tmp = one_to_two_dim(n);
		int going_to = two_to_one_dim(tmp.plus(available[i]));

		if (going_to >= 1 && going_to <= 64 && visited[going_to] == false)
			result.push_back(going_to);
	}
}

//  函数:接收位置的数字标号,找出其下一步能访问且未被访问的位置的总数量,并返回
int countCanMove(int n) {
	int result = 0;
	for (int i = 0; i < 8; i++) {
		struct pos tmp = one_to_two_dim(n);
		int going_to = two_to_one_dim(tmp.plus(available[i]));

		if (going_to >= 1 && going_to <= 64 && visited[going_to] == false)
			result++;
	}
	return result;
}

  //  自定义比较函数,用于启发式搜索前的排序:
  //  接受两个位置的数字标号,若前者下一步能访问且未被访问的位置的总数量大于后者,返回真,否则返回假。
bool myCmp(int a, int b) {
	return countCanMove(a) < countCanMove(b);
}

  //  深度优先搜寻算法。传入:当前位置的数字标号,是否完成搜索(布尔值),访问计数器
void DFS(int n, bool& isDone, int counter) {
	if (isDone)  //  若已完成搜索则返回
		return;

	if (counter == 65) {  //  若访问计数器为65,周游完成,输出结果
		isDone = true;  //  是否完成搜索的布尔值设为真

		bool spaceFlag = false;
		for (int i = 1; i <= 64; i++) {  //  利用周游顺序数组逐一输出结果
			if (!spaceFlag)
				spaceFlag = true;
			else
				cout << ' ';

			cout << printSeq[i];
		}
		cout << endl;
	}
	else {
		vector<int> canGoTo;
 //  找出当前位置下一步能访问且未被访问的所有位置,数字标号存于canGoTo容器中
		canMove(n, canGoTo); 		
 //  按“启发式搜索”中描述的排序标准给canGoTo容器中的位置排序。
sort(canGoTo.begin(), canGoTo.end(), myCmp);  

		for (int i = 0; i < canGoTo.size(); i++) {
			visited[canGoTo[i]] = true;  //  将位置canGoTo[i]记为已访问
			printSeq[counter] = canGoTo[i];  //周游顺序数组的第counter项记为canGoTo[i]
			counter++;  //  访问计数器自增1

			DFS(canGoTo[i], isDone, counter);  //  将canGoTo[i]做DFS

			visited[canGoTo[i]] = false;  //  回溯:将位置canGoTo[i]记为未访问
			counter--;  //  访问计数器自减1
		}
	}
}

int main() {
	int start;
	cin >> start;

	while (start != -1) {
		bool isDone = false;  //  初始化标记是否完成搜索的布尔值

		for (int i = 1; i <= 64; i++) {  //  初始化周游顺序数组以及访问数组
			printSeq[i] = 0;
			visited[i] = false;
		}

		printSeq[1] = start;   //  周游顺序数组的第一项记为起点的数字标号
		visited[start] = true;   //  将起点标记为已访问

		DFS(start, isDone, 2);  //  对起点进行DFS

		cin >> start;
	}

	return 0;
}


你可能感兴趣的:(sicily)