编程之美1.14——连连看工程(含全部代码,伪哈希+BFS)

25 篇文章 1 订阅
13 篇文章 0 订阅

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我,希望和打架一起分享代码和优化思路的乐趣

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值