八数码问题(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;
}

 

posted @ 2020-11-01 16:42  悲惨痛苦太刀  阅读(978)  评论(0编辑  收藏  举报