双向广搜 bfs进阶 open the lock——hdu1195

目录

前言

   传统bfs

    双向广搜

open the lock

   问题描述

   输入

   输出

问题分析

   状态转变

   去重

单向搜索的bfs

双向广搜

   结束条件

   输出步数


前言

        其实这题数据不算复杂,不用双向广搜也可以完成,仅仅是为了更直观展现双向广搜的编码方式。

   传统bfs

        bfs向来都是搜索问题的一把好手,但一直以来bfs都有一个痛点,就是随着bfs层数的增加,bfs所要入队的元素一般会增长很快,有时的指数形,去重做的比较好那就是线性增长,但如果数据庞大是,这个数量依旧惊人

                       (线性增长)                                                  (指数增长)         

    双向广搜

        如果我们已知起点和终点,分别从起点和终点出发,那么就可以减少大量的状态数量

总结一点,如果去重都无法减少庞大的状态数量,那么就可以考虑双向广搜

open the lock

   问题描述

        有一个密码锁,总共四位数字,现已知四位数字的初始状态,和开锁密码,求最少几步可以开锁,密码锁上可以进行的操作,对于密码锁上的任意一位数字,都可以加一或减一,但如果数字原先为9,加一后为1,数字原先为1,减一后为9,也可以和该数字相邻的数字交换位置,最后一位数字和第一位数字不相邻,每次操作算一步

   输入

        第一行输入一个整数代表有多个测试,对于每个测试每行输入两个四位数,分别代表起始数字和密码锁密码

   输出

        对于每个测试,输出一个整数,代表所花费的最少步数

问题分析

        本题其实没有特别的难点,最主要的就是处理四位数之间状态的转变

   状态转变

        我使用了一个函数来封装这个转变:

string Skip(string& ss, int i, int j) {
    //注:字符-'0' 字符变数字,数字+'0' 数字变字符
    /*i:操作目标四位数的位数
      j:操作方法
      ss:目标四位数
    */
    int m;
    if (j == 1) {
        m = ss[i] - '0' + 1;
        if (m == 10) m = 1;
        ss[i] = m + '0';
    }
    if (j == 2) {
        m = ss[i] - '0' - 1;
        if (m == 0) m = 9;
        ss[i] = m + '0';
    }
    if (j == 3 && i <= 2) { //j<=2,最后一位数字不交换,其实也不是说不交换,就是我们只考虑单项交换,所以最后一位数字相当于不交换,其实他还是可以和他的前一位数字交换的,只是发起交换的对象不是它
        Swap(ss, i);
    }
    return ss;
}

        值得一提的是交换操作,最后一位数字不交换,其实也不是说不交换,就是我们只考虑单项交换,所以最后一位数字相当于不交换,其实他还是可以和他的前一位数字交换的,只是发起交换的对象不是它

   去重

        这里我使用的是map去重,也可以用set去重,详情请看bfs与判重

单向搜索的bfs

        其实本题单向搜索就可以完成了,因为判重操作后,每次进队的数量不会超过9999-1111,不算多,所以也可以通过:

#include<iostream>
#include<queue>
#include<map>
using namespace std;

string st, ed;

struct node {
    string s;
    int step;
    node(){}
    node(string ss,int st):s(ss),step(st){}
};

node now, nxt;
queue<node>que;
map<string, bool>mp;

void Swap(string& ss, int i) {
        char tmp = ss[i];
        ss[i] = ss[i + 1];
        ss[i + 1] = tmp;
}

string Skip(string& ss,int i,int j) {
    int m;
    if (j == 1) {
        m = ss[i] - '0' + 1;
        if (m == 10) m = 1;
        ss[i] = m + '0';
    }
    if (j == 2) {
        m = ss[i] - '0' - 1;
        if (m == 0) m = 9;
        ss[i] = m + '0';
    }
    if (j == 3 && i<=2) {
        Swap(ss, i);
    }
    return ss;
}

void bfs() {
    que.push(node(st, 0));
    mp[st] = true;
    while (!que.empty()) {
        now = que.front();
        que.pop();
        for (int i = 0; i < 4; i++) {  //交换操作或数值改变操作
            for (int j = 1; j <= 3; j++) {
                nxt.s = now.s;  //这个注意一下,每次修改都是在now.s的基础上该
                nxt.s = Skip(nxt.s, i, j);
                if (nxt.s == ed) {
                    cout << nxt.s << endl;
                    cout << now.step+1 << endl;
                    return;
                }
                if (!mp[nxt.s]) {
                    cout << nxt.s << endl;
                    nxt.step = now.step + 1;
                    que.push(node(nxt));
                    mp[nxt.s] = true;
                }
            }
        }
    }
}

int main() {
    int test;
    cin >> test;
    while (test--) {
        cin >> st;
        cin >> ed;
        //cout << st << ed;
        if (st == ed) {
            cout << 0 << endl;
            continue;
        }
        mp.clear();
        bfs();
    }
}

双向广搜

        使用双向广搜,可以减少一次进入队列的状态数量,也是个不错的算法

   结束条件

        单向搜索的结束条件可以根据是否到达终点判断,当时双向搜索是在中途就已经结束了,所以还需要对结束条件进行讨论

        我们定义了两个map,如果找到一个位置,两个map都已经搜过,那么这个点就是相遇点,结束搜索。

   输出步数

        单向搜索的输出很简单,只需要输出now.step即可,但是双向搜索显然不行,因为两个now节点不一定同时指向同一个四位数,所以还需要一个dis数组保存步数

        接下来就可以编写代码了(这里我使用双队列的双向广搜)

#include<iostream>
#include<queue>
#include<map>
using namespace std;

string st, ed;

struct node {
    string s;
    int step;
    node() {}
    node(string ss, int st) :s(ss), step(st) {}
};

node nowa, nxta;
node nowb, nxtb;;
queue<node>qa;  //从起点开始搜
queue<node>qb;  //从终点开始搜
map<string, bool>mp1;   //起点队列判重
map<string, bool>mp2;   //终点队列判重
map<string, int>dis;    //记录距离

void Swap(string& ss, int i) {
    //其实交换操作可以看成是单向的,因为双向和单向结果没区别
    char tmp = ss[i];
    ss[i] = ss[i + 1];
    ss[i + 1] = tmp;
}

string Skip(string& ss, int i, int j) {
    //注:字符-'0' 字符变数字,数字+'0' 数字变字符
    int m;
    if (j == 1) {
        m = ss[i] - '0' + 1;
        if (m == 10) m = 1;
        ss[i] = m + '0';
    }
    if (j == 2) {
        m = ss[i] - '0' - 1;
        if (m == 0) m = 9;
        ss[i] = m + '0';
    }
    if (j == 3 && i <= 2) { //j<=2,最后一位数字不交换,其实也不是说不交换,就是我们只考虑单项交换,所以最后一位数字相当于不交换,其实他还是可以和他的前一位数字交换的,只是发起交换的对象不是它
        Swap(ss, i);
    }
    return ss;
}

void bfs() {
    qb.push(node(ed, 0));
    qa.push(node(st, 0));
    dis[st] = 0;
    dis[ed] = 0;
    mp1[st] = true;
    mp2[ed] = true;
    while (!qa.empty() && !qb.empty()) {
        //保证起点和终点同步扩散
        if (qa.size() < qb.size()) {
            //起点
            nowa = qa.front();
            qa.pop();
            for (int i = 0; i < 4; i++) {  //交换操作或数值改变操作
                for (int j = 1; j <= 3; j++) {
                    nxta.s = nowa.s;  //这个注意一下,每次修改都是在now.s的基础上该
                    nxta.s = Skip(nxta.s, i, j);
                    if (mp2[nxta.s] == true) {
                        //cout << nxta.s << endl;
                        cout << nowa.step + dis[nxta.s]+1 << endl; //注意这里要加1,因为起点和终点的step都是0
                        return;
                    }
                    if (!mp1[nxta.s]) {
                        //cout << nxta.s << endl;
                        nxta.step = nowa.step + 1;

                        qa.push(node(nxta));
                        dis[nxta.s] = nxta.step;
                        mp1[nxta.s] =true;
                    }
                }
            }
        }
        else {
            //终点
            nowb = qb.front();
            qb.pop();
            for (int i = 0; i < 4; i++) {  //交换操作或数值改变操作
                for (int j = 1; j <= 3; j++) {
                    nxtb.s = nowb.s;  //这个注意一下,每次修改都是在now.s的基础上该
                    nxtb.s = Skip(nxtb.s, i, j);
                    if (mp1[nxtb.s]==true) {
                        cout << nowb.step +dis[nxtb.s]+1<< endl; 
                        return;
                    }
                    if (!mp2[nxtb.s]) {
                        nxtb.step = nowb.step + 1;

                        qb.push(node(nxtb));
                        dis[nxtb.s] = nxtb.step;
                        mp2[nxtb.s] = true;
                    }
                }
            }
        }
        
    }
}

int main() {
    int test;
    cin >> test;
    while (test--) {
        cin >> st;
        cin >> ed;
        if (st == ed) {
            cout << 0 << endl;
            continue;
        }
        bfs();
        //很多人喜欢用数组实现,但我还是倾向于用stl,更加简洁
        mp1.clear();
        mp2.clear();
        dis.clear();
    }
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值