算法描述
把一个整数 X 展开成如下形式:
X = a[n] * (n-1)! + a[n-1] * (n-2)! + ... + a[i] * (i-1)! + ... + a[1] * 0!
其中,a[i] 为整数,并且 0 <= a[i] < i (1 <= i <= n)
适用领域范围:解决一些序列问题的算法.
实例一
{1,2,3,4,…,n} 表示 1,2,3,…,n 的排列.如 {1,2,3} 按从小到大排列一共6个:123, 132, 213, 231, 312, 321.代表它们的数字是:1, 2, 3, 4, 5, 6,也就是把10进制的某个数与一个排列对应起来.
他们间的对应关系可由康托展开来找到.
如我想知道 321 是 {1,2,3} 中第几个小的数时,可以这样考虑:
第一位是3:小于3的数有1、2.所以有2*2!个;
第二位是2:小于2的数只有一个就是1,所以有1*1!=1;
第三位是1:小于1的数有0个,也就是0*0!=0;
所以小于321的{1,2,3}排列数有2*2!+1*1!=5个;
所以321是第6个小的数。
2*2!+1*1!+0*0!就是康托展开。
再举个例子:1324是{1,2,3,4}排列数中第几个大的数:
第一位是1,小于1的数没有,是0个,0*3!
第二位是3,小于3的数有1和2,但1已经在第一位了,所以只有一个数2,1*2! 。
第三位是2,小于2的数是1,但1在第一位,所以有0个数,0*1! ,
所以比1324小的排列有0*3!+1*2!+0*1!=2个,1324是第三个小数。
C 语言代码实现:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21//参数 int s[] 为待展开之数的各位数字,如需展开2134,则s[4] = {2,1,3,4}
//参数 n 为待展开的数字个数,这里是4
//阶乘表 long int fac[] = {1, 1, 2, 6, 24, 120, 720, 5040, 40320, 362880}
long cantor(int s[], int n)
{
long int i, j, temp, num;
num = 0;
for(i=0; i<n; i++)
{
temp = 0;
for(int j=i+1; j<n; j++)
{
if(s[j]<s[i])
temp++; //判断几个数小于它
}
num += fac[n-i-1] * temp; //(或num = num + fac[n-i-1] * temp;)
}
return num+1;
}
实例二
在魔方风靡全球之后不久,Rubik先生发明了它的简化版――魔板。魔板由 8 个同样大小的方块组成,每个方块颜色均不相同,可用数字 1-8 分别表示。任一时刻魔板的状态可用方块的颜色序列表示:从魔板的左上角开始,按顺时针方 向依次写下各方块的颜色代号,所得到的数字序列即可表示此时魔板的状态。
例如,序列(1,2,3,4,5,6,7,8)表示魔板状态为:
1 2 3 4
8 7 6 5
对于魔板,可施加三种不同的操作,具体操作方法如下:
A: 上下两行互换,如上图可变换为状态87654321
B: 每行同时循环右移一格,如上图可变换为41236785
C: 中间4个方块顺时针旋转一格,如上图可变换为17245368
给你魔板的初始状态与目标状态,请给出由初态到目态变换数最少的变换步骤,若有多种变换方案则取字典序最小的那种.
解题思路:
1: 把初始状态转换成 12345678 这个状态
2: 把目标状态按照初始状态的转换进行转换
例如:初始状态为 63728145,目标状态为 86372541
将初始状态转换为 12345678,目标状态相应转化为 51234876
注释:初始状态6在1的位置上,将初始状态和目标状态中的6都用1代替,一次类推.
3: 使用康托展开和广度优先算法生成从12345678这个状态到其他所有状态的路径,即:8!种
注释:使用广度优先可以确保从12345678这个状态转换到的任何其他状态所经过的变换是最少的,这样就可以
保证最后结果是我们需要的字典序最小的那种.
代码:
1 | #include <iostream> |