2.1 穷竭搜索
深度优先搜索
部分和问题
/***************************************************
User name: 寻雾启示wpf
Note: p30 暴力深搜
****************************************************/
#include<iostream>
#include<algorithm>
#define MAX 21
using namespace std;
int flag = 0;
bool dfs(int *a, int i, int sum, int n, int k)
{
if (flag) return 1;
if (sum > k)//剪枝
return 0;
if (i == n) { flag = (sum == k); return flag; }
else
return dfs(a, i + 1, sum, n, k) || dfs(a, i + 1, sum + a[i], n, k);
}
int main()
{
int n, k, a[MAX];
while (cin >> n){
flag = 0;
for (int i = 0; i < n; i++)
cin >> a[i];
cin >> k;
if (dfs(a, 0, 0, n, k)) cout << "Yes" << endl;
else cout << "No" << endl;
}
return 0;
}
Lake Counting
/***************************************************
题目:2386
内存:648kB
时间:5ms
语言:G++
提交时间:2017-10-12 13:20:26
User name: 寻雾启示wpf
Note: p32 递归深搜 时间复杂度O(n*m) 空间复杂度O(n*m)
****************************************************/
#include<iostream>
using namespace std;
#define MAX 101
int num = 0, n, m;
char a[MAX][MAX];
bool judge[MAX][MAX] = { 0 };
void dfs(int i, int j)
{
if (i < 0 || i >= n || j < 0 || j >= m)
return;
if (a[i][j] != 'W' || judge[i][j] != 0)
return;
judge[i][j] = 1;
for (int k = -1; k < 2; k++)
for (int r = -1; r < 2; r++)
dfs(i + k, j + r);
}
int main()
{
cin >> n >> m;
for (int i = 0; i < n; i++)
for (int j = 0; j < m; j++)
cin >> a[i][j];
for (int i = 0; i < n; i++)
for (int j = 0; j < m; j++)
if (a[i][j] == 'W'&&judge[i][j] == 0) { num++; dfs(i, j); }
cout << num << endl;
return 0;
}
空间复杂度优化:将judge数组去掉
/***************************************************
题目:2386
内存:648kB
时间:3ms
语言:G++
提交时间:2017-10-12 13:31:30
User name: 寻雾启示wpf
Note: p32 递归深搜 空间复杂度优化为O(1) 时间复杂度O(n*m)
****************************************************/
#include<iostream>
using namespace std;
#define MAX 101
int num = 0, n, m;
char a[MAX][MAX];
void dfs(int i, int j)
{
if (i < 0 || i >= n || j < 0 || j >= m || a[i][j] != 'W')
return;
a[i][j] = '.';
for (int k = -1; k < 2; k++)
for (int r = -1; r < 2; r++)
dfs(i + k, j + r);
}
int main()
{
cin >> n >> m;
for (int i = 0; i < n; i++)
for (int j = 0; j < m; j++)
cin >> a[i][j];
for (int i = 0; i < n; i++)
for (int j = 0; j < m; j++)
if (a[i][j] == 'W') { num++; dfs(i, j); }
cout << num << endl;
return 0;
}
广度优先搜索
介绍:
迷宫最短路径
分析:
/***************************************************
User name: 寻雾启示wpf
Note: p32 递归深搜做法 空间复杂度O(1) 时间复杂度O(n*m)
****************************************************/
#include <iostream>
using namespace std;
char a[101][101];
int step = 1 << 30;
int m, n;
int start_i = 0, start_j = 0;
int orient[4][2] = { -1,0,0,-1,1,0,0,1 };
void dfs(int i, int j,int step_){
if (i < 0 || i >= n || j < 0 || j >= m || a[i][j] == '#' || step_ >= step)
return;
if (a[i][j] == 'G'){
step = step_;
return;
}
a[i][j] = '#';
for (int k = 0; k < 4; k++)
dfs(i + orient[k][0], j + orient[k][1], step_ + 1);
}
int main()
{
cin >> n >> m;
for (int i = 0; i < n; i++)
for (int j = 0; j < m; j++){
cin >> a[i][j];
if (a[i][j] == 'S'){
start_i = i;
start_j = j;
}
}
dfs(start_i, start_j, 0);
cout << step << endl;
return 0;
}
宽搜实现:
/***************************************************
User name: 寻雾启示wpf
Note: p32 宽搜做法 空间复杂度O(n*m) 时间复杂度O(n*m)
宽搜队列实现
起始位置入队列,队列不空就循环,找到终点就break,
否则下一层节点入队列;
****************************************************/
#include <iostream>
#include <queue>
using namespace std;
#define INF 1<<30
#define MAX 101
typedef pair<int, int> P;
char a[MAX][MAX];//存地图
int m, n, sx = 0, sy = 0, gx = 0, gy = 0;
int dx[4] = { 1,0,-1,0 }, dy[4] = { 0,1,0,-1 };
int d[MAX][MAX];//到各个位置最短距离的数组
//求从a[sx][sy]到a[gx][gy]的最短距离,到不了就还是INF
int bfs()
{
queue<P> q;
q.push(P(sx, sy));
d[sx][sy] = 0;
while (!q.empty())
{
P p = q.front(); q.pop();
if (p.first == gx&&p.second == gy)
break;
for (int k = 0; k < 4; k++)
{
int nx = p.first + dx[k], ny = p.second + dy[k];
if (0 <= nx&&nx < n && 0 <= ny&&ny < m&&a[nx][ny] != '#'&&d[nx][ny] == INF)
{
q.push(P(nx, ny));
d[nx][ny] = d[p.first][p.second] + 1;
}
}
}
return d[gx][gy];
}
int main()
{
cin >> n >> m;
for (int i = 0; i < n; i++)
for (int j = 0; j < m; j++)
{
d[i][j] = INF;//距离初始化
cin >> a[i][j];
if (a[i][j] == 'S') { sx = i; sy = j; }
if (a[i][j] == 'G') { gx = i; gy = j; }
}
cout << bfs() << endl;
system("pause");
return 0;
}
小结:
生成1~n的全排列
利用递归+回溯进行深搜,used数组标记
#include <iostream>
using namespace std;
#define MAX 10001
bool used[MAX] = { 0 };
int perm[MAX] = { 0 };
int num = 0;
void permutation(int pos, int n)
{
if (pos == n){
/*
*生成了一种全排列存于perm中,可以执行某操作
eg.打印输出:
num++; printf("第%d种全排列:", num);
for (int i = 0; i < n; i++)
printf("%d",perm[i]);
cout << endl;
*/
return;
}
for (int i = 0; i < n; i++)
if (!used[i])
{
perm[pos] = i+1;
used[i] = 1;
permutation(pos + 1, n);
used[i] = 0;
}
return;
}
int main()
{
int n; cin >> n;
permutation(0, n);
return 0;
}
也可以直接用STL中的函数next_permutation生成
函数原型:
template<class _BidIt> inline
bool next_permutation(_BidIt _First, _BidIt _Last, _Pr _Pred)
//默认用<比较,参数给定一个排列区间[ , )
#include <iostream>
#include<algorithm>
using namespace std;
#define MAX 10001
int perm[MAX] = { 0 };
void permutation(int n)
{
//先生成第一种
for (int i = 0; i < n; i++)
perm[i] = i + 1;
do
{
/*
*生成了一种全排列存于perm中,可以执行某操作
*/
return;
} while (next_permutation(perm, perm + n));
return;
}
int main()
{
int n; cin >> n;
permutation(n);
return 0;
}
关于next_permutation的实现原理:
在当前序列中,从尾端向前寻找两个相邻元素,前一个记为i,后一个记为t,并且满足i < t。然后再从尾端寻找另一个元素j,如果满足i < j,即将第i个元素与第j个元素对调,并将第t个元素之后(包括t)的所有元素颠倒排序,即求出下一个序列了。
八数码问题
利用生成全排列和单向广搜的方法可以实现八数码问题。
/***************************************************
题目:poj1077
内存:2568kB
时间:461ms
语言:G++
提交时间:2017-10-13 16:20:07
User name: 寻雾启示wpf
Note: 八数码 单项bfs
****************************************************/
#include <iostream>
#include <bitset>
#include <cstring>
using namespace std;
int goalStatus; //目标状态
bitset<362880> Flags; //节点是否扩展的标记
const int MAXS = 400000; //>400000
char result[MAXS]; //结果
struct Node {
int status; //状态, 即排列的编号
int father; //父节点指针
char move; //父节点到本节点的移动方式 u/d/r/l
Node(int s, int f, char m) :status(s), father(f), move(m) { }
Node() { }
};
Node myQueue[MAXS]; //状态队列, 状态总数362880
int qHead;
int qTail;
//队头指针和队尾指针
char sz4Moves[] = "udrl"; //四种动作
unsigned int factorial[21];
//存放0-20的阶乘.21的阶乘unsigned放不下了
//给定排列求序号
unsigned int GetPermutationNumForInt(int * perInt, int len) {
//perInt里放着整数0到(len-1)的一个排列,求它是第几个排列
//len不能超过21
unsigned int num = 0;
bool used[21];
memset(used, 0, sizeof(bool)*len);
for (int i = 0; i < len; ++i) {
unsigned int n = 0;
for (int j = 0; j < perInt[i]; ++j) {
if (!used[j]) ++n;
}
num += n * factorial[len - i - 1];
used[perInt[i]] = true;
}
return num;
}
//给定排列,求序号
template< class T>
unsigned int GetPermutationNum(T s1, T s2, int len) {
//[s1,s1+len)里面放着第0号排列,[s2,s2+len)是要求序号的排列
//两者必须一样长, len不能超过21
//排列的每个元素都不一样.返回排列的编号
int perInt[21]; //要转换成 [0, len-1] 的整数的排列
for (int i = 0; i < len; ++i)
for (int j = 0; j < len; ++j) {
if (*(s2 + i) == *(s1 + j)) {
perInt[i] = j;
break;
}
}
unsigned int num = GetPermutationNumForInt(perInt, len);
return num;
}template <class T>
void GenPermutationByNum(T s1, T s2, int len, unsigned int No)
//根据排列编号, 生成排列 len不能超过21
{ //[s1, s1+len) 里面放着第0号 permutation, 排列的每个元素都不一样
int perInt[21]; //要转换成 [0, len-1] 的整数的排列
bool used[21];
memset(used, 0, sizeof(bool)*len);
for (int i = 0; i < len; ++i) {
unsigned int tmp; int n = 0; int j;
for (j = 0; j < len; ++j) {
if (!used[j]) {
if (factorial[len - i - 1] >= No + 1) break;
else No -= factorial[len - i - 1];
}
}
perInt[i] = j;
used[j] = true;
}
for (int i = 0; i < len; ++i)
* (s2 + i) = *(s1 + perInt[i]);
}
//字符串形式的状态, 转换为整数形式的状态(排列序号)
int StrStatusToIntStatus(const char * strStatus) {
return GetPermutationNum("012345678", strStatus, 9);
}
//整数形式的状态(排列序号) , 转换为字符串形式的状态
void IntStatusToStrStatus(int n, char * strStatus) {
GenPermutationByNum((char*)"012345678", strStatus, 9, n);
}
int NewStatus(int nStatus, char cMove) {
//求从nStatus经过 cMove 移动后得到的新状态. 若移动不可行则返回-1
char szTmp[20]; int nZeroPos;
IntStatusToStrStatus(nStatus, szTmp);
for (int i = 0; i < 9; ++i)
if (szTmp[i] == '0') {
nZeroPos = i;
break;
} //返回空格的位置
switch (cMove) {
case 'u': if (nZeroPos - 3 < 0) return -1; //空格在第一行
else {
szTmp[nZeroPos] = szTmp[nZeroPos - 3];
szTmp[nZeroPos - 3] = '0';
}
break;
case 'd': if (nZeroPos + 3 > 8) return -1; //空格在第三行
else {
szTmp[nZeroPos] = szTmp[nZeroPos + 3];
szTmp[nZeroPos + 3] = '0';
}
break;
case 'l': if (nZeroPos % 3 == 0) return -1;
//空格在第一列
else {
szTmp[nZeroPos] = szTmp[nZeroPos - 1];
szTmp[nZeroPos - 1] = '0';
}
break;
case 'r': if (nZeroPos % 3 == 2) return -1;
//空格在第三列
else {
szTmp[nZeroPos] = szTmp[nZeroPos + 1];
szTmp[nZeroPos + 1] = '0';
}
break;
}
return StrStatusToIntStatus(szTmp);
}
bool Bfs(int nStatus) { //寻找从初始状态nStatus到目标的路径
int nNewStatus; Flags.reset(); //清除所有扩展标记
qHead = 0; qTail = 1;
myQueue[qHead] = Node(nStatus, -1, 0);
while (qHead != qTail) { //队列不为空
nStatus = myQueue[qHead].status;
if (nStatus == goalStatus) //找到目标状态
return true;
for (int i = 0; i < 4; i++) { //尝试4种移动
nNewStatus = NewStatus(nStatus, sz4Moves[i]);
if (nNewStatus == -1) continue; //不可移, 试下一种
if (Flags[nNewStatus]) continue; //扩展标记已经存在, 则不入队
Flags.set(nNewStatus, true); //设上已扩展标记
myQueue[qTail++] = Node(nNewStatus, qHead, sz4Moves[i]);
//新节点入队列
}
qHead++;
}
return false;
}
int main() {
factorial[0] = factorial[1] = 1;
for (int i = 2; i < 21; ++i)
factorial[i] = i * factorial[i - 1];
goalStatus = StrStatusToIntStatus("123456780");
char szLine[50];
char szLine2[20];
while (cin.getline(szLine, 48)) {
int i, j;
//将输入的原始字符串变为数字字符串
for (i = 0, j = 0; szLine[i]; i++) {
if (szLine[i] != ' ') {
if (szLine[i] == 'x') szLine2[j++] = '0';
else szLine2[j++] = szLine[i];
}
}
szLine2[j] = 0; //字符串形式的初始状态
int sumGoal = 0; //从此往后用奇偶性判断是否有解
for (int i = 0; i < 8; ++i)
sumGoal += i - 1;
int sumOri = 0;
for (int i = 0; i < 9; ++i) {
if (szLine2[i] == '0')
continue;
for (int j = 0; j < i; ++j) {
if (szLine2[j] < szLine2[i] && szLine2[j] != '0')
sumOri++;
}
}
if (sumOri % 2 != sumGoal % 2) {
cout << "unsolvable" << endl;
continue;
}
//上面用奇偶性判断是否有解
if (Bfs(StrStatusToIntStatus(szLine2))) {
int nMoves = 0;
int nPos = qHead;
do { //通过father找到成功的状态序列, 输出相应步骤
result[nMoves++] = myQueue[nPos].move;
nPos = myQueue[nPos].father;
} while (nPos); //nPos = 0 说明已经回退到初始状态了
for (int i = nMoves - 1; i >= 0; i--)
cout << result[i];
}
else
cout << "unsolvable" << endl;
}
}
内存中的堆栈
栈内存区:局部变量,调用时统一分配,有上限
堆内存区:全局变量