zoukankan      html  css  js  c++  java
  • 【算法学习笔记】18.暴力求解法06 隐式图搜索2 八数码问题 未启发

    <p>/*
    因为注释很详细,就直接上代码了,需要注意的是,用了白书的三种方法来进行判重,其中最快捷的方法还是stl的set,还有哈希技术涉及到了多个链表的处理,还有一种就是编码解码技术,这个需要找到一个非常好的函数才能达到一一对应。而哈希表不需要一一对应(因为有链表)。</p><div>*/</div>//
    //  main.cpp
    //  EightBits
    //
    //  Created by LinYuchen on 2/13/15.
    //  Copyright (c) 2015 LinYuchen. All rights reserved.
    //八数码问题 暂时不用启发式(A*)只是想锻炼结点查找表(判重)的部分
    //http://codevs.cn/problem/1225/
    
    #include <iostream>
    #include <set>
    #include <string>
    #define MAXSTATE 10000
    using namespace std;
    typedef int State[9];//把棋盘的九个位置存起来当做一个状态 State
    State goal;//用来存储最后想达到的状态
    State st[MAXSTATE];//用来存储从启示到达终点的所有状态过程 实际上是个队列 因为是 bfs
    int dist[MAXSTATE];//用来存储每一个走到st里的每一个state都已经走了多少步 (why?)
    int d = 0;
    //进行坐标变换 分别是上下左右
    int dx[]={-1,1,0,0},dy[]={0,0,-1,1};
    int bfs();//宽度优先搜索来找到最短路径(图的最短路径)
    void init_lookup_table();//初始化查找表
    bool try_to_insert(int rear);//尝试插入,进行判重如果已经走过就返回false不许插入队列
    int getId(int x,int y){return (x-1)*3+y-1;}//第x行 第y列 x=1,2,3 y=1,2,3
    
    
    
    
    
    
    int bfs(){
        init_lookup_table();
        int front=1,rear=2;//队列的头指针是1 尾指针是2(牢记:尾指针指向的是现有的最后一个元素的下一个位置)
        
        while (front<rear) {//front<rear 可以用来判断队列是否非空
            State& s = st[front];//此时的s表示的是此时的队首状态,也是即将进行发生变化的
            /*判断待处理的s是否正好就是goal 也就是达到了目的否?
            //如果已经找到了直接返回front 在结尾处我们可以通过front在st中找到goal*/
            if(memcmp(goal, s, sizeof(State))==0)
                return front;
            //如果可以进行到这里 说明我们要进行移动空白格(0)了
            //想移动空白格 首先要找到它
            int i=0;
            for (;i<9;i++)if(!s[i]) break;
            int zero_x=i/3+1 , zero_y=i%3+1;//转成坐标 和白书不同
            //开始进行四个方向的移动 需要进行出界判断
            for (int t=3; t>=0; t--){
                int newx = zero_x+dx[t],newy=zero_y+dy[t];
                if(newx<=3 and newx>=1 and newy>=1 and newy<=3){
                    int new_zero = getId(newx,newy);
                    //如果移动是合法的 就开始进行向队尾加入元素
                    State& r = st[rear];
                    //r是从s上移动而来的 所以只需要进行微调
                    memcpy(&r, &s, sizeof(State));
                    //swap(r[new_zero],r[i]);//i是s中0的位置 new_zero是0移动之后的位置
                    r[new_zero]=s[i];r[i]=s[new_zero];
                    dist[rear] = dist[front]+1;//front可以取到没有移动之前的距离
                    //尝试把移动之后的状态进行插入队列继续走,如果发现已经重复则不进行rear++
                    //rear++表示已经插入了队列,否则即使占领了st[rear]也会被下一次循环覆盖
                    if(try_to_insert(rear)) rear++;
                }
            }
            front++;//不管怎样都是处理完了一个~所以要出队
        }
        //如果没有找到任何的路径那么就返回0
        return 0;
    }
    
    //对应set的方式 init函数是
    
    //利用stl的set进行判重 集合的互异性 set的元素类型必须重载 < 运算 所以有限考虑int
    set<int> vis;
    void init_lookup_table(){vis.clear();}//对vis集合进行清空
    bool try_to_insert(int rear){
        //首先要把state转换成一个一一对应的int 才能插入集合来判断
        int id =0;
        for (int i=0; i<9; i++) id += st[rear][i] + id*10;
        //第一种检查方法是用find函数 和 end函数来进行比较 这里原理不是很清楚
        //if(vis.find(id)==vis.end()) return false;
        //第二种用count函数来进行判断 依然不懂得原理
        if(vis.count(id)!=0) return false;
        
        vis.insert(id);
        return true;
    }
     
    
    //对应编码解码的方法判重
    
    bool vis[362880];int fact[9];//vis的长度是由9!确定的,fact[i]存的是i的阶乘
    void init_lookup_table(){
        memset(vis, false, sizeof(bool));
        //初始化fact数组 保存每个数的阶乘 为了以后使用方便
        fact[0]=1;
        for(int i=1;i<=8;i++) fact[i]=i*fact[i-1];
    }
    bool try_to_insert(int rear){
        //进行编码 编码之后看vis是否
        int code = 0;
        for(int i =0;i<9;i++){
            int cnt =0;//cnt是为了记录st[rear][i]后面有几个比他小的数
            for (int j=i+1; j<9; j++) if(st[rear][j]<st[rear][i]) cnt++;
            code += fact[8-i]*cnt;//编码方式比较奇怪
        }
        //code是个 sigma(i=0-8) (8-i)!*第i个数后面比它小的数的个数。
        if (vis[code])     return false;
        vis[code]=true;    return true;
    }
    //最优方式 hash链表技术
    const int MAXHASHSTATE = 1000003;//这个常数是hash值的范围(最大值)
    int head[MAXHASHSTATE],nextState[MAXSTATE];
    //head的下表是hash值,我们可以通过下标(也就是hash值)去访问这个hash值所对应的state
    //也就是说head数组里的每个值其实是st数组的下标
    //next是链表 一条链是同一哈希值 一个state接一个state 所以next数组的下标和值都是st的下标
    
    int getHash(int t){//返回哈希值
        int hash_value = 0;
        for (int i=0; i<9; i++)
            hash_value += hash_value*10+st[t][i];
        return hash_value%MAXHASHSTATE;
    }
    void init_lookup_table(){ memset(head, 0, sizeof(int));
                                memset(nextState, 0, sizeof(int));}
    bool try_to_insert(int rear){
        int hv = getHash(rear);
        int u = head[hv];//找到此哈希值对应的state 没有就是0 如果有就是这条哈希链的首个
        while(u){//循环地去看这条链子
            if(memcmp(st[u], st[rear], sizeof(State))==0) return false;//重复
            //如果不重复那么就在这个链子上继续寻找
            u = nextState[u];//如果没有了u就会变成0
        }
        //循环完整条链子 没有发现重复 那么就要在这条链子的头部插入当前元素
        nextState[rear]=head[hv];//注意是插在头部,所以rear的下一个是当前的头部
        head[hv]=rear;
        return true;
    }
    
    
    
    int main(int argc, const char * argv[]) {
        //初始化目标状态
    //    for (int i=0; i<8; i++)
    //        goal[i]=i+1;
    //    goal[8]=0;
    //
        char goal_str[] = "123804765";
        for (int i=0; i<9; i++) {
            goal[i]=goal_str[i]-'1'+1;
        }
        //进行输入
        State& start=st[1];//引用地址来进行改名字 主要是为了简化代码
        dist[1]=0;        //用1是为了配合bfs 0表示没有
        char start_state[9];
        cin>>start_state;
        
        for (int i=0; i<9; i++)
            start[i]=start_state[i]-'1'+1;
        d = dist[bfs()];
        cout<<d<<endl;
        return 0;
    }
    



  • 相关阅读:
    10106 Product
    枚举值不占对象空间
    编译器会强制没有数据成员的对象长度非零
    对象切片与虚函数机制
    私有继承成员的公有化
    2013年开发者需要了解的开发趋势和技能
    redis 入门笔记
    Tomcat的异步通讯配置使用NIO
    如何成为“10倍效率的开发者”
    如何安装Node.js
  • 原文地址:https://www.cnblogs.com/yuchenlin/p/4379253.html
Copyright © 2011-2022 走看看