【算法】康托展开
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>通过。
源代码:
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; }
相关文章推荐
- 白话算法(7) 生成全排列的几种思路(二) 康托展开
- javascript utf-8 url 编码 之 纯算法 解码 by shawl.qiu 2007-9-24
- java数学表达式计算算法
- 泛型算法中c++ primer介绍的函数
- 特征选择算法之开方检验
- 15位身份证转18位批量转换工具及其算法(VB版)
- java经典算法四十题
- Java虚拟机的垃圾收集算法
- 图像算法在DSP嵌入式移植中常用的优化方法
- 关于DPM(Deformable Part Model)算法中模型可视化的解释
- 简单排序算法之堆排序
- 算法与数据结构面试题(20)-层序遍历二叉树
- 简单的跟踪算法
- <Fast Tracking via Spatio-Temporal Context Learning> 阅读和算法使用总结
- 曲面映射的算法理论基础
- C语言之linux内核实现最大公约数算法
- BST算法原理及其实现
- 利用MSER算法提取图像区域(车牌区域)的OpenCV代码~
- java全组合算法
- 算法第一天--桶排序