【算法学习笔记】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;
}



posted @ 2015-02-14 18:43  雨尘之林  阅读(274)  评论(0编辑  收藏  举报