八數碼問題(DFS,BFS,A*)


DFS,BFS的open表分別使用棧、隊列

A*的open表使用優先隊列

close表都使用集合

使用了兩種啟發函數:Fn=Gn+Hn,Fn=Hn.

#include <queue>
#include <stack>
#include <unordered_set>
#include <unordered_map>
#include <string>
#include <iostream>
#include <cstdlib>
#include <ctime>
using namespace std;

struct borad {
    int status[9];//status[0]到status[8]表示3X3的矩陣,0表示空格
    int depth;//深度
    int Fn;//啟發函數值,Fn = depth + hn即深度加曼哈頓距離
    borad* pre;//父指針,指向移動前的棋盤狀態
    borad() : pre(0), status(), depth(0), Fn(INT_MAX - 1) {
        for (int j = 0; j < 9; j++) {
            status[j] = j;
        }
    }
    borad(borad* x, int i[9], int y, int z) : pre(x), depth(y), Fn(z) {
        for (int j = 0; j < 9; j++) {
            status[j] = i[j];
        }
    }
};

//優先隊列自定義排序規則,升序
struct cmp {
    bool operator() (const borad* a, const borad* b) {
        return a->Fn > b->Fn;
    }
};

bool swapnum(int a, int b, int* status);//交換元素
int getindex(int* status, int num);//獲得元素在棋盤上的一維坐標
void print(int* status);//打印棋盤
int hn(int* status, int* target);//當前狀態與目標狀態的曼哈頓距離
void printans(borad* cur);//打印解法,回溯
int status2int(int* status);//棋盤狀態轉為int格式
int reversesum(int* status);//計算逆序數之和
int* randstatus(int* target);//獲得隨機初始狀態

