ACM:搜索算法专题(3)——启发式搜索

题目来源:

HihoCoder1312

题目描述:

给出一个九宫格的拼图游戏的棋局,求完成拼图最少需要一定的步数。

解答:

·规则: 

首先简要说明游戏规则。

游戏的棋局如下:

九宫格中放置8个标有不同数字的棋子,其中一个位置为空,通过移动棋子,使得数字有序排列,则游戏完成,如下:

在移动的过程中,只有和空白位置相邻的棋子才可以移动,并仅可以移动到空白位置。下面的例子中可以通过6次移动完成游戏:

以上为游戏规则。

·编码:

本题的思路还是比较简单的。通过dijkstra算法,计算每个棋盘状态到最终状态的最短路径即可。这里的“最短路径”定义为最少的移动的次数。那么首先的问题就是以某种方式记录棋局的状态。方法如下:

假设空白位置记为数字9,那么不同的棋局的数目为:9!= 362880种。如果以字典序将所有的排列方式排序的话,那么每个排列结果就会有唯一的一个编号。因此,编号为1的排列为:123456789,编号为362880的排列为:987654321。所以,目前的问题是给出一个排列,找出它在字典序中的编号。这个问题可以采用一种叫做“康托展开”的方法来求解,方法如下:

对于某一个排列:s[1,2,...9], 定义序列a[1,2,...,9],其中a[i]表示序列s[i+1,
i+2, ... 9]中比a[i]小的数值的个数, 例如,排列 9 1 2 3 6 4 7 8 5 对应的a[i] 序列为:8
0 0 2 0 1 0 0。

在给出a[i]序列后,排列s对应的序号为:

X = a[1] * (9 - 1)! + a[2] * (9 - 2)! + ... + a[9] * (9 - 9)!

游戏的目标结果为:1 2 3 4 5 6 7 8 9,对应的序号为1。

·解码:

在知道编码方法后,我们还需要知道如何在给定编号X的情况下,找到对应的排列结果。这个过程是康托展开的逆过程,可以称为“逆康托展开”。方法如下:

① 用X除以(9-1)!得到商q和余数r。

② 从1,2,...9中的尚未使用过的数字中选择第q+1小的数字,该数字就是s[1]。

③ 令: X=r。

④ 反复执行步骤①-③,在每次迭代中依次令X除以(9-2)!, (9-3)!, ..., (9-9)!,就可以依次得到s[2], s[3]...s[9],进而得到对应的排列结果。

下面给出一个例子,求解1-9的排列中,排在第5000的排列结果:

① 令:X=5000, X ÷ (9-1)! = 0...........5000, q=0 r=5000, 当前未使用的数字有{1,2,3,
4,5,6,7,8,9},选择第1小的数字,即1, 因此s[1] = 1。

② 令:X=5000, X
÷ (9-2)! = 0...........5000, q=8
r=5000, 当前未使用的数字有{2,3,4,5,6,7,8,9},选择第1小的数字,即2,
因此s[2] = 2。

③ 令:X=5000, X
÷ (9-3)! = 6...........680, q=6
r=680, 当前未使用的数字有{3,4,5,6,7,8,9},选择第7小的数字,即9,
因此s[3] = 9。

④ 令:X=680, X
÷ (9-4)! = 5...........80, q=5
r=80, 当前未使用的数字有{3,4,5,6,7,8},选择第6小的数字,即8,
因此s[4] = 8。

⑤ 令:X=80, X
÷ (9-5)! = 3...........8, q=3
r=8, 当前未使用的数字有{3,4,5,6,7},选择第4小的数字,即6,
因此s[5] = 6。

⑥ 令:X=8, X
÷ (9-6)! = 1...........2, q=1
r=2, 当前未使用的数字有{3,4,5,7},选择第2小的数字,即4,
因此s[6] = 4。

⑦ 令:X=2, X
÷ (9-7)! = 1...........0, q=1
r=0, 当前未使用的数字有{3,5,7},选择第2小的数字,即5,
因此s[7] = 5。

⑧ 令:X=0, X
÷ (9-8)! = 0...........0,
q=0 r=0, 当前未使用的数字有{3,7},选择第1小的数字,即3,
因此s[8] = 3。

