八數碼問題(三種解決辦法)


題目鏈接: 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
*/

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM