1.概述
康托展开(Cantor Expansion)是一个相对快速的判重方法,是一种特殊的哈希函数,其复杂度为O(n^2),n是集合中元素的个数。函数康托Cantor()实现的功能是:输入一个排列,得到这个排列所对应的Cantor值。
2.举例
表1 康托展开完成的工作
排列状态 | 012345678 | 012345687 | 012345768 | 012345786 | …… | 876543210 |
Cantor值 | 0 | 1 | 2 | 3 | …… | 362880-1 |
表1向我们展示了0~8这9个数的全排列,共9! = 362880个,并按从小到达的顺序排序。第二行Cantor值代表了每个排列所对应的位置,例如最小的排列012345678对应了0这个位置,最大的876543210对应了362880-1这个位置。
3.使用价值
为什么说使用康托展开能提升判重速度呢?如表1所示,若我们使用暴力判重,每次把得到的新状态与这9个数的全排列状态9! = 362880个来对比,完成搜索+判重可能会有9! * 9!次检查(最差情况),不可行。而如果我们使用康托展开判重,完成搜索+判重只需要9! * 9^2次。对应的复杂度即为O(n! * n^2),暴力法为O(n! * n!),可以看出复杂度大大降低。
4.原理讲解
下面我通过将例子的方式讲解康托展开的原理:
例子:判断2143是{1, 2, 3, 4}集合中的全排列中的第几大的数
求解:
{1, 2, 3, 4}共四个数,共有4! = 24种排序。计算排在2143前面的排列数目,可以将这个问题转化为求以下排列的和:
- 首位小于2的所有排列:比2小的只有1一个数,后面3个数有3! = 6种,写成1 * 3! = 6种。
- 首位为2、第二位小于1的所有排列:无,写成0 * 2! = 0种。
- 前两位为21、第三位小于4的所有排列:只有3一个数,写成1 * 1! = 1种。
- 前三位为214、第四位小于3的所有排列:无,写成0 * 0! = 0种。
求和:1 * 3! + 0 * 2! + 1 * 1! + 0 * 0! = 7,所以说2143是排在第八大的排列。如果用visited[24]这个数组来存放各个排列的位置的话,2143应该在visited[7]这个位置。如果我们要判重,只需要将visited[7]这个标志为改一下就行,比如原先visited[24] = {0},当我们访问过visited[7]后,将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大的排列。