⑨ 令:X=0, X
÷ (9-9)! = 0...........0, q=0
r=0, 当前未使用的数字有{7},选择第1小的数字,即7,
因此s[9] = 7。

因此,排在第5000位的排列结果为:1 2 9 8 6 4 5 3 7。

·搜索:

在知道如何将棋局的状态表示为数字后,就可以进行搜索了。我们可以将整个游戏抽象为一个有向图,图中的点为各个不同的棋局状态。而如果两个状态可以通过移动棋子一次达到互相转换,那么,两个点之间就包含一条长度为1的无向边:

构建无向图的工作完成后,可以采用两种方式进行搜索:

方法一:从终点到起点

以最终局面——状态1为起点,运用dijkstra算法进行搜索,就可以知道每一个状态到最终状态的最短距离,然后在接下来给定初始状态后,就可以直接找到对应的最少移动次数。此方法只要搜索一次,就可以对所有的输入的初始状态直接给出答案。

方法二:从起点到终点

也可以在给定起始状态的情况下,进行搜索到达终点的最短距离。此时,对于每一组给出的起始位置,都需要进行一次搜索。为了提高效率,可以采用一种“启发式”的搜索方式对于dijkstra算法进行改进。方法如下:

启发式搜索的思想是:设计一个评估函数F,使用评估函数对于每一个点评估,然后在搜索的过程中优先考虑评估结果较好的点。这样就可以有更高的概率优先找到通向终点的最短路径。

评估函数F由两部分组成:F = G + H,其中G表示从起点到当前点的最短路径长度,初始状态为0,在搜索过程中逐步对G进行赋值;H则表示从当前点到终点的最短距离路径,函数H需要在搜索前进行预估。

搜索过程中维护两个集合:openSet和closeSet,初始状态时,两个集合均为空。搜索步骤如下:

① 将起点的G值设置为0,并设置为当前点。

② 将当前点键入到closeSet中。

③ 搜索所有和当前点邻接的点,如果该点已经在closeSet中,则不做操作,如果该点在openSet中,则更新其G值,比较当前点的G值加1和该临界点已有的G值,取较小者;如果该临界点不在openSet中,则设置该点的G值为当前点G值加1,然后将其加入到openSet中。

④ 选择openSet中F值最小的点作为当前点。

⑤ 反复执行步骤②-④,直到openSet为空或重点已经加入到了closeSet,此时就找到了起点到终点的最短路径。

注意为了保证算法的正确性,评估函数H对于每一个点的函数值一定不能大于其到终点的实际距离。否则在从openSet选择当前搜索点时,就可能会出错,导致无法得到最优解。对于本题,我们则可以定义函数H为:当前状态中的1-8的8个数字的当前位置到目标状态中的8个对应位置的曼哈顿距离之和。由于每次移动会改变一个棋子的位置,导致该棋子的位置到目标位置的曼哈顿距离增大或者减小,如果每次移动都是减小移动棋子到目标位置的曼哈顿距离,那么总的移动次数就是8个棋子到目标位置的曼哈顿距离之和。如果在移动的过程中有些操作会使得移动的棋子的曼哈顿距离变大,那么总的移动次数还要更多。因此这样定义函数H是正确的。

下面的代码采用的是方法一。

输入输出格式:

    输入:第1行:1个正整数t,表示数据组数;接下来有t组数据,每组数据有3行,每行3个整数,包含0-8,每个数字只出现一次,其中0表示空位。

输出:第1..t行:每行1个整数,表示该组数据解的步数。若无解输出"No
Solution!"

数据范围:

1≤t≤8

程序代码:

/****************************************************/
/* File        : Hiho_Week_100                      */
/* Author      : Zhang Yufei                        */
/* Date        : 2016-05-30                         */
/* Description : HihoCoder ACM program. (submit:g++)*/
/****************************************************/

#include<stdio.h>
#include<stdlib.h>

#define NUM 362880

typedef struct node1 vertex;
typedef struct node2 edge;

/*
 * Define structure to store the node of graph.
 * Parameters:
 *		@distance: The distance between the current node and the start node.
 *		@edges: The link list of all the nodes which are adjacent with this node.
 *		@tag: Mark if the node has been visited.
 */
