《挑战程序设计竞赛》学习笔记(二):穷竭搜索

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;
    }
}

内存中的堆栈

这里写图片描述
栈内存区:局部变量,调用时统一分配,有上限
堆内存区:全局变量

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值