八数码问题

一. 广度优先算法

使用队列记录当前层次的状态
同时使用哈希表防止重复遍历
单向广度优先是逐渐增大范围同时判断目标是否在范围内

int dir[4][2] = {{1,0},{0,-1},{-1,0},{0,1}};
int main()
{
    string board;
    cin>>board;//初始化棋盘
    string target = "123804765"; //初始化目标
    int depth = 0;//搜索深度
    queue<string> q;//队列记录当前状态
    unordered_set<string> s;//不重复遍历
    q.push(board);
    while(!q.empty()){
        int n = q.size();
        for(int i=0;i<n;i++){//遍历当前层次
            string &str = q.front(); q.pop(); //下面都是针对str状态来写
            if(str==target){//找到目标
                cout<<depth;
                return 0;
            }
            s.insert(str);//将已遍历状态记录下来,防止重复遍历
            int pos = str.find('0');
            int x = pos/3; int y = pos%3;
            for(int j=0;j<4;j++){//遍历四个方向,进行交换
                int nx = x + dir[j][0]; int ny = y + dir[j][1];
                int nextpos = nx*3+ny;
                if(nx<0||nx>2||ny<0||ny>2) continue;//下个位置不存在,或者是原来过来的位置,跳过
                swap(str[pos],str[nextpos]); //移动
                if(s.find(str)==s.end()) q.push(str);//加入一个新的状态
                swap(str[pos],str[nextpos]);//撤回
            }
        }
        depth++;
    }
    return 0;
}

二. 双向广度优先

双向广度优先交替增大范围,并且判断是否交叉


int dir[4][2] = {{1,0},{0,-1},{-1,0},{0,1}};

int main()
{
    string board;
    cin>>board;//初始化棋盘
    string target = "123804765"; //初始化目标
    int depth = 0;//搜索深度
    queue<string> q1;//队列记录当前状态
    unordered_set<string> s1;//不重复遍历
    queue<string> q2;//队列记录当前状态
    unordered_set<string> s2;//不重复遍历

    q1.push(board); 
    q2.push(target);
    while(!q1.empty()||!q2.empty()){
        int n = q1.size();
        for(int i=0;i<n;i++){//遍历当前层次
            string &str = q1.front(); q1.pop(); //下面都是针对str状态来写
            if(s2.count(str)){//找到目标
                cout<<depth-1;
                return 0;
            }
            s1.insert(str);//将已遍历状态记录下来,防止重复遍历
            int pos = str.find('0');
            int x = pos/3; int y = pos%3;
            for(int j=0;j<4;j++){//遍历四个方向,进行交换
                int nx = x + dir[j][0]; int ny = y + dir[j][1];
                int nextpos = nx*3+ny;
                if(nx<0||nx>2||ny<0||ny>2) continue;//下个位置不存在,或者是原来过来的位置,跳过
                swap(str[pos],str[nextpos]); //移动
                if(s1.find(str)==s1.end()) q1.push(str);//加入一个新的状态
                swap(str[pos],str[nextpos]);//撤回
            }
        }
        depth++;
        swap(q1,q2);
        swap(s1,s2);
    }
    return 0;
}

三. A*算法

广度优先+估值函数

结合估值函数进行的广度优先搜索,这里使用优先队列,根据估计的搜索深度进行贪心搜索

string target;
int dir[4][2] = {{1,0},{0,-1},{-1,0},{0,1}};

int h1(string &s){//估值函数1,根据曼哈顿距离
    int res = 0;
    for(int i=0;i<s.size();i++){
        if(s[i]=='0') continue;//不用计算0的匹配度
        int x1 = i/3; int y1 = i%3;
        int idx = target.find(s[i]);
        int x2 = idx/3; int y2 = idx%3;
        res += abs(x2-x1) + abs(y2-y1);
    }
    return res;
}

int h2(string &s){//估值函数2,根据不在位个数
    int res = 0;
    for(int i=0;i<s.size();i++){
        if(s[i]=='0') continue;
        if(s[i]!=target[i]) res++;
    }
    return res;
}


