康托展开


解释

康托展开是一种特殊的哈希函数。康托展开可以完成了如下图所示的工作

状态012345678012345687012345768012345786876543210
Cantor0123362880 - 1

第一行是 0 ~ 8 这 9 个数字的全排列,共 9! = 362880 个,按从小到大排序。第二行是每个排列对应的位置,例如最小的 {012345678} 在第 0 个位置, 最大的 {876543210} 在最后的 362880 - 1 位置。

  • 函数Cantor()实现的功能是:输入一个排列,即第一行的某个排列,计算出它的Cantor值,即第二行对应的数值

Cantor()的复杂度为O(n2),n 是集合中元素的个数。

原理

例:判断 2143 是 {1, 2, 3, 4} 的全排列中第几大的数。
计算排在 2143 前面的排列数目,可以将问题转化为以下排列的和。

  • (1)首位小于 2 的所有排列。比 2 小的只有 1 一个数,后面 3 个数的排列有 3 * 2 * 1 = 3! 个,写成 1 * 3! = 6
  • (2)首位为 2、第 2 位小于 1 的所有排列。无,写成 0 * 2! = 0。
  • (3)前两位为 21、第 3 位小于 4 的所有排列。只有 3 一个数(2134),写成 1 * 1! = 1。
  • (4)前 3 位为 214、第四位小于 3 的所有排列。无,写成 0 * 0! = 0。

求和:1 * 3! + 0 * 2! + 1 * 1! + 0 * 0! = 7,所以 2143 是第八大的数。如果用 int visited[24]数组记录各排列的位置,{2143} 就是 visited[7]; 第一次访问这个排列时,置visited[7] = 1; 当再次访问这个排列的时候发现 visited[7] = 1,说明已经处理过,判重。
根据上面的例子得到康托展开公式。
把一个集合产生的全排列按字典序排列,第 X 个元素的计算公式如下

  • X = a[n] * (n - 1)! + a[n - 1] * (n - 2)! + … + a[i] * (i - 1)! + … + a[2] * 1! + a[1] * 0!

其中,a[i] 表示原数的第 i 位在当前未出现元素中排在第几个(从 0 开始),并且有 0 <= a[i] < i(1 <= i < n)

  • 上述过程的反过程是康托逆展开:某个集合的全排列,输入一个数字 k,返回第 k 大的排列

处理问题

八数码问题

在一个 3 * 3 的棋盘上放置编号为 1 ~ 8 的 8 个方块,每个占一格,另外还有一个空格。与空格相邻的数字方块可以移动到空格里。任务一:指定初始棋局和目标棋局,计算出最少的移动步数;任务二:输出数码的移动序列。
//输入:1 2 3 0 8 4 7 6 5
1 0 3 8 2 4 7 6 5
//输出:2

#include <bits/stdc++.h>
const int LEN = 362880;//共 9! = 362880 种 
using namespace std;
struct node{
	int state[9];//记录一个八数码的排列,即一个状态 
	int dis;//记录到起点的距离 
};

int dir[4][2] = {{-1, 0}, {0, -1}, {1, 0}, {0, 1}};//左、上、右、下,顺时针,左上为(0, 0); 

int visited[LEN] = {0};//与每个状态对应的记录,cantor()函数对它置数,并判重 
int start[9];//开始状态 
int goal[9];//目标状态 
long int factory[] = {1, 1, 2, 6, 24, 720, 5040, 40320, 362880};

bool Cantor(int str[], int n){
	long result = 0;
	for(int i = 0; i < n; i ++){
		int counted = 0;
		for(int j = i + 1; j < n; j ++){
			if(str[i] > str[j])
				++ counted;
		}
		result += counted * factory[n - i - 1];
	}
	if(!visited[result]){
		visited[result] = 1;
		return 1;
	}
	else
		return 0;
}

int bfs(){
	node head;
	memcpy(head.state, start, sizeof(head.state));
	head.dis = 0;
	queue<node>q;
	Cantor(head.state, 9);
	q.push(head);
	
	while(!q.empty()){
		head = q.front();
		if(memcmp(head.state, goal, sizeof(goal)) == 0)
			return head.dis;
		q.pop();
		int z;
		for(z = 0; z < 9; z ++)//找这个状态种元素 0 的位置 
			if(head.state[z] == 0)//找到了 
				break;
		int x = z%3;//横坐标 
		int y = z/3;//纵坐标 
		for(int i = 0; i < 4; i ++){//左、上、右、下,四种变化 
			int newx = x + dir[i][0];//元素 0 转移后的新坐标 
			int newy = y + dir[i][1];
			int nz = newx + 3 * newy;//转化为一维 
			if(newx >= 0 && newx < 3 && newy >= 0 && newy < 3){//未越界 
				node newnode;
				memcpy(&newnode, &head, sizeof(struct node));//复制这新的状态 
				swap(newnode.state[z], newnode.state[nz]);//把 0 移到新的位置 
				newnode.dis ++;
				if(Cantor(newnode.state, 9))//用康托展开判重 
					q.push(newnode);//把新的状态放进队列 
			}
		} 
	}
	return -1;//没找到 
}
int main(){
	for(int i = 0; i < 9; i ++) cin >> start[i];//初始状态 
	for(int i = 0; i < 9; i ++) cin >> goal[i];//目标状态 
	int num = bfs();
	if(num != -1) cout << num << endl;
	else cout << "Impossible" << endl;
	return 0;
}
//1 2 3 0 8 4 7 6 5 1 0 3 8 2 4 7 6 5

  • 4
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值