typedef struct node1 {
	int status;
	int index;
	int distance;
	edge *edges;
	int tag;
} vertex;

/*
 * Define structure to store the edges of the graph.
 * Parameters:
 *		@index: The end point of this edge.
 * 		@next: The next pointer.
 */
typedef struct node2 {
	vertex* v;
	struct node2 *next;
} edge;

// Record the graph.
vertex graph[NUM];

// Record the multiply value.
int multiply[9] = {1, 1, 2, 6, 24, 120, 720, 5040, 40320};

// Record map.
int map[9];

// Record the Set.
vertex *heap[NUM + 1];

// The size of heap.
int heap_cnt;

/*
 * This function compare the distance of the 2 elements in heap.
 * Parameters:
 *		@a & @b: The 2 elements to compare.
 * Returns:
 *		If @a is greater than @b, returns 1; or if @a is equal to @b, returns 0;
 *		or returns -1;
 */
int cmp(int a, int b) {
	if(heap[b]->distance == -1) {
		return -1;
	} else if(heap[a]->distance == -1) {
		return 1;
	}else if(heap[a]->distance > heap[b]->distance) {
		return 1;
	}else if(heap[a]->distance == heap[b]->distance) {
		return 0;
	} else {
		return -1;
	}
}

/*
 * This function adjusts the array into a heap.
 * Parameters:
 *		@root: The root node of heap.
 * Returns:
 *		None.
 */
void min_heapify(int root) {
	int min_index = root;
	if(root * 2 <= heap_cnt) {
		if(cmp(min_index, 2 * root) > 0) {
			min_index = 2 * root;
		}
	}

	if(root * 2 + 1 <= heap_cnt) {
		if(cmp(min_index, 2 * root + 1) > 0) {
			min_index = 2 * root + 1;
		}
	}

	if(min_index != root) {
		vertex *swap = heap[root];
		heap[root] = heap[min_index];
		heap[min_index] = swap;
		heap[root]->index = root;
		heap[min_index]->index = min_index;
		min_heapify(min_index);
	}
}

/*
 * This node adjust the heap after update the distance of node.
 * Parameters:
 *		@index: The element to update.
 * Returns:
 *		None.
 */
void update(int index) {
	while(index > 1) {
		if(cmp(index, index / 2) < 0) {
			vertex *swap = heap[index];
			heap[index] = heap[index / 2];
			heap[index / 2] = swap;
			heap[index]->index = index;
			heap[index / 2]->index = index / 2;
			index /= 2;
		} else {
			break;
		}
	}
}

/*
 * This function removes the top element from heap.
 * Parameters:
 *		None.
 * Returns:
 *		The element removed.
 */
vertex* remove(void) {
	vertex *r = heap[1];
	heap[1] = heap[heap_cnt];
	heap[1]->index = 1;
	heap_cnt--;

	min_heapify(1);

	return r;
} 

/*
 * This function transports the map into status number.
 * Parameters£º
 *		None.
 * Returns:
 *		The status number.
 */
int get_status_from_map(void) {
	int status = 0;
	int tag[10] = {0};

	for(int i = 0; i < 9; i++) {
		int cnt = 0;
		for(int j = 1; j < map[i]; j++) {
			if(tag[j] == 0) {
				cnt++;
			}
		}
		status += cnt * multiply[8 - i];
		tag[map[i]] = 1;
	}

	return status;
} 

/*
 * This function transports the status into map.
 * Parameters:
 *		@status: The current status.
 * Returns:
 *		None.
 */
void get_map_from_status(int status) {
	int tag[10] = {0};
	for(int i = 0; i < 9; i++) {
		int s = status / multiply[8 - i];
		int cnt = 0;
		for(int j = 1; j < 10; j++) {
			if(tag[j] == 0) {
				cnt++;
				if(cnt > s) {
					tag[j] = 1;
					map[i] = j;
					break;
				}
			}
		}
		status %= multiply[8 - i];
	}
}

/*
 * This function computes the edges of each node in graph.
 * Parameters:
 *		@current: The current node.
 * Returns:
 *		None.
 */