int main() {
    int go[4] = { -1,1,-3,3 };//四個移動方向
    int* start;//隨機初始狀態
    //int start[9] = { 2,8,3,1,6,4,7,0,5 };//初始狀態
    //2,3,7 ,4,5,8 ,0,6,1
    int target[9] = { 1,2,3,8,0,4,7,6,5 };//目標狀態
    stack<borad*> D_open;//DFS的open表,使用棧,深度大的在表頭
    queue<borad*> B_open;//BFS的open表,使用隊列,深度小的在表頭
    priority_queue<borad*, vector<borad*>, cmp> A_open;//A*的open表,使用優先隊列,啟發函數值小的元素在表頭
    unordered_set<int> close;//close表,存放已訪問過的狀態,元素為狀態的int格式
    //例:{ 1,2,3,8,0,4,7,6,5 }==》123804765(int)
    //{ 0,1,3,8,2,4,7,6,5 }==》13824765(int)

    //生成隨機初始狀態
    start = randstatus(target);

    //--------------------------------------------start-A*-------- Fn=Gn+Hn -----------------------------//
    //初始狀態壓入隊列
    A_open.push(new borad(NULL, start, 0, INT_MAX - 1));
    borad* temp = A_open.top();
    printf("初始狀態:");
    print(temp->status);
    printf("目標狀態:");
    print(target);
    printf("A* Fn=Gn+Hn:\n");
    while (!A_open.empty()) {
        //彈出一個狀態
        borad* cur = A_open.top();
        A_open.pop();
        //hn=Fn-depth為與目標狀態的曼哈頓距離,為0即到達目標狀態
        if (cur->Fn - cur->depth == 0) {
            printf("到達目標狀態\nclose表大小為%d\n目標狀態深度為%d\n\n", close.size(), cur->depth);
            //printans(cur);
            break;
        }
        //存放int格式的狀態
        int intstatus = status2int(cur->status);
        //出現重復狀態
        if (close.count(intstatus)) {
            continue;
        }
        //加入close表,表示已訪問過
        close.insert(intstatus);
        //獲得0的坐標
        int zeroindex = getindex(cur->status, 0);
        for (int i = 0; i < 4; i++) {
            //新建節點,復制當前棋盤狀態,深度+1
            borad* temp = new borad(cur, cur->status, cur->depth + 1, 0);
            //0向四個方向移動
            if (swapnum(zeroindex, zeroindex + go[i], temp->status)) {
                //移動成功
                //計算啟發函數值,並更新節點
                temp->Fn = temp->depth + hn(temp->status, target);
                //加入A_open表
                A_open.push(temp);
            }
            else {
                //移動失敗
                delete(temp);
            }
        }
    }
    //清空close表
    close.clear();
    //--------------------------------------------end-A*--------- Fn=Gn+Hn -------------------------//

    //清空A_open
    while (!A_open.empty()) {
        A_open.pop();
    }
    //--------------------------------------------start-A*改----- Fn=Hn ------------------------//
    //初始狀態壓入隊列
    A_open.push(new borad(NULL, start, 0, INT_MAX - 1));
    printf("A* Fn=hn:\n");
    while (!A_open.empty()) {
        //彈出一個狀態
        borad* cur = A_open.top();
        A_open.pop();
        if (cur->Fn == 0) {
            printf("到達目標狀態\nclose表大小為%d\n目標狀態深度為%d\n\n", close.size(), cur->depth);
            //printans(cur);
            break;
        }
        //存放int格式的狀態
        int intstatus = status2int(cur->status);
        //出現重復狀態
        if (close.count(intstatus)) {
            continue;
        }
        //加入close表,表示已訪問過
        close.insert(intstatus);
        //獲得0的坐標
        int zeroindex = getindex(cur->status, 0);
        for (int i = 0; i < 4; i++) {
            //新建節點,復制當前棋盤狀態,深度+1
            borad* temp = new borad(cur, cur->status, cur->depth + 1, 0);
            //0向四個方向移動
            if (swapnum(zeroindex, zeroindex + go[i], temp->status)) {
                //移動成功
                //計算啟發函數值,並更新節點
                temp->Fn = hn(temp->status, target);
                //加入A_open表
                A_open.push(temp);
            }
            else {
                //移動失敗
                delete(temp);
            }
        }
    }
    //清空close表
    close.clear();
    //--------------------------------------------end-A*改----------- Fn=Hn -------------------------//


    //--------------------------------------------start-BFS------------------------------------------//
    //初始狀態壓入隊列
    B_open.push(new borad(NULL, start, 0, INT_MAX - 1));
    printf("BFS:\n");
    while (!B_open.empty()) {
        //彈出一個狀態
        borad *cur = B_open.front();
        B_open.pop();
        //與目標狀態的距離,為0即到達目標狀態
        if (hn(cur->status, target) == 0) {
            printf("到達目標狀態\nclose表大小為%d\n目標狀態深度為%d\n\n", close.size(), cur->depth);
            //printans(cur);
            break;
        }
        //存放int格式的狀態
        int intstatus = status2int(cur->status);
        //出現重復狀態
        if (close.count(intstatus)) {
            continue;
        }
        //加入close表,表示已訪問過
        close.insert(intstatus);

        //獲得0的坐標
        int zeroindex = getindex(cur->status, 0);
        for (int i = 0; i < 4; i++) {
            //新建節點,復制當前棋盤狀態,深度+1
            borad *temp = new borad(cur, cur->status, cur->depth + 1, INT_MAX - 1);
            //0向四個方向移動
            if (swapnum(zeroindex, zeroindex + go[i], temp->status)) {
                //移動成功
                B_open.push(temp);
            }
            else {
                //移動失敗
                delete(temp);
            }
        }
    }
    //清空close表
    close.clear();
    //--------------------------------------------end-BFS------------------------------------------//

    //--------------------------------------------start-DFS------------------------------------------//
    //初始狀態壓入隊列
    D_open.push(new borad(NULL, start, 0, INT_MAX - 1));
    printf("DFS:\n");
    while (!D_open.empty()) {
        //彈出一個狀態
        borad *cur = D_open.top();
           D_open.pop();
        //if (cur->depth == 5) {
        //    break;
        //}
        //與目標狀態的距離,為0即到達目標狀態
        if (hn(cur->status, target) == 0) {
            printf("到達目標狀態\nclose表大小為%d\n目標狀態深度為%d\n\n", close.size(), cur->depth);
            //printans(cur);
            break;
        }
        //存放int格式的狀態
        int intstatus = status2int(cur->status);
        //出現重復狀態
        if (close.count(intstatus)) {
            continue;
        }
        //加入close表,表示已訪問過
        close.insert(intstatus);

        //獲得0的坐標
        int zeroindex = getindex(cur->status, 0);
        for (int i = 0; i < 4; i++) {
            //新建節點,復制當前棋盤狀態,深度+1
            borad *temp = new borad(cur, cur->status, cur->depth + 1, INT_MAX - 1);
            //0向四個方向移動
            if (swapnum(zeroindex, zeroindex + go[i], temp->status)) {
                //移動成功
                D_open.push(temp);
            }
            else {
                //移動失敗
                delete(temp);
            }
        }
    }
    //--------------------------------------------end-DFS------------------------------------------//

    delete(start);
    return 1;
}

