八数码问题(三种解决办法)

题目链接: https://www.luogu.org/problemnew/show/P1379

题目链接:https://vijos.org/p/1360 (题目一样,上面一个测试数据更多)


0.问题引入

 在3×3的棋盘上,摆有八个棋子,每个棋子上标有1至8的某一数字。棋盘中留有一个空格,空格用0来表示。空格周围的棋子可以移到空格中。要求解的问题是:给出一种初始布局(初始状态)和目标布局(为了使题目简单,设目标状态为123804765)。

找到一种最少步骤的移动方法,实现从初始布局到目标布局的转变。

1. 广度优先遍历

  • 用字符串表示状态。
  • 使用 map<string,bool> 进行判定当前状态是否进行过搜索。
  • 记录进行到第几层BFS,需要一个 9! 的数组(0~8全排列的个数)。

这题是说明了不会有无解的情况,真正情况下是可能会有无解的情况的,出现在 队列为空还未走到目标状态。

 

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

map<string, bool>m;
int cnt[400000]; int head = 0, tail = 1;
string aim = "123804765";

//1:up,2:down,3:left,4:right
int moveto(int i, int index) {
    switch (i) {
    case 1:
        if (index - 3 >= 0)return index - 3;
        break;
    case 2:
        if (index + 3 <= 8)return index + 3;
        break;
    case 3:
        if (index % 3 != 0)return index - 1;
        break;
    case 4:
        if (index % 3 != 2)return index + 1;
        break;
    }
    return -1;
}

void BFS(string start) {
    if (start.compare(aim) == 0) {
        cout << '0' << endl;
        return;
    }
    bool flag = false;
    queue<string>q;
    q.push(start);
    
    while (!q.empty() && !flag) {
        string now = q.front(); q.pop();
        int from = now.find('0');
        //向四个方向
        for (int i = 1; i <= 4; i++) {
            int to = moveto(i, from);
            //如果返回-1,当前方向无法到达
            if (to == -1)continue;
            swap(now[from], now[to]);
            if (!m[now]) {
                if (now.compare(aim) == 0) {
                    cout << cnt[head] + 1 << endl;//输出当前BFS的层数
                    flag = true;
                    break;
                }
                cnt[tail++] = cnt[head] + 1;
                //cnt[tail++] = cnt[head] + 1;
                m[now] = true;
                q.push(now);
            }
            swap(now[from], now[to]);
        }
        head++;
    }
}

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

    return 0;
}
View Code

 

   

2.DBFS 双向广度优先遍历

双向广度优先,两个队列,一个从起点开始扩展状态,另一个从终点开始扩展状态;如果两者相遇,则表示找到了一条通路,而且是最短的通路。

双向,必须要记录下来每个状态处于第几层,因为相遇的时候,必须知道这个相遇状态是处于BFS的哪一层。

#include <iostream>
#include <string>
#include <queue>
#include <map>

using namespace std;

//记录该string状态处于正(反)向的第几层BFS
map<string, int>fsign, rsign;
int fcnt[300000];
int rcnt[300000];

string aim = "123804765";

//1:up,2:down,3:left,4:right
int moveto(int i, int index) {
    switch (i) {
    case 1:
        if (index - 3 >= 0)return index - 3;
        break;
    case 2:
        if (index + 3 <= 8)return index + 3;
        break;
    case 3:
        if (index % 3 != 0)return index - 1;
        break;
    case 4:
        if (index % 3 != 2)return index + 1;
        break;
    }
    return -1;
}

bool extend(queue<string>&q, int head, int &tail, int direction) {
    string now = q.front(); q.pop();
    int from = now.find('0');
    for (int i = 1; i <= 4; i++) {
        int to = moveto(i, from);
        if (to != -1) {
            swap(now[from], now[to]);
            if (direction == 1) {
                if (!fsign[now]) {
                    q.push(now);
                    fsign[now] = fcnt[tail++] = fcnt[head] + 1;
                    if (rsign[now]) {
                        cout << fcnt[tail - 1] + rsign[now] << endl;
                        return true;
                    }
                }
            }
            else {
                if (!rsign[now]) {
                    q.push(now);
                    rsign[now] = rcnt[tail++] = rcnt[head] + 1;
                    if (fsign[now]) {
                        cout << rcnt[tail - 1] + fsign[now] << endl;
                        return true;
                    }

                }
            }
            swap(now[from], now[to]);
        }
    }
    return false;
}

void DBFS(string start) {
    if (start.compare(aim) == 0) {
        cout << '0' << endl;
        return;
    }
    bool flag = false;
    queue<string>forward, reverse;
    forward.push(start);
    reverse.push(aim);
    string forwardNow = start, reverseNow = aim;
    fsign[start] = 0; rsign[aim] = 0;
    int head = 0, tail = 1, rhead = 0, rtail = 1;
    while (!flag) {
        if (forward.size() <= reverse.size()) {
            if (extend(forward, head, tail, 1))return;
            head++;
        }
        else {
            if (extend(reverse, rhead, rtail, 2))return;
            rhead++;
        }
    }
}

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

    DBFS(start);

    return 0;
}
View Code

 3.A*(启发式搜索)

启发式搜索_百度百科

启发策略:f(n) = g(n) + h(n),其中 g(n) 为层数,h(n)为当前状态“不在位”的数量。建立优先级队列,每次选取 f(n) 最小的状态。

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

string aim = "123804765";
map<string, bool>flag;

int getDifferent(string s) {
    int cnt = 0;
    for (int i = 0; i < s.length(); i++) {
        if (s[i] != aim[i])cnt++;
    }
    return cnt;
}
class PAIR{
public:
    int floor;//层数
    int cnt;//"不在位"的块数
    string ss;
    PAIR(int floor, string s) {
        this->floor = floor;
        cnt = getDifferent(s);
        ss = s;
    }
    bool operator<(PAIR f)const {
        return (f.floor + f.cnt) < (floor + cnt);
    }
};
priority_queue<PAIR>q;

int moveto(int i, int index) {
    switch (i) {
    case 1:
        if (index - 3 >= 0)return index - 3;
        break;
    case 2:
        if (index + 3 <= 8)return index + 3;
        break;
    case 3:
        if (index % 3 != 0)return index - 1;
        break;
    case 4:
        if (index % 3 != 2)return index + 1;
        break;
    }
    return -1;
}

void BFS(string start) {
    if (start.compare(aim) == 0) {
        cout << "0" << endl;
        return;
    }
    q.push(PAIR(0, start));
    int head = 0, tail = 1;
    bool sign = false;
    while (!q.empty()&&!sign) {
        PAIR p = q.top(); q.pop();
        string now = p.ss;
        int from = now.find('0');
        for (int i = 1; i <= 4; i++) {
            int to = moveto(i, from);
            if (to != -1) {
                swap(now[from], now[to]);
                if (!flag[now]) {
                    if (now.compare(aim) == 0) {
                        cout << p.floor + 1 << endl;
                        sign = true;
                        break;
                    }
                    flag[now] = true;
                    q.push(PAIR(p.floor + 1, now));
                }
                swap(now[from], now[to]);
            }
        }
        head++;
    }
}

int main() {            
    string start;
    cin >> start;
    BFS(start);
    return 0;
}
View Code

但是在这个题目,两个网站上跑得都更慢了,应该数据比较特殊。

 附测试数据:

/*
273645801
out:15

053276184
out:21

836752104
out:14
*/

 

posted @ 2019-02-25 17:05  czc1999  阅读(3268)  评论(0编辑  收藏  举报