Luogu P1379 [Eight Digital Problem] [Nine House Rearrangement] Algorithm optimization

Title description

Luogu P1379 Eight Digital Puzzle  (The code and description of this blog are all aimed at Luogu's problem)

Question 1426: [蓝桥杯] [Previous Test Questions] Nine Palaces Rearrangement  (same as Luo Gu’s [Basic] question, input is different)

On a 3×3 chessboard, there are eight chess pieces, and each chess piece is marked with a number from 1 to 8. There is a space in the chessboard, and the space is represented by 0. The pieces around the space can be moved into the space. The problem to be solved is: give an initial layout (initial state) and target layout (in order to make the problem simple, set the target state to 123804765), find a moving method with the fewest steps to achieve the transition from the initial layout to the target layout .

Input format

Enter the initial state, nine numbers in a row, and a space with 0

Output format

There is only one line, and this line has only one number, which indicates the minimum number of moves required from the initial state to the target state (the target state data cannot be reached without special test data)

Sample input and output

slightly

Preface

Recently, I am studying data structure, and look back at the eight-digit problem in Liu Jiaru's "Introduction to Algorithm Competition Classic". I found that Luogu, Lanqiao Cup, and my school oj (scnuoj) have this question. So this weekend, I was bored , and I typed several versions of the code. I have submitted all 3 oj, and all have passed. While the brain is still hot, I feel like to record the next blog.

First explain, I will only talk about the algorithm ideas below, I will not repeat the specific code details, I posted the AC code, I hope I can give you a little help! Since I submit on different oj, the code will be changed a little, I don't know if it will be confused. If there is an error, please correct me.

 

version 1

One-way bfs + stl set container weighing

(Luogu) Total time: 7.53s

But I couldn't pass the question of the Blue Bridge Cup. The data point of that question was stronger than that of Luogu. . .

#include<iostream>
#include<cstdio>
#include<queue>
#include<cstring>
#include<set>
using namespace std;

const int d[][2]={
   
   {-1,0}, {0,1}, {1,0}, {0,-1}}; //上右下左 

//bfs的状态结点 
typedef struct State
{
	char state[15];
	int step; 
	
	State() { step=0; }
	State(char* s, int cnt)
	{
		strcpy(state, s);
		step=cnt;
	}
}State;
State origin; //初始状态
char dest[]="123804765"; //最终状态 

set<string> st;

void bfs()
{
	queue<State> q;
	q.push(origin);
	
	while(!q.empty())
	{
		State head=q.front();
		q.pop();
		
		//判断是否已经达到最终状态
		if(!strcmp(head.state, dest))
		{
			printf("%d\n", head.step);
			return;
		} 
		
		//找到空格的位置 
		int pos;
		for(int i=0; head.state[i]!='\0'; i++)
			if(head.state[i]=='0')
			{
				pos=i;
				break;
			}
		
		int x=pos/3;
		int y=pos%3;
		for(int i=0;i<4;i++)
		{
			int x1=x+d[i][0];
			int y1=y+d[i][1];
			
			if(x1>=0 && x1<3 && y1>=0 && y1<3)
			{
				int pos1=x1*3+y1;
			
				char s[15]; //扩展的新节点 
				strcpy(s, head.state);
				swap(s[pos], s[pos1]);
				
				if(!st.count(string(s))) //判断扩展的新状态是否已经访问过 
				{
					st.insert(string(s));
					q.push(State(s, head.step+1));
				}
			}
		}				
	}
	printf("-1\n");
}

int main()
{
	scanf("%s", origin.state);
	
	bfs();
	
	return 0;
}

 Version 2

One-way bfs + dictionary tree judgment 

Separate judgment weight and insertion: (Luogu) Total time: 3.38s

Inserting while judging the weight: (Luogu) Total time: 2.59s 

In "Classic of Introduction to Algorithm Competition", each state is stored in integer form, and I store it in string form, which makes the operation easier

Handwritten dictionary tree, do not install × unless necessary. After making sure that my main bfs algorithm was correct, I tried to write the dictionary tree by hand, although I was still a little imaginary. But fortunately, I adjusted it twice. Before, my brother showed us a template to implement dictionary tree with array, but due to the data structure of this semester, the teacher introduced the left brother and right child representation of the tree, so I tried to implement dictionary tree with linked list. 

#include<iostream>
#include<cstdio>
#include<queue>
#include<cstring>
#include<set>
using namespace std;

const int d[][2]={
   
   {-1,0}, {0,1}, {1,0}, {0,-1}}; //上右下左 

//bfs的状态结点 
typedef struct State
{
	char state[15];
	int step; 
	
	State() { step=0; }
	State(char* s, int cnt)
	{
		strcpy(state, s);
		step=cnt;
	}
}State;
State origin; //初始状态
char dest[]="123804765"; //最终状态 

typedef struct Node
{
	char c;
	int cnt; //以该字符结尾的[前缀]出现的次数
	Node* child; //左孩子有兄弟表示法 
	Node* brother;
	Node()
	{
		cnt=0;
		child=NULL;
		brother=NULL;
	}
}Node;

class Trie
{
	public:
		Trie(); 
		int insert(char* s);
		int find(char* s);
	private:
		Node* root;
};

Trie::Trie()
{
	root=new Node;
}

int Trie::insert(char* s)
{
	Node* u=root;
	Node* v=NULL;
	int success=0;
	for(int i=0; s[i]!='\0'; i++)
	{
		int flag=0;
		for(v=u->child; v!=NULL; v=v->brother)
			if(v->c==s[i])	
			{
				v->cnt+=1;
				flag=1;
				break;
			}
		
		if(!flag)
		{
			success=1;
			
			v=new Node;
			v->c=s[i];
			v->child=NULL;
			v->brother=u->child;
			v->cnt=1;
			
			u->child=v;
		}
		u=v;	
	}
	return success;
}

int Trie::find(char* s)
{
	Node* u=root;
	Node* v=NULL;
	for(int i=0; s[i]!='\0'; i++)
	{
		int flag=0;
		for(v=u->child; v!=NULL; v=v->brother)
			if(v->c==s[i])	
			{
				flag=1;
				break;
			}
				
		if(!flag)
			return 0;
		u=v;	
	}
	return u->cnt;
}

Trie trie;

void bfs()
{
	queue<State> q;
	q.push(origin);
	
	while(!q.empty())
	{
		State head=q.front();
		q.pop();
		
		//判断是否已经达到最终状态
		if(!strcmp(head.state, dest))
		{
			printf("%d\n", head.step);
			return;
		} 
		
		//找到空格的位置 
		int pos;
		for(int i=0; head.state[i]!='\0'; i++)
			if(head.state[i]=='0')
			{
				pos=i;
				break;
			}
		
		int x=pos/3;
		int y=pos%3;
		for(int i=0;i<4;i++)
		{
			int x1=x+d[i][0];
			int y1=y+d[i][1];
			
			if(x1>=0 && x1<3 && y1>=0 && y1<3)
			{
				int pos1=x1*3+y1;
			
				char s[15]; //扩展的新节点 
				strcpy(s, head.state);
				swap(s[pos], s[pos1]);
				
				if(trie.insert(s)) //在判重的同时实现插入
					q.push(State(s, head.step+1));
				
				/*
				if(!trie.find(s)) //先判重
				{
					trie.insert(s); //再插入
					q.push(State(s, head.step+1));
				}
				*/
			}
		}
					
	}
	printf("-1\n");
	
}


int main()
{
	scanf("%s", origin.state);
	
	bfs();
	
	return 0;
}

Version 3

One-way bfs + handwritten hash table judgment

(Luogu) Total time: 2.67s

Insert while judging the weight. I use an array to simulate the queue, but the efficiency is not significantly improved. It is recommended to use the queue provided by stl.

Um, do you think I'm bored? Handwritten hash table again. . . This semester I learned Java and learned about the underlying implementation of HashSet, so I imitated the implementation principle of Java and tried to write a simple hash table in C++. In fact, this is not the first time I write a hash table, 23333. . .

Measured on 3 oj, the efficiency is generally higher than the dictionary tree, but it is not stable. The efficiency of the hash table mainly depends on the advantages of the hash function and the size of the hash table. The hash function of the string I used was found on the Internet and tested by others. In addition, the size of the hash table is 1000003, it is best not to touch it, I tried once I moved it, the side length will be longer when used. At least, if you use this hash function, the size of the hash table is recommended to use 1000003! I don't know about other hash functions.

#include<iostream>
#include<cstdio>
#include<queue>
#include<cstring>
#include<set>
using namespace std;

const int d[][2]={
   
   {-1,0}, {0,1}, {1,0}, {0,-1}}; //上右下左 

//bfs的状态结点 
typedef struct State
{
	char state[15];
	int step; 
	
	State() { step=0; }
	State(char* s, int cnt)
	{
		strcpy(state, s);
		step=cnt;
	}
}State;
State origin; //初始状态
char dest[15]="123804765"; //最终状态 

//set<string> st;

//哈希表的结点 
const int maxn=1000003; 
typedef long long ll;
typedef struct Node
{
	char str[15];
	Node* next;
	Node() { }
	Node(char* s, Node* nt)
	{
		strcpy(str, s);
		next=nt;
	}
}Node;
Node* hashTable[maxn]; //哈希表

//求哈希值并映射到哈希表的坐标 
int BKDRHash(char *str)
{
    ll seed = 131; 
    ll hash = 0;
 
    while (*str)
        hash = hash * seed + (*str++);
    
    return (int)((hash & 0x7FFFFFFF)%maxn);
}

//0:表示该字符串已存在,插入失败  1:字符串不存在,插入成功 
int tryInsert(char* s)
{
	int hash=BKDRHash(s);
	
	Node* p=hashTable[hash];
	if(p==NULL)
		hashTable[hash]=new Node(s, NULL); //注意不能写成 p=  
	else
	{
		while(p->next!=NULL)
		{
			if(!strcmp(p->str, s)) //已存在 
				return 0;
			p=p->next;
		}
		p->next=new Node(s, NULL);
	}
	
	return 1;
}

State* q[maxn]; //模拟队列
int front=-1; 
int rear=-1; 

void bfs()
{
	//queue<State> q;
	//q.push(origin);
	q[++rear]=&origin;
	
	while(front<rear)
	{
		//State head=q.front();
		//q.pop();
		State* head=q[++front];
		
		//判断是否已经达到最终状态
		if(!strcmp(head->state, dest))
		{
			printf("%d\n", head->step);
			return;
		} 
		
		//找到空格的位置 
		int pos;
		for(int i=0; head->state[i]!='\0'; i++)
			if(head->state[i]=='0')
			{
				pos=i;
				break;
			}
		
		int x=pos/3;
		int y=pos%3;
		for(int i=0;i<4;i++)
		{
			int x1=x+d[i][0];
			int y1=y+d[i][1];
			
			if(x1>=0 && x1<3 && y1>=0 && y1<3)
			{
				int pos1=x1*3+y1;
			
				char s[15]; //扩展的新节点 
				strcpy(s, head->state);
				swap(s[pos], s[pos1]);
				
				if(tryInsert(s)) //不存在,插入成功 
				{
					//q.push(State(s, head.step+1));
					q[++rear]=new State(s, head->step+1);
				}
				
				
				/*
				if(!st.count(string(s))) //判断扩展的新状态是否已经访问过 
				{
					st.insert(string(s));
					q.push(State(s, head.step+1));
				}
				*/
			}
		}
					
	}
	printf("-1\n");
	
}

int main()
{
	scanf("%s", origin.state);
	
	bfs();
	
	return 0;
}

Version 4

Two-way bfs + map mark

(Luogu) Total time: 351m s

Before participating in the Blue Bridge Cup provincial competition in his freshman year, my brother had a training session. At that time, my brother introduced two-way bfs and also talked about hash tables. . But at the time, it was ignorant. Two-way bfs means to "simultaneously" bfs from the starting point and from the end point. This simultaneous is not really simultaneous, but the two bfs trees alternately expand outwards, which is equivalent to you expand one level, and then my turn to expand one level. When two bfs trees meet, the shortest path is the sum of the steps of the two states that meet +1. You can also open a queue!

How to judge when two bfs trees meet? This mark is very clever. . I borrowed this mark from other solutions.

It can be seen that, comprehensively, this is the first choice on the field! The code is short and efficient.

#include<iostream>
#include<cstdio>
#include<queue>
#include<cstring>
#include<map>
using namespace std;
const int d[][2]={
   
   {-1,0}, {0,1}, {1,0}, {0,-1}}; //上右下左 
string origin; //初始状态
string dest="123804765"; //最终状态 

map<string,int> vis;
map<string,int> step;

void bfs()
{
	//特判
	if(origin==dest)
	{
		printf("0");
		return;
	}
	 
	queue<string> q;
	
	q.push(origin);
	vis[origin]=1;
	step[origin]=0;
	
	q.push(dest);
	vis[dest]=2;
	step[dest]=0;
	
	while(!q.empty())
	{
		string head=q.front();
		q.pop();
		
		int pos;
		for(int i=0; i<head.length(); i++)
			if(head[i]=='0')
			{
				pos=i;
				break;
			}
		
		int x=pos/3;
		int y=pos%3;
		for(int i=0;i<4;i++)
		{
			int x1=x+d[i][0];
			int y1=y+d[i][1];
			
			if(x1>=0 && x1<3 && y1>=0 && y1<3)
			{
				int pos1=x1*3+y1;
				string s=head; //copy一份 
				swap(s[pos], s[pos1]);
				
				if(!vis.count(s))
				{
					q.push(s);
					vis[s]=vis[head];
					step[s]=step[head]+1;
				}
				else if(vis[s]+vis[head]==3)
				{
					printf("%d", step[s]+step[head]+1);
					return;
				}
			}
		}
	}
    printf("-1"); //这个没用,因为题目说一定可达。。但蓝桥杯那题有不可达的情况
}

int main()
{
	
	cin>>origin;
	
	bfs();
	
	return 0;
}

Version 5

Two-way bfs optimization + map judgment

(Luogu) Total time: 358ms

Every time you depart, the opponent element in the queue with fewer elements departs! All can only open two queues.

For details, please refer to the Great God Blog: https://blog.csdn.net/ww32zz/article/details/50755225

It seems that the time used has not decreased. . But I submitted it on the Blue Bridge Cup question bank and school oj, which took a little less time. The reason I guess is that the reason that the number of elements in the two queues is not equal is that one of the bfs hits the boundary when the state node is expanded. So whether this optimization is obvious depends on the position of the starting point of the two bfs. The end state of the Blue Bridge Cup question is not fixed, maybe this optimization will be more obvious for the Blue Bridge Cup question. . It's almost a dozen ms. . if I recall it correctly. . Of course, the above is purely my blind guess. . . It is also possible that my code is wrong, so it is not obvious. .

#include<iostream>
#include<cstdio>
#include<queue>
#include<cstring>
#include<map>
using namespace std;
const int d[][2]={
   
   {-1,0}, {0,1}, {1,0}, {0,-1}}; //上右下左 
string origin; //初始状态
string dest="123804765"; //最终状态 

map<string,int> vis;
map<string,int> step;

void bfs()
{
	//特判
	if(origin==dest)
	{
		printf("0");
		return;
	}
	 
	queue<string> q1;
	queue<string> q2;
	
	q1.push(origin);
	vis[origin]=1;
	step[origin]=0;
	
	q2.push(dest);
	vis[dest]=2;
	step[dest]=0;
	
	while(!q1.empty() || !q2.empty())
	{
		string head;
		int flag;
		if(q1.size()<q2.size())
		{
			head=q1.front();
			q1.pop();
			flag=1;
		}
		else
		{
			head=q2.front();
			q2.pop();
			flag=2;
		}
		
		int pos;
		for(int i=0; i<head.length(); i++)
			if(head[i]=='0')
			{
				pos=i;
				break;
			}
		
		int x=pos/3;
		int y=pos%3;
		for(int i=0;i<4;i++)
		{
			int x1=x+d[i][0];
			int y1=y+d[i][1];
			
			if(x1>=0 && x1<3 && y1>=0 && y1<3)
			{
				int pos1=x1*3+y1;
				string s=head; //copy一份 
				swap(s[pos], s[pos1]);
				
				if(!vis.count(s))
				{
					if(flag==1)
						q1.push(s);
					else if(flag==2)
						q2.push(s);
					
					vis[s]=vis[head];
					step[s]=step[head]+1;
				}
				else if(vis[s]+vis[head]==3)
				{
					printf("%d", step[s]+step[head]+1);
					return;
				}
			}
		}
	}
}

int main()
{
	
	cin>>origin;
	
	bfs();
	
	return 0;
}

Version 6 Ultimate version

Two-way bfs optimization + dictionary tree judgment

(Luogu) Total time: 178ms

Due to special marking when using bidirectional bfs, the dictionary tree must be changed! I won't go into details about the specific implementation, see the code.

In the school oj test, the highest time: 16ms

In the blue bridge cup question bank, the highest time: 23ms

#include<iostream>
#include<cstdio>
#include<queue>
#include<cstring>
#include<set>
using namespace std;

const int d[][2]={
   
   {-1,0}, {0,1}, {1,0}, {0,-1}}; //上右下左 

string origin; //初始状态
string dest="123804765"; //最终状态 

typedef struct Node
{
	char c;
	int cnt; //以该字符结尾的[前缀]出现的次数。这个没用。。但顺手写上去 
	int flag; //这个flag标记只有在字典树的叶子结点才被标记,非叶子节点这个flag相当于个冗余字段
	int step;
	Node* child; //左孩子有兄弟表示法 
	Node* brother;
	Node()
	{
		cnt=0;
		flag=0;
		step=0;
		child=NULL;
		brother=NULL;
	}
}Node;

class Trie
{
	public:
		Trie(); 
		int insert(string s, int flag, int step);
		Node* find(string s);
	private:
		Node* root;
};

Trie::Trie()
{
	root=new Node;
}

int Trie::insert(string s, int flag, int step)
{
	Node* u=root;
	Node* v=NULL;
	int success=0;
	for(int i=0; i<s.length(); i++)
	{
		int flag=0;
		for(v=u->child; v!=NULL; v=v->brother)
			if(v->c==s[i])	
			{
				v->cnt+=1;
				flag=1;
				break;
			}
		
		if(!flag)
		{
			success=1;
			
			v=new Node;
			v->c=s[i];
			v->child=NULL;
			v->brother=u->child;
			v->cnt=1;
			
			u->child=v;
		}
		u=v;	
	}
	u->flag=flag;
	u->step=step;
	return success;
}

Node* Trie::find(string s)
{
	Node* u=root;
	Node* v=NULL;
	for(int i=0; i<s.length(); i++)
	{
		int flag=0;
		for(v=u->child; v!=NULL; v=v->brother)
			if(v->c==s[i])	
			{
				flag=1;
				break;
			}
				
		if(!flag)
			return NULL;
		u=v;	
	}
	return u;
}

Trie trie;

void bfs()
{
	//特判
	if(origin==dest)
	{
		printf("0");
		return;
	}
	 
	queue<string> q1;
	queue<string> q2;
	
	q1.push(origin);
	trie.insert(origin, 1, 0);
	
	q2.push(dest);
	trie.insert(dest, 2, 0);
	
	while(!q1.empty() || !q2.empty())
	{
		string head;
		int flag;
		if(q1.size()<=q2.size())
		{
			head=q1.front();
			q1.pop();
			flag=1;
		}
		else
		{
			head=q2.front();
			q2.pop();
			flag=2;
		}
		
		int pos;
		for(int i=0; i<head.length(); i++)
			if(head[i]=='0')
			{
				pos=i;
				break;
			}
		
		int x=pos/3;
		int y=pos%3;
		for(int i=0;i<4;i++)
		{
			int x1=x+d[i][0];
			int y1=y+d[i][1];
			
			if(x1>=0 && x1<3 && y1>=0 && y1<3)
			{
				int pos1=x1*3+y1;
				string s=head; //copy一份 
				swap(s[pos], s[pos1]);
				
				Node* h=trie.find(head);
				Node* t=trie.find(s);
				if(t==NULL)
				{
					if(flag==1)
						q1.push(s);
					else if(flag==2)
						q2.push(s);
					
					trie.insert(s, h->flag, h->step+1);
				}
				else if(t->flag + h->flag==3)
				{
					printf("%d", t->step + h->step + 1);
					return;
				}
			}
		}
	}
}


int main()
{
	cin>>origin;
	
	bfs();
	
	return 0;
}

 

Guess you like

Origin blog.csdn.net/qq_43290318/article/details/102764787