【算法】康托展开

1. 概述


康托展开是将n个数的全排列映射到自然数空间{0, 1, ... , n!-1}的双射。在介绍康托展开之前,先介绍几个概念:变进制数、逆序对。


1.1 变进制


我们经常使用进制有:二进制、十进制、十六进制。这些进制称为“常数进制”,有一个共同点,即逢p进1;比如,十六进制是每位逢16进1,十进制数每位逢10进1。p进制数K可表示为

K = a1*p^1 + a2*p^2 + ... + an*p^n  ,其中1<= ai <= p-1
该表示法可表示任何一个自然数。
 

有这样一种变进制数:第1位逢2进1,第2位逢3进1,……,第n位逢n+1进1。变进制数可K表示为   

K = a1*1! + a2*2! + a3*3! + ... + an*n!  ,其中0 <= ai <= i
假设变进制数K第i位ai为i+1,则说明需要进位,且ai*i!=(i+1)*i!=1*(i+1)!,即向高位进1;说明该变进制数能够正确进位,从而这是一种合法的计数方式。


这种变进制数K有如下性质(证明参看[1]):
(1)当所有位ai均为i时,K有最大值(n+1)!-1
(2)当所有位ai均为0时,K有最小值0


1.2 逆序对


对有 n 个互异元素的有序集A,如果存在正整数 i, j 使得 1 ≤ i < j ≤ n 而且 A[i] > A[j],则 <A[i], A[j]> 这个有序对称为 A 的一个逆序对,也称作逆序数。

例如:数组 <2,3,8,6,1> 的逆序对为:<2,1> <3,1> <8,6> <8,1> <6,1> 共5个。


1.3 康托展开


假设我们有b0,b1,b2,b3,...,bn共n+1个不同的元素,并假设各元素之间有一种次序关系 b0<b1<b2<...<bn。对它们进行全排列,共产生(n+1)!种不同的排列。对于产生的任一排列,第i个元素(1 <= i <= n)与它前面的i个元素构成的逆序对的对数为di(0 <= di <= i),那么我们得到一个逆序对对数序列d1,d2,...,dn(0 <= di <= i)。这不就是前面的n位变进制数的每一位么?于是,我们用n位变进制数M来表示该排列:

    M = d1*1! + d2*2! + ... + dn*n!
因此,每个排列都可以按这种方式表示成一个n位变进制数,并且该n位变进制数能与n+1个元素的全排列建立起一一对应的关系(证明参看[1])。上述这种将全排列映射到自然数空间的算法称为康托展开。


2. Referrence


[1] tyc611, 一种变进制数及其应用(全排列之Hash实现).

[2] 维基百科, 逆序对.

[3] 维基百科, 康托展开.



3. 问题


3.1 POJ 1077


n数码问题:输入一个排列,求能得到目标排列的最少步数变换。


先将排列映射到自然数空间,然后用BFS遍历求解最少步数的策略(参看前一篇)。


用C++结果TLE,换成G++结果报错'memset' was not declared in this scope,最后加上#include <cstring>  #include <cstdio>通过。


源代码:

1077 Accepted 6056K 219MS G++ 2001B 2013-12-06 10:28:46

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

#define MAX 362880

const int factorial[9] = {1,1,2,6,24,120,720,5040,40320};  //阶乘
int end,visit[MAX],solvable;

struct _parent    //记录父节点及移动方向
{
	int key;
	char op;
}parent[MAX];

struct state     //xtile记录9(即x)所在位置     
{
	int per[9],key,xtile;
};

state start;
queue<state>que;

int hash(int per[])                   //康托展开,即全排列的hash
{
	int i,j,count,result=0;
	for(i=1;i<9;i++)
	{
		count=0;
		for(j=0;j<i;j++)
			if(per[j]>per[i])
				count++;
		result+=count*factorial[i];	
	}
	return result;
}

void swap(int *a,int *b)
{
	int temp;
	temp=*a;
	*a=*b;
	*b=temp;
}

void exchange(state *ptr,char op,int varia)        //交换元素9
{
	int i;
	state next;
    for(i=0;i<9;i++)
		next.per[i]=ptr->per[i];
	swap(next.per[ptr->xtile],next.per[ptr->xtile+varia]);
    next.key=hash(next.per);
	next.xtile=ptr->xtile+varia;

	if(!visit[next.key])
	{
		visit[next.key]=1;
		parent[next.key].key=ptr->key;
		parent[next.key].op=op;
		que.push(next);
	}
}

void bfs()
{
	int que_size;
	state head;
	memset(visit,0,sizeof(visit));
	solvable=0;
	
	visit[start.key]=1;
	que.push(start);
	while(!que.empty()&&!solvable)
	{
		que_size=que.size();
		while(que_size--)
		{
			head=que.front();
			que.pop();
			if(head.key==end)
			{
				solvable=1;
				return;
			}
			
			if(head.xtile/3!=0) exchange(&head,'u',-3);
			if(head.xtile/3!=2) exchange(&head,'d',+3);
			if(head.xtile%3!=2) exchange(&head,'r',+1);
			if(head.xtile%3!=0) exchange(&head,'l',-1);
		}
	}
}

void init()
{
	int i;
	char ch;
	for(i=0;i<9;i++)
	{
		cin>>ch;
		if(ch=='x') 
		{
			start.per[i]=9;
			start.xtile=i;
		}
		else start.per[i]=ch-'0';
	}
	start.key=hash(start.per);
	end=0;
}

void output(int k)
{
	if(k!=start.key)
	{
		output(parent[k].key);
		printf("%c",parent[k].op);
	}
}

int main()
{
	init();
	bfs();
	if(solvable) 
	{
		output(end);
		printf("\n");
	}
	else printf("unsolvable\n");
	return 0;
}


你可能感兴趣的:(【算法】康托展开)