<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;
}