AIProject1

1、Zobrist Hash(By 15-Puzzle problem)

目的:使用Zobrist Hash判断N-Pullze棋局新生成的状态是否存在;

​ 如何从一个状态的Zobrist值得到其后继状态的Zobrist值;

Zobrist Hash思想:

Zobrist hash是一种专门针对棋类游戏而提出来的编码方式, Zobrist哈希通过一种特殊的置换表,也就是对棋盘上每一位置的各个可能状态赋予一个编码索引值,来实现在极低冲突率的前提下在一个整型数据上对棋盘进行编码。

以15-Puzzle为例说明:

题意:4*4棋盘,由15个方块和一个空位组成。15个棋子,每个棋子上标有1至15的某一数字;空格用0表示;空格周围的棋子可以移动到空格中。移动方块,让所有的方块按照数字的顺序排列。(以下讨论将0也算入其中)

一共4x4x16种组合;设定三维数组

1
2
3
Z[4][4][16] //每一个产生一个随机数,至少保证是32位的长度(即32bit),最好是64位。一般我们设定为32位,很多时候为了校验还会再设定一个64位的随机数。

//Z[0][0][0]——Z[0][0][15]的值表示0-15在(0,0)的每个状态的随机数;

对于一个特定的棋局,将每个单位的状态对应的随机数作异或(^)运算,所得值即为Hash值;

对于生成的初始状态,判断新生成的状态是否有与原来的生成的初始状态的Hash值是否一致。若一致,则新生成的初始状态已经存在。

对于判断是否与某历史状态是否存在同理。

如何从一个状态的Zobrist值得到其后继状态的Zobrist值?

当前状态会得到一个Zobrist值a;

比如(0,0)位置上是8,(0,1)位置上是0;

操作:将8移到(0,1);

​ 0到(0,0);

1
2
3
//原来键值里异或过Z[0][0][8]  和 Z[0][1][0];
//再次异或这个值,棋子即从键值中删除;再异或新的状态;所以后继状态的Zobrist为
a1=a^Z[0][0][8]^Z[0][1][0]^Z[0][0][0]^Z[0][1][8];

参考:https://www.cnblogs.com/IThaitian/p/3616507.html

2、N-Puzzle问题的可解性判断

末状态逆序对:0

初始状态:8-Puzzle ;

​ 3x3; 3为奇数;

上下每交换一次逆序数改变偶次数,左右交换不变

初始状态的逆序数与末状态逆序数奇偶性相同则可达;

​ 15-Puzzle;

​ 4x4; 4为偶数;

上下每交换一次逆序数改变奇次数,左右交换不变;

初始状态的逆序数+初始0到最低行的行距(0最后在最低行)与末状态奇偶性相同即可互相到达;

延申:MxN Puzzle问题同理

逆序数求法:归并排序&树状数组

归并排序原理:分治法

图来源:https://www.cnblogs.com/chengxiao/p/6194356.html

合并:

Code:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
#include<bits/stdc++.h>
#define maxn 100
int a[maxn],b[maxn];
using namespace std;
void merge_sort(int l,int r){
if(r-l>0){
int mid=(l+r)/2;
int i=l;
int p=l;
int q=mid+1;
merge_sort(l,mid);
merge_sort(mid+1,r);
while(p<=mid||q<=r){ //左右两部分只要有一部分不为空
if(q>r||(p<=mid&&a[p]<=a[q])){
b[i++]=a[p++];//q>r 即将左边剩的复制到数组,否则比较
}
else{
b[i++]=a[q++]; //将右半数组复制到辅助数组
}
}
for(i=l;i<=r;i++){
a[i]=b[i]; //拷贝
}
}
}
int main(){
int n;
scanf("%d",&n);
for(int i=1;i<=n;i++){
scanf("%d",&a[i]);
}
merge_sort(1,n);
for(int i=1;i<n;i++){
printf("%d ",a[i]);
}
cout<<a[n]<<endl;

return 0;
}

求逆序对原理:

左右两部分合并时,当右指针数字小于左指针的时候,他会比从左指针开始到中间的元素个数个逆序对。也就是mid-l+1;

所以添加一行代码即可求出逆序对:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
#include<bits/stdc++.h>
#define maxn 100
int a[maxn],b[maxn];
int cnt;
using namespace std;
void merge_sort(int l,int r){
if(r-l>0){
int mid=(l+r)/2;
int i=l;
int p=l;
int q=mid+1;
merge_sort(l,mid);
merge_sort(mid+1,r);
while(p<=mid||q<=r){ //左右两部分只要有一部分不为空
if(q>r||(p<=mid&&a[p]<=a[q])){
b[i++]=a[p++];//q>r 即将左边剩的复制到数组,否则比较
}
else{
b[i++]=a[q++]; //将右半数组复制到辅助数组
cnt+=mid-p+1;
}
}
for(i=l;i<=r;i++){
a[i]=b[i]; //拷贝
}
}
}
int main(){
int n;
cnt=0;
scanf("%d",&n);
for(int i=1;i<=n;i++){
scanf("%d",&a[i]);
}
merge_sort(1,n);
for(int i=1;i<n;i++){
printf("%d ",a[i]);
}
cout<<a[n]<<endl;
cout<<cnt<<endl;

return 0;
}

树状数组求逆序对:

树状数组原理:

参考https://blog.csdn.net/Small_Orange_glory/article/details/81290634

Code:

1
2
3
4
5
//单点更新
void update(int x,int y,int n){
for(int i=x;i<=n;i+=lowbit(i))    //x为更新的位置,y为更新后的数,n为数组最大值
c[i] += y;
}
1
2
3
4
5
6
7
//区间查询
int getsum(int x){
int ans = 0;
for(int i=x;i;i-=lowbit(i))
ans += c[i];
return ans;
}

求逆序对原理:

https://blog.csdn.net/ssimple_y/article/details/53744096

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
#include<bits/stdc++.h>
#define maxn 1007
using namespace std;
int aa[maxn];//离散化后的数组
int c[maxn]; //树状数组
int n;

struct Node
{
int v;
int order;
}a[maxn];

bool cmp(Node a, Node b)
{
return a.v < b.v;
}

int lowbit(int k)
{
return k&(-k); //基本的lowbit函数
}

void update(int t, int value)
{ //即一开始都为0,一个个往上加(+1),
int i;
for (i = t; i <= n; i += lowbit(i))
c[i] += value;
}

int getsum(int t)
{ //即就是求和函数,求前面和多少就是小于它的个数
int i, sum = 0;
for (i = t; i >= 1; i -= lowbit(i))
sum += c[i];
return sum;
}

int main()
{
int i;
while (scanf("%d", &n), n)
{
for (i = 1; i <= n; i++) //离散化
{
scanf("%d", &a[i].v);
a[i].order = i;
}
sort(a + 1, a + n + 1,cmp);//从1到n排序,cmp容易忘
memset(c, 0, sizeof(c));
for (i = 1; i <= n; i++)
aa[a[i].order] = i;
int ans = 0;
for (i = 1; i <= n; i++)
{
update(aa[i], 1);
ans += i - getsum(aa[i]); //减去小于的数即为大于的数即为逆序数
}
printf("%d\n", ans);
}
return 0;
}