Luogu P1379 [Problema de ocho digitales] [Reordenamiento de nueve casas] Optimización del algoritmo

Descripción del Título

Luogu P1379 Eight Digital Puzzle  (El código y la descripción de este blog están dirigidos al problema de Luogu)

Pregunta 1426: [蓝 桥 杯] [Preguntas de la prueba anterior] Reordenamiento de nueve palacios  (igual que la pregunta [básica] de Luo Gu, la entrada es diferente)

En un tablero de ajedrez de 3 × 3, hay ocho piezas de ajedrez, y cada pieza de ajedrez está marcada con un número del 1 al 8. Hay un espacio en el tablero de ajedrez y el espacio está representado por 0. Las piezas alrededor del espacio se pueden mover al espacio. El problema a resolver es: proporcione un diseño inicial (estado inicial) y un diseño de destino (para simplificar el problema, establezca el estado de destino en 123804765), encuentre un método de movimiento con el menor número de pasos para lograr la transición desde el estado inicial diseño al diseño de destino.

Formato de entrada

Ingrese el estado inicial, nueve números seguidos y un espacio con 0

Formato de salida

Solo hay una línea, y esta línea tiene solo un número, que indica el número mínimo de movimientos requeridos desde el estado inicial al estado objetivo (los datos del estado objetivo no se pueden alcanzar sin datos de prueba especiales)

Entrada y salida de muestra

ligeramente

Prefacio

Recientemente, estoy estudiando la estructura de datos y miro hacia atrás en el problema de ocho dígitos en la "Introducción al Clásico de Competencia de Algoritmos" de Liu Jiaru. Descubrí que Luogu, Lanqiao Cup y mi escuela oj (scnuoj) tienen esta pregunta. Así que este fin de semana estaba aburrido y escribí varias versiones del código. He enviado los 3 oj y todos han pasado. Mientras el cerebro todavía está caliente, tengo ganas de grabar el próximo blog.

En primer lugar, solo hablaré sobre las ideas de algoritmos a continuación, y no repetiré los detalles del código específico. Publiqué el código AC, ¡espero poder brindarle un poco de ayuda! Dado que envío en diferentes oj, el código se cambiará un poco, no sé si se confundirá. Si hay un error, corríjame.

 

versión 1

Pesaje de contenedores unidireccional bfs + stl set

(Luogu) Tiempo total: 7.53s

Pero no pude pasar la cuestión de la Blue Bridge Cup. El dato de esa pregunta era más fuerte que el de 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;
}

 Versión 2

Juicio de árbol de diccionario bfs + unidireccional 

Peso de juicio e inserción separados: (Luogu) Tiempo total: 3.38 s

Insertar mientras se juzga el peso: (Luogu) Tiempo total: 2,59 s 

En "Clásico de Introducción a la Competencia de Algoritmos", cada estado se almacena en forma de número entero y yo lo guardo en forma de cadena, lo que facilita la operación

Árbol de diccionario escrito a mano, no instale × a menos que sea necesario. Después de asegurarme de que mi algoritmo bfs principal era correcto, intenté escribir el árbol del diccionario a mano, aunque todavía era un poco imaginario. Pero, afortunadamente, lo ajusté dos veces. Antes, mi hermano nos mostró una plantilla para implementar el árbol de diccionario con matriz, pero debido a la estructura de datos de este semestre, el maestro introdujo la representación del árbol del hermano izquierdo y el hijo derecho, así que traté de implementar el árbol de diccionario con lista vinculada. 

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

Versión 3

Juicio de tabla hash unidireccional bfs + manuscrito

(Luogu) Tiempo total: 2,67 s

Insertar mientras juzgo el peso. Utilizo una matriz para simular la cola, pero la eficiencia no mejora significativamente. Se recomienda usar la cola proporcionada por stl.

¿Crees que estoy aburrido? Tabla hash manuscrita de nuevo. . . Este semestre aprendí Java y aprendí sobre la implementación subyacente de HashSet, así que imité el principio de implementación de Java e intenté escribir una tabla hash simple en C ++. De hecho, esta no es la primera vez que escribo una tabla hash, 23333. . .

Medido en 3 oj, la eficiencia es generalmente más alta que la del árbol del diccionario, pero no es estable. La eficiencia de la tabla hash depende principalmente de las ventajas de la función hash y del tamaño de la tabla hash. La función hash de la cadena que utilicé fue encontrada en Internet y probada por otros. Además, el tamaño de la tabla hash es 1000003, lo mejor es no tocarlo, lo intenté una vez que lo moví, la longitud lateral será más larga cuando se use. ¡Al menos, si usa esta función hash, se recomienda que el tamaño de la tabla hash sea 1000003! No conozco otras funciones hash.

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

Versión 4

Marca de mapa bidireccional bfs +

(Luogu) Tiempo total: 351 m s

Antes de participar en la competencia provincial Blue Bridge Cup en su primer año, mi hermano tenía una sesión de entrenamiento. En ese momento, mi hermano presentó bfs bidireccionales y también habló sobre tablas hash. . Pero en ese momento, fue ignorante. Bfs bidireccional significa "simultáneamente" bfs desde el punto de inicio y desde el punto final. Este simultáneo no es realmente simultáneo, pero los dos árboles bfs se expanden alternativamente hacia afuera, lo que equivale a expandir un nivel, y luego mi turno de expandir un nivel. Cuando dos árboles bfs se encuentran, el camino más corto es la suma de los pasos de los dos estados que se encuentran con +1. ¡También puedes abrir una cola!

¿Cómo juzgar cuando dos árboles bfs se encuentran? Esta marca es muy inteligente. . Tomé prestada esta marca de otras soluciones.

Se puede ver que, en general, ¡esta es la primera opción en el campo! El código es breve y eficaz.

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

Versión 5

Optimización bfs bidireccional + juicio de mapa

(Luogu) Tiempo total: 358ms

¡Cada vez que te marchas, el elemento oponente en la cola con menos elementos sale! Todos solo pueden abrir dos colas.

Para obtener más información, consulte el Blog del gran Dios: https://blog.csdn.net/ww32zz/article/details/50755225

Parece que el tiempo empleado no ha disminuido. . Pero lo envié en el banco de preguntas de Blue Bridge Cup y en la escuela oj, lo que me llevó un poco menos de tiempo. La razón por la que supongo a ciegas, la razón por la que el número de elementos en las dos colas no es igual es que uno de los bfs llega al límite al expandir el nodo de estado. Entonces, si esta optimización es obvia depende de la posición del punto de partida de las dos bfs. El estado final de la pregunta de la Copa Blue Bridge no está arreglado. Quizás esta optimización sea más obvia para la pregunta de la Copa Blue Bridge. . Son casi una docena de ms. . si lo recuerdo correctamente. . Por supuesto, lo anterior es puramente mi conjetura. . . También es posible que mi código sea incorrecto, por lo que no es obvio. .

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

Versión 6 Versión Ultimate

Optimización bfs bidireccional + juicio de árbol de diccionario

(Luogu) Tiempo total: 178 ms

Debido al marcado especial al usar bfs bidireccionales, ¡se debe cambiar el árbol del diccionario! No entraré en detalles sobre la implementación específica, consulte el código.

En la prueba de escuela oj, el tiempo más alto: 16 ms

En el banco de preguntas de la copa blue bridge, el tiempo más alto: 23 ms

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

 

Supongo que te gusta

Origin blog.csdn.net/qq_43290318/article/details/102764787
Recomendado
Clasificación