void add_edge(int current) {
	get_map_from_status(current);
	/*if(current == 15331) {
		for(int i = 0; i < 9; i++) {
			printf("%d ", map[i]);
		}
		printf("\n");
	}*/
	for(int j = 0; j < 9; j++) {
		if(map[j] == 9) {
			if(j + 3 < 9) {
				int swap = map[j];
				map[j] = map[j + 3];
				map[j + 3] = swap;
				int status = get_status_from_map();

				edge *e = (edge*) malloc(sizeof(edge));
				e->v = &graph[status];
				e->next = graph[current].edges;
				graph[current].edges = e;

				swap = map[j];
				map[j] = map[j + 3];
				map[j + 3] = swap;
			}
			if(j - 3 >= 0) {
				int swap = map[j];
				map[j] = map[j - 3];
				map[j - 3] = swap;
				int status = get_status_from_map();

				edge *e = (edge*) malloc(sizeof(edge));
				e->v = &graph[status];
				e->next = graph[current].edges;
				graph[current].edges = e;

				swap = map[j];
				map[j] = map[j - 3];
				map[j - 3] = swap;
			}
			if(j % 3 != 2 && j + 1 < 9) {
				int swap = map[j];
				map[j] = map[j + 1];
				map[j + 1] = swap;
				int status = get_status_from_map();

				edge *e = (edge*) malloc(sizeof(edge));
				e->v = &graph[status];
				e->next = graph[current].edges;
				graph[current].edges = e;

				swap = map[j];
				map[j] = map[j + 1];
				map[j + 1] = swap;
			}
			if(j % 3 != 0 && j - 1 >= 0) {
				int swap = map[j];
				map[j] = map[j - 1];
				map[j - 1] = swap;
				int status = get_status_from_map();

				edge *e = (edge*) malloc(sizeof(edge));
				e->v = &graph[status];
				e->next = graph[current].edges;
				graph[current].edges = e;

				swap = map[j];
				map[j] = map[j - 1];
				map[j - 1] = swap;
			}
			break;
		}
	}
}

/*
 * This function computes the shortest distance from the start to every node
 * using dijkstra algorithm.
 * Parameters:
 *		@start: The start node.
 * Returns:
 *		None.
 */
void dijkstra(int start) {
	for(int i = 0; i < NUM; i++) {
		heap[i + 1] = &graph[i];
		graph[i].distance = -1;
		graph[i].tag = 0;
		graph[i].index = i + 1;
	}

	heap_cnt = NUM;
	graph[start].distance = 0;
	update(graph[start].index);

	while(heap_cnt > 0 && heap[1]->distance	!= -1) {
		vertex *v = remove();
		edge *e = v->edges;
		while(e != NULL) {
			if(e->v->distance == -1 ||
				e->v->distance > v->distance + 1) {
				e->v->distance = v->distance + 1;
				update(e->v->index);
			}
			e = e->next;
		}
	}
}

/*
 * This function initiates the graph.
 * Parameters:
 *		None.
 * Returns:
 *		None.
 */
void init(void) {
	for(int i = 0; i < NUM; i++) {
		graph[i].distance = -1;
		graph[i].edges = NULL;
		graph[i].status = i;
		add_edge(i);
	}
}

/*
 * The main program.
 */
int main(void) {

	init();
	dijkstra(0);

	int t;
	scanf("%d", &t);

	for(int i = 0; i < t; i++) {
		for(int j = 0; j < 9; j++) {
			scanf("%d", &map[j]);
			if(map[j] == 0) {
				map[j] = 9;
			}
		}
		int s = get_status_from_map();
		if(graph[s].distance == -1) {
			printf("No Solution!\n");
		} else {
			printf("%d\n", graph[s].distance);
		}
	}

	return 0;
}
时间: 2024-11-06 03:37:14

ACM:搜索算法专题(3)——启发式搜索的相关文章

ACM专题

密码:gzhuacm512 喝茶补番码专题之五十二:数论补充 喝茶补番码专题之四十九:容斥原理和莫比乌斯 喝茶补番码专题之二十九:插头DP 喝茶补番码专题之二十七:数位DP 喝茶补番码专题之二十五:polya 喝茶补番码专题之十二:组合数学基础 数学训练六 概率/期望 数学训练三 数学训练二 counting