//打印棋盤
void print(int* status) {
    for (int i = 0; i < 9; i++) {
        if (i % 3 == 0) {
            printf("\n");
        }
        printf("%d", status[i]);

    }
    printf("\n\n");
}

//獲得元素在棋盤上的一維坐標
int getindex(int* status, int num) {
    for (int i = 0; i < 9; i++) {
        if (status[i] == num) {
            return i;
        }
    }
    return -1;
}

//交換元素
bool swapnum(int a, int b, int* status) {
    if (b >= 0 && b <= 8 && (a / 3 == b / 3 || a % 3 == b % 3)) {
        swap(status[a], status[b]);
        return true;
    }
    else {
        return false;
    }
}

//當前狀態與目標狀態的曼哈頓距離
int hn(int* status, int* target) {
    //獲得當前狀態與目標狀態的二維x,y坐標
    int x, y, xt, yt, it, h = 0;
    for (int i = 0; i < 9; i++) {
        x = i % 3;
        y = i / 3;
        it = getindex(target, status[i]);
        xt = it % 3;
        yt = it / 3;
        h += abs(x - xt) + abs(y - yt);
    }
    return h;
}

//打印解法,回溯
void printans(borad* cur) {
    vector<string> ans;
    while (cur) {
        ans.push_back(to_string(cur->status[0]) + to_string(cur->status[1]) + to_string(cur->status[2]) + "\n"
            + to_string(cur->status[3]) + to_string(cur->status[4]) + to_string(cur->status[5]) + "\n"
            + to_string(cur->status[6]) + to_string(cur->status[7]) + to_string(cur->status[8]));
        cur = cur->pre;
    }
    for (int i = ans.size() - 1; i >= 0; i--) {
        printf("%s\n ↓\n", ans[i].c_str());
    }
    printf("END\n\n");
}

//棋盤狀態轉為int格式
int status2int(int* status) {
    int res = 0;
    for (int i = 0, j = 8; i < 9; i++, j--) {
        res += status[i] * pow(10, j);
    }
    return res;
}

//計算逆序數之和
int reversesum(int* status) {
    int sum = 0;
    for (int i = 0; i < 9; i++) {
        if (status[i] != 0) {
            for (int j = 0; j < i; j++) {
                if (status[j] > status[i]) {
                    sum++;
                }
            }
        }
    }
    return sum;
}

//獲得隨機初始狀態
int* randstatus(int* target) {
    int* start=new int[9]();
    unordered_map<int, int> nums;//記錄已添加的數
    srand((int)time(0));
    int element, sum1, sum2;
    sum2 = reversesum(target);
    //根據初始狀態與目標狀態的逆序數之和(sum1、sum2)是否相等,判斷初始狀態是否有解,不相等(即無解)則重新生成初始狀態
    do {
        for (int i = 0; i < 9; i++) {
            element = rand() % 9;
            while (nums[element]) {
                element = rand() % 9;
            }
            nums[element]++;
            start[i] = element;
        }
        //清空記錄
        nums.clear();
        //計算逆序數之和
        sum1 = reversesum(start);
    } while (sum1 % 2 != sum2 % 2);
    return start;
}

 


免責聲明!

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



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