struct Strforsort {
    string str; //字符串
    int depth; //当前状态搜索深度 
    int hval;//启发值(最小待搜索层次)
    int w;//经过该状态转移,预计的最小搜索次数
    Strforsort(){}
    Strforsort(const string& s, int d,int h) : str(s), depth(d), hval(h), w(d+h){}
    bool operator<(const Strforsort& other) const {//预计搜索次数越小,权重越大
        return w > other.w; 
    }
};

int main()
{
    string board;
    cin>>board;//初始化棋盘
    target = "123804765"; //初始化目标
    Strforsort start(board,0,h1(board));//起点搜索深度为0,
    priority_queue<Strforsort> pq;
    unordered_set<string> s;
    pq.push(start);
    while(!pq.empty()){//贪心搜索
        Strforsort cur = pq.top(); pq.pop();
        if(cur.hval==0){//找到目标值
            cout<<cur.depth;
            return 0;
        }
        string &str = cur.str;
        s.insert(str);//避免重复遍历同一状态
        int pos = str.find('0');
        int x = pos/3; int y = pos%3;
        for(int i=0;i<4;i++){//遍历四个方向,进行交换
            int nx = x + dir[i][0]; int ny = y + dir[i][1];
            int nextpos = nx*3+ny;
            if(nx<0||nx>2||ny<0||ny>2) continue;//下个位置不存在,跳过
            swap(str[pos],str[nextpos]);
            if(s.find(str)==s.end()){ //该状态没有遍历过
                Strforsort next(str,cur.depth+1,h1(str));
                pq.push(next);
            }
            swap(str[pos],str[nextpos]);
        }
    }
    return 0;
}

四. IDA*算法

深度优先+估值函数

核心思想在于像广度优先搜索一样逐步增大搜索层次
这里使用估值函数对深度优先搜索进行剪枝
估值函数为1~8每个数字的曼哈顿距离,这个同样也是理论上的最少步骤
同时提供另一个对不在位进行估值的函数
保证当前搜索的是最优解

string board;
string target;
int depth;//搜索深度

int h1(string &s){//估值函数1,根据曼哈顿距离
    int res = 0;
    for(int i=0;i<s.size();i++){
        if(s[i]=='0') continue;//不用计算0的匹配度
        int x1 = i/3; int y1 = i%3;
        int idx = target.find(s[i]);
        int x2 = idx/3; int y2 = idx%3;
        res += abs(x2-x1) + abs(y2-y1);
    }
    return res;
}

int h2(string &s){//估值函数2,根据不在位个数
    int res = 0;
    for(int i=0;i<s.size();i++){
        if(s[i]=='0') continue;
        if(s[i]!=target[i]) res++;
    }
    return res;
}

int dir[4][2] = {{1,0},{0,-1},{-1,0},{0,1}};

bool dfs(int d,int prepos){
    int cnt = h1(board);
    if(cnt==0) return 1;//边界条件,与目标一致
    if(d+cnt>depth) return 0;//直接剪枝,达到预期深度,不再递归

    int pos = board.find('0');
    int x = pos/3; int y = pos%3;
    for(int i=0;i<4;i++){//遍历四个方向,进行交换
        int nx = x + dir[i][0]; int ny = y + dir[i][1];
        int nextpos = nx*3+ny;
        if(nx<0||nx>2||ny<0||ny>2||nextpos==prepos) continue;//下个位置不存在,或者是原来过来的位置,跳过
        swap(board[pos],board[nextpos]);
        if(dfs(d+1,pos)) return true;
        swap(board[pos],board[nextpos]);
    }
    return false;
}


int main()
{
    cin>>board;//初始化棋盘
    target = "123804765"; //初始化目标
    depth = h1(board);//一开始评估一下局面,初始化一个深度
    while(!dfs(0,-1)) depth++;//初始深度为0,没有上一个步
    cout<<depth;
    return 0;
}
posted @ 2023-08-25 02:16  失控D大白兔  阅读(39)  评论(0编辑  收藏  举报