ACM:数论专题(3)——约瑟夫问题

(p.s: 以前做约瑟夫问题都是用链表模拟,今天发现了一个效率更高的方法,受教了...) 题目描述: 小Hi和小Ho的班级正在进行班长的选举,他们决定通过一种特殊的方式来选择班长. 首先N个候选人围成一个圈,依次编号为0..N-1.然后随机抽选一个数K,并0号候选人开始按从1到K的顺序依次报数,N-1号候选人报数之后,又再次从0开始.当有人报到K时,这个人被淘汰,从圈里出去.下一个人从1开始重新报数. 也就是说每报K个数字,都会淘汰一人.这样经过N-1轮报数之后,圈内就只剩下1个人了,这个人就作

acm专题---拓扑排序+优先队列

题目来源:http://acm.hdu.edu.cn/showproblem.php?pid=1285 Problem Description 有N个比赛队(1<=N<=500),编号依次为1,2,3,....,N进行比赛,比赛结束后,裁判委员会要将所有参赛队伍从前往后依次排名,但现在裁判委员会不能直接获得每个队的比赛成绩,只知道每场比赛的结果,即P1赢P2,用P1,P2表示,排名时P1在P2之前.现在请你编程序确定排名. Input 输入有若干组,每组中的第一行为二个数N(1<=N&l

ACM: POJ 1401 Factorial-数论专题-水题

POJ 1401 Factorial Time Limit:1500MS     Memory Limit:65536KB     64bit IO Format:%lld & %llu Description The most important part of a GSM network is so called Base Transceiver Station (BTS). These transceivers form the areas called cells (this term

ACM&amp;OI 基础数学算法专题

[前言] 本人学习了一定时间的算法,主要精力都花在数学类的算法上面 而数学类的算法中,本人的大部分精力也花费在了数论算法上 此类算法相对抽象,证明过程比较复杂 网络上的博客有写得非常好的,但也有写得不明所以的 因此,本人特此开一个新的专题,专门负责讲解一些比较基础的数学类算法 但本人知识面也有限,部分算法也还未掌握.因此,希望本专题也能促进本人学习这些算法 下面做出对更新的一些规定: 基本上保持每日更新一贴 对标注(已完结)的贴子,表明本贴子已完结 对标注(正在更新)的贴子,表明正在更新本贴 对

搜索算法acm

2016-10-11 21:18:45 HDU 1016 A ring is compose of n circles as shown in diagram. Put natural number 1, 2, ..., n into each circle separately, and the sum of numbers in two adjacent circles should be a prime. Note: the number of first circle should al

ACM: How many integers can you find-数论专题-容斥原理的简单应用+GCD

How many integers can you find Time Limit:5000MS     Memory Limit:32768KB     64bit IO Format:%I64d & %I64u Description Now you get a number N, and a M-integers set, you should find out how many integers which are small than N, that they can divided

acm专题三1006

Problem Description 在讲述DP算法的时候,一个经典的例子就是数塔问题,它是这样描述的:<br><br>有如下所示的数塔,要求从顶层走到底层,若每一步只能走到相邻的结点,则经过的结点的数字之和最大是多少?<br><img src=../data/images/2084-1.jpg><br>已经告诉你了,这是个DP的题目,你能AC吗? Input 输入数据首先包括一个整数C,表示测试实例的个数,每个测试实例的第一行是一个整数N(1

ACM: POJ 1061 青蛙的约会 -数论专题-扩展欧几里德

POJ 1061 青蛙的约会 Time Limit:1000MS     Memory Limit:10000KB     64bit IO Format:%lld & %llu Description 两只青蛙在网上相识了,它们聊得很开心,于是觉得很有必要见一面.它们很高兴地发现它们住在同一条纬度线上,于是它们约定各自朝西跳,直到碰面为止.可是它们出发之前忘记了一件很重要的事情,既没有问清楚对方的特征,也没有约定见面的具体位置.不过青蛙们都是很乐观的,它们觉得只要一直朝着某个方向跳下去,总能碰