您的位置:首页 > 其它

【算法】康托展开

2013-12-06 11:22 453 查看

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>通过。

源代码:

1077Accepted6056K219MSG++2001B2013-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;
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: