1.连连看问题描述:
1.用计算机模型模拟这个问题
2.怎样判断两个图形可以相消(核心)
3.怎样求出相同图形之间的最短路径(转弯次数最少,路径经过的格子最少,后置是在前者的制约下的)动态规划的思想在队列中的实现
4.怎么确定死锁状态(本人的一点小创新,为了优化查找的时间复杂度,开辟了一个伪哈希表)
2.算法思想:
1.如何判断相消:
在本文章中,BFS的思路无疑是最好象,也是最出色,效率最搞得一种方法,网上的一些人说可以之间分类判断贵啊点的数目来判断,那是他们没有仔细思考多种特殊情况就没有是想,光凭想象说的,这里的BFS的思路是最合适的
首先我们以我们第一次选中的节点作为扩展的父节点,然后十字开辟空余的格子,然后分别以这些空余的格子作为父节点再次开辟(当然开辟的次数不能超过三次),还有,这里面我们为了下面好计算最短路径,我们将开始的转弯次数赋值为-1
本读者在这里其实对于搜索最短路径其实有一个自己的想法,我们从完成游戏的角度来看的话,完全没必要非求出最短路径,求出可行路径就可以,但是实际上的连连看游戏电脑是可以输出最短路径的,但是如果采用BFS来做的话,我们只要搜索到就可以从队列中出来,不用继续扩展队列了,除非如果用心的方法的话,那么的确有必要(可能更快)求出满足条件的最短路径
2.如何确定死锁:
在本问题中,我们通过遍历图中所有的剩余的格子,对美个剩余的格子,在图中查找所有的与之图案相同的格子都进行BFS判断一次,成功就返回非死锁
知道所有的空余的格子都被扫描完还是没有发现可行的解,那么就是死锁状态,提前返回失败,游戏结束
本人为了加快查找的效率,利用空间换时间,采用装填因子为200%的拉链法,虽然这已经超过了哈希表可以容忍的范围,但是我们不得不说,这样子的查找效率相对于对图的遍历来说,已经将O(n*n)降到O(C)常熟时间,已经是非常大的优化了,当惹按读者有什么更好的方法,欢迎在评论区联系我,骂我哦
3.扩展问题:
本人目前还在研究如何实现这两个扩展问题,但是思路和想法还是有的
1.优缺点:
我们如果每次都要求出图中任意两点之间的最短路径来求解的话,对于一个高手来说,这要非常的耗时,但是对于一个敌手来说,这要非常的高效
2.不会。。。。。
3.代码实现:
#include"iostream"
#include"cstdio"
#include"cstring"
#include"cstdlib"
#define inf 99999999
#define N 10
//测试用例,40块,每组俩块
//1 3 7 4 -1 -1 5 6 3 2
//5 7 8 -1 -1 -1 -1 6 4 7
//2 1 -1 -1 -1 -1 -1 -1 0 9
//6 -1 -1 -1 -1 -1 -1 -1 -1 8
//-1 -1 -1 -1 -1 -1 -1 -1 -1 -1
//-1 -1 -1 -1 -1 -1 -1 -1 -1 -1
//5 -1 -1 -1 -1 -1 -1 -1 -1 1
//6 9 -1 -1 -1 -1 -1 -1 2 4
//4 0 0 -1 -1 -1 -1 2 3 8
//1 3 0 7 -1 -1 8 9 9 5
using namespace std;
typedef struct nod //伪哈希表的判断成员,每个关键字基本被分配2.5个(涂图有25对的时候),会超过拉链法的120%的要求,但是这已经不是哈希表了,我的优化策略
{
int x;
int y;
struct nod* next;
}node;
class hash
{
public:
hash()
{
for(int i=0;i<N;i++) book[i].next=NULL;
}
~hash() //有指针的存在,防止内存泄漏
{
for(int i=0;i<N;i++)
{
node* p=book[i].next;
while(p!=NULL)
{
book[i].next=book[i].next->next;
free(p);
p=book[i].next;
}
}
}
node* returnxy(int); //按照关键字返回下一个同状态的块的坐标信息
void push(node,int); //int代表关键字,node代表位置,根据这两点在设置图的同时将信息加入哈希表
void pop(node,int); //块已经被删除了,该点弹出哈希表
node*& returnnode(int key)
{
node* p=book[key].next;
return p;
}
private:
node book[N];
};
class point
{
public:
point()
{
thing=-1;
check=death=0;
mincross=mindest=inf; //为了保持最优化转台并且好判断,该两项初始化为无穷大
}
void givething(int); //初始化图的时候和消除的时候的重行分配内容的操作
void choosepoint(); //该点被加入队列,check准备变换,之后的map勒种还要加入全图清楚check标记的函数操作,形参表包含父亲点的坐标,目的是帮助在此函数内修改mincross和mindest成员
void claimdeath(); //该点已经被判断过是思索的状态,这样做是为了方便之后的其他块判断死锁状态时忽略掉改掉,从而加快速度
int returnthing()
{
return thing;
}
void initpoint()
{
check=death=0;
mincross=mindest=inf;
}
void setmincross(int a)
{
mincross=a;
}
void setmindest(int a)
{
mindest=a;
}
int returnmincross()
{
return mincross;
}
int returnmindest()
{
return mindest;
}
int returndeath()
{
return death;
}
int returncheck()
{
return check;
}
private:
bool check; //当前是否被选中,加入队列中
int thing; //该块中的信息,若为-1,代表此点没有块,为空,否则应该在0-9之间的状态
bool death; //死锁标记
int mincross; //该点被扩展时的转弯次数,该项要被不断的更新为最小的状态
int mindest; //该点被扩展的时候,在mincross的情况下的距离起点的路程
};
class linkup
{
public:
linkup()
{
memset(queue,0,sizeof(queue));
head=tail=0;
num=0;
startx=starty=endx=endy=0;
cross=dest=inf;
}
void initmap(); //每次消除完或者判断连接失败之后之后,我们都要将图中每个点的queue,head,tail,check,death,mincross,mindest,startx等成员全部清空
bool bfs(int,int,int,int); //后两个形参可以变更为判断死锁的时候的查找点,在游戏进行中的时候,代表的是起点和终点
void creatmap(); //遗留问题,怎么实现随机生成图???
bool spread(int,int,int,int); //扩展的父亲点,bfs的附属函数
void print(); //打印全图,以便玩家浏览
void listenmotion(); //监听玩家的动作,输入坐标
bool giveanswer(); //对于玩家的动作,根据bfs进行相应的处理,并且输出语句告知玩家
bool judge(); //判断是否死锁,如果死锁我们告知玩家游戏结束
void stream(); //游戏的流程
void clearjudge()
{
memset(queue,0,sizeof(queue));
head=tail=1;
}
private:
point map[N][N]; //10*10的图,50个空位,25对连接
hash hashmap; //哈希表
int num; //目前剩余的块的数目num/2代表目前剩下的对数
int startx;
int starty;
int endx;
int endy;
node queue[N*N];
int head;
int tail;
int cross; //每次连接完之后的最短路径的数据
int dest;
};
node* hash::returnxy(int key)
{
node* p=book[key].next;
if(p==NULL)
{
cout<<"图存在问题,你可以扇游戏制作者一个巴掌了"<<endl;
return NULL;
}
else
{
return p;
}
}
void hash::push(node p,int key)
{
node* k=new node;
k->x=p.x; k->y=p.y;
k->next=book[key].next;
book[key].next=k;
}
void hash::pop(node p,int key)
{
node* help=&book[key];
node* k=book[key].next;
while(k!=NULL)
{
if(k->x==p.x&&k->y==p.y)
{
help->next=k->next;
free(k);
break;
}
else help=k;
k=k->next;
}
}
void point::givething(int key)
{
thing=key;
}
void point::choosepoint()
{
check=1;
}
void point::claimdeath()
{
death=1;
}
void linkup::creatmap()
{
cout<<"开始输入你的地图(-1代表空地,0-9代表块)"<<endl;
for(int i=0;i<N;i++)
{
for(int j=0;j<N;j++)
{
node p;
p.x=i;p.y=j;
int key;
cin>>key;
map[i][j].givething(key);
hashmap.push(p,key);
if(key!=-1) num++;
}
}
}
void linkup::listenmotion()
{
cout<<"玩家开始输入必要的信息"<<endl;
cout<<"起点坐标";cin>>startx>>starty;
cout<<"终点坐标";cin>>endx>>endy;
cout<<"你的请求已经受理,即将返回你的选择结果"<<endl;
}
bool linkup::bfs(int sx,int sy,int ex,int ey)
{
tail++;
map[sx][sy].setmincross(-1); //定义刚开始的拐弯数是-1,方便后续计算
map[sx][sy].setmindest(0);
if(spread(sx,sy,ex,ey)==1) return true;
else
{
int i=1;
while(head!=tail&&i<3) //控制转弯次数
{
if(spread(queue[head].x,queue[head].y,ex,ey)) return true; //BFS找到了,返回true,在giveanswer中进行删除操作
i++;
}
return false;
}
}
bool linkup::giveanswer()
{
if(map[startx][starty].returnthing()!=map[endx][endy].returnthing()) return false;
else
{
if(bfs(startx,starty,endx,endy)) //可以连接
{
node help;
help.x=startx;help.y=starty;
hashmap.pop(help,map[startx][starty].returnthing());
help.x=endx;help.y=endy;
hashmap.pop(help,map[endx][endy].returnthing());
num-=2; //块消除,数目减少
map[startx][starty].givething(-1); //消除两个点
map[endx][endy].givething(-1);
return true;
}
else return false; //不能连接
}
}
void linkup::initmap()
{
memset(queue,0,sizeof(queue));
head=tail=1;
for(int i=0;i<N;i++)
{
for(int j=0;j<N;j++) map[i][j].initpoint();
}
startx=starty=endx=endy=0;
cross=dest=inf;
}
bool linkup::spread(int x,int y,int ex,int ey)
{
queue[head].x=x;
queue[head].y=y;
int nextstage=tail-1;
map[x][y].choosepoint();
while(head<=nextstage)
{
x=queue[head].x; //及时更新新的扩展父节点
y=queue[head].y;
for(int i=y+1;i<N;i++) //向右
{
if(map[x][i].returncheck()==1&&map[x][i].returnthing()==-1)
{
if(map[x][i].returnmincross()>=map[x][y].returnmincross()+1)
{
map[x][i].setmincross(map[x][y].returnmincross()+1);
map[x][i].setmindest(map[x][y].returnmindest()+i-y);
}
continue;
}
if(map[x][i].returnthing()==-1)
<h2> {</h2> if(map[x][i].returnmincross()>=map[x][y].returnmincross()+1)
{
map[x][i].setmincross(map[x][y].returnmincross()+1);
map[x][i].setmindest(map[x][y].returnmindest()+i-y);
}
map[x][i].choosepoint();
queue[tail].x=x;
queue[tail].y=i;
tail++;
}
else
{
if(x==ex&&i==ey)
{
cross=map[x][y].returnmincross(); //搜集到最短路径的数据
dest=map[x][y].returnmindest()+i-y;
return true;
}
else break;
}
}
for(int i=y-1;i>=0;i--) //向左
{
if(map[x][i].returncheck()==1&&map[x][i].returnthing()==-1)
{
if(map[x][i].returnmincross()>=map[x][y].returnmincross()+1)
{
map[x][i].setmincross(map[x][y].returnmincross()+1);
map[x][i].setmindest(map[x][y].returnmindest()+y-i);
}
continue;
}
if(map[x][i].returnthing()==-1)
{
if(map[x][i].returnmincross()>=map[x][y].returnmincross()+1)
{
map[x][i].setmincross(map[x][y].returnmincross()+1);
map[x][i].setmindest(map[x][y].returnmindest()+y-i);
}
map[x][i].choosepoint();
queue[tail].x=x;
queue[tail].y=i;
tail++;
}
else
{
if(x==ex&&i==ey)
{
cross=map[x][y].returnmincross();
dest=map[x][y].returnmindest()+y-i;
return true;
}
else break;
}
}
for(int i=x-1;i>=0;i--) //向上
{
if(map[i][y].returncheck()==1&&map[i][y].returnthing()==-1)
{
if(map[i][y].returnmincross()>=map[x][y].returnmincross()+1)
{
map[i][y].setmincross(map[x][y].returnmincross()+1);
map[i][y].setmindest(map[x][y].returnmindest()+x-i);
}
continue;
}
if(map[i][y].returnthing()==-1)
{
if(map[i][y].returnmincross()>=map[x][y].returnmincross()+1)
{
map[i][y].setmincross(map[x][y].returnmincross()+1);
map[i][y].setmindest(map[x][y].returnmindest()+x-i);
}
map[i][y].choosepoint();
queue[tail].x=i;
queue[tail].y=y;
tail++;
}
else
{
if(i==ex&&y==ey)
{
cross=map[x][y].returnmincross();
dest=map[x][y].returnmindest()+x-i;
return true;
}
else break;
}
}
for(int i=x+1;i<N;i++) //向下
{
if(map[i][y].returncheck()==1&&map[i][y].returnthing()==-1)
{
if(map[i][y].returnmincross()>=map[x][y].returnmincross()+1)
{
map[i][y].setmincross(map[x][y].returnmincross()+1);
map[i][y].setmindest(map[x][y].returnmindest()+i-x);
}
continue;
}
if(map[i][y].returnthing()==-1)
{
if(map[i][y].returnmincross()>=map[x][y].returnmincross()+1)
{
map[i][y].setmincross(map[x][y].returnmincross()+1);
map[i][y].setmindest(map[x][y].returnmindest()+i-x);
}
map[i][y].choosepoint();
queue[tail].x=i;
queue[tail].y=y;
tail++;
}
else
{
if(i==ex&&y==ey)
{
cross=map[x][y].returnmincross();
dest=map[x][y].returnmindest()+i-x;
return true;
}
else break;
}
}
head++;
}
return false;
}
bool linkup::judge()
{
for(int i=0;i<N;i++)
{
for(int j=0;j<N;j++)
{
if(map[i][j].returnthing()!=-1&&map[i][j].returndeath()!=1)
{
int key=map[i][j].returnthing();
node* p=hashmap.returnnode(key);
while(p!=NULL)
{
if(p->x==i&&p->y==j) p=p->next;
else if(bfs(i,j,p->x,p->y)) return true;
else p=p->next;
}
clearjudge();
}
map[i][j].claimdeath(); //判断都不成功,加上死亡标记
}
}
return false;
}
void linkup::print()
{
printf(" ");
for(int i=0;i<N;i++) cout<<i<<' '; //打印表头
cout<<endl;
for(int i=0;i<N;i++)
{
cout<<i<<' '; //打印表列
for(int j=0;j<N;j++)
{
if(map[i][j].returnthing()!=-1) cout<<map[i][j].returnthing()<<' ';
else printf(" ");
}
cout<<endl;
}
}
void linkup::stream()
{
creatmap();
print();
while(num!=0)
{
if(judge()==0)
{
clearjudge();
cout<<"很不幸,地图死锁,游戏失败!"<<endl;
return ;
}
else
{
clearjudge();
initmap();
listenmotion();
if(startx<0||startx>=N||starty<0||starty>=N||endx<0||endx>=N||endy<0||endy>=N)
{
cout<<"输入的坐标点存在错误,请重新输入"<<endl;
continue;
}
if(giveanswer()==1)
{
cout<<"本次的连接,拐弯次数:"<<cross+1<<"路径长度:"<<dest<<endl;
system("PAUSE");
system("cls");
print();
cout<<"连接有效,开始删除"<<endl;
}
else
{
system("cls");
print();
cout<<"不能连接或者图样不匹配,连接失败!"<<endl;
}
}
}
cout<<"全部消除,恭喜你游戏成功!"<<endl;
return ;
}
int main()
{
linkup my;
my.stream();
return 0;
}
4.结语:
Dijstra,DFS,BFS,A*都可以实现,各有优点,BFS是可以搜索出最优的拐弯数下的最优解路径
BFS可以较快低损耗的求出可行解,不一定最优
A*和Dijstra相对于上面的搜索算法来说,在这种地图下不高效在有些情况的路径下很低效
感谢《编程之美》这本书,我对数的扩展问题真的非常的有兴趣,对于我的代码实现的思路和优化,还有扩展问题的解决,如果读者有更好的思路,欢迎在评论区call我,希望和打架一起分享代码和优化思路的乐趣