一种变进制数及其应用(全排列之Hash实现)

AI权益加码!Claude Code、Cursor等20+工具免费用! 购周边限时加赠Coding Plan Lite,畅享主流AI工具!学习进阶更高效! 阅读详情

 我们经常使用的数的进制为“常数进制”,即始终逢p进1。例如,p进制数K可表示为
    K = a0*p^0 + a1*p^1 + a2*p^2 + ... + an*p^n (其中0 <= ai <= p-1),
它可以表示任何一个自然数。

对于这种常数进制表示法,以及各种进制之间的转换大家应该是很熟悉的了,但大家可能很少听说变进制数。这里我要介绍一种特殊的变进制数,它能够被用来实现全排列的Hash函数,并且该Hash函数能够实现完美的防碰撞和空间利用(不会发生碰撞,且所有空间被完全使用,不多不少)。这种全排列Hash函数也被称为全排列数化技术。下面,我们就来看看这种变进制数。

我们考查这样一种变进制数:第1位逢2进1,第2位逢3进1,……,第n位逢n+1进1。它的表示形式为
    K = a1*1! + a2*2! + a3*3! + ... + an*n! (其中0 <= ai <= i),
也可以扩展为如下形式(因为按定义a0始终为0),以与p进制表示相对应:
    K = a0*0! + 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。这说明该变进制数能够正确进位,从而是一种合法的计数方式。

接下来我们考查n位变进制数K的性质:
(1)当所有位ai均为i时,此时K有最大值
    MAX[K] = 1*1! + 2*2! + 3*3! + ... + n*n!
           = 1! + 1*1! + 2*2! + 3*3! + ... + n*n! - 1
           = (1+1)*1! + 2*2! + 3*3! + ... + n*n! - 1
           = 2! + 2*2! + 3*3! + ... + n*n! - 1
           = ...
           = (n+1)!-1
    因此,n位K进制数的最大值为(n+1)!-1。
(2)当所有位ai均为0时,此时K有最小值0。
因此,n位变进制数能够表示0到(n+1)!-1的范围内的所有自然数,共(n+1)!个。

在一些状态空间搜索算法中,我们需要快速判断某个状态是否已经出现,此时常常使用Hash函数来实现。其中,有一类特殊的状态空间,它们是由全排列产生的,比如N数码问题。对于n个元素的全排列,共产生n!个不同的排列或状态。下面将讨论如何使用这里的变进制数来实现一个针对全排列的Hash函数。

从数的角度来看,全排列和变进制数都用到了阶乘。如果我们能够用0到n!-1这n!个连续的变进制数来表示n个元素的所有排列,那么就能够把全排列完全地数化,建立起全排列和自然数之间一一对应的关系,也就实现了一个完美的Hash函数。那么,我们的想法能否实现呢?答案是肯定的,下面将进行讨论。

假设我们有b0,b1,b2,b3,...,bn共n+1个不同的元素,并假设各元素之间有一种次序关系 b0<b1<b2<...<bn。对它们进行全排列,共产生(n+1)!种不同的排列。对于产生的任一排列 c0,c1,c2,..,cn,其中第i个元素ci(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个元素的全排列建立起一一对应的关系。

由于n位变进制数能表示(n+1)!个不同的数,而n+1个元素的全排列刚好有(n+1)!个不同的排列,且每一个排列都已经能表示成一个n位变进制数。如果我们能够证明任意两个不同的排列产生两个不同的变进制数,那么我们就可以得出结论:
★ 定理1 n+1个元素的全排列的每一个排列对应着一个不同的n位变进制数。

对于全排列的任意两个不同的排列p0,p1,p2,...,pn(排列P)和q0,q1,q2,...,qn(排列Q),从后往前查找第一个不相同的元素,分别记为pi和qi(0 < i <= n)。
(1)如果qi > pi,那么,
如果在排列Q中qi之前的元素x与qi构成逆序对,即有x > qi,则在排列P中pi之前也有相同元素x > pi(因为x > qi且qi > pi),即在排列P中pi之前的元素x也与pi构成逆序对,所以pi的逆序数大于等于qi的逆序数。又qi与pi在排列P中构成pi的逆序对,所以pi的逆序数大于qi的逆序数。
(2)同理,如果pi > qi,那么qi的逆序数大于pi的逆序数。
因此,由(1)和(2)知,排列P和排列Q对应的变进制数至少有第i位不相同,即全排列的任意两个不同的排列具有不同的变进制数。至此,定理1得证。

计算n个元素的一个排列的变进制数的算法大致如下(时间复杂度为O(n^2)):
template <typename T>
size_t PermutationToNumber(const T permutation[], int n)
{
    // n不能太大,否则会溢出(如果size_t为32位,则n <= 12)
    size_t result = 0;
    for (int j = 1; j < n; ++j) {
        int count = 0;
        for (int k = 0; k < j; ++k) {
            if (permutation[k] > permutation[j])
                ++count;
        }
        // factorials[j]保存着j!
        result += count * factorials[j];
    }

    return result;
}

说明:
(1)由于n!是一个很大的数,因此一般只能用于较小的n。
(2)有了计算排列的变进制数的算法,我们就可以使用一个大小为n!的数组来保存每一个排列的状态,使用排列的变进制数作为数组下标,从而实现状态的快速检索。如果只是标记状态是否出现,则可以用一位来标记状态。

最后,附上一段完整的代码来演示使用变进制数实现全排列的数化(或者Hash,随便怎么称乎了)。


ImmutableArray.h

 

ImmutableArray_example.cpp

 

Assure.h

 

 

PermutationMap.h

 

PermutationMap_example.cpp

 

 

十进制数 <--> 变进制数 <--> 排列”之间的转换算法

 

 

 

【vjudge】八数码问题 EightThe 15-puzzle has been around for over 100 years; even if you don’t know it by that name, you’ve seen it. It is constructed with 15 sliding tiles, each with a number from 1 to 15 on it, and all pa 阅读详情

相关推荐

poj-1077 Eight(BFS 回溯)

Eight Time Limit: 1000MS Memory Limit: 65536K Total Submissions: 43117 Accepted: 17541 Special Judge Description The 15-puzzle has been around for over 100 years; even if you don’t know it by that name, you’ve seen it. It is constructed with 15 sliding

weixin_47750287的博客 370

【HDU 1043】Eight(A*启发式搜索算法

DescriptionThe 15-puzzle has been around for over 100 years; even if you don’t know it by that name, you’ve seen it. It is constructed with 15 sliding tiles, each with a number from 1 to 15 on it, and

Ice_Confuse 1739

(皇后移动类)八数码难题引发的搜索思考及总结

POJ 1077 Eight The 15-puzzle has been around for over 100 years; even if you don't know it by that name, you've seen it. It is constructed with 15 sliding tiles, each with a number from 1 to 15 o

在思索中前行! 4619

关于poj1077-八数码问题的深入探究

作为一个刚入acm坑不久的小白,看到绿色的accept六个字母的总是格外激动。可最近几天笔者却迟迟无法AC这道不算太复杂的八数码问题。谨以此文记录我的解题经验和跌宕起伏的心路历程。同样是小白的同学可以参考一下,技术大牛就直接飘过吧~ 题目是这样的: Description The 15-puzzle has been around for over 100 years; even if

Q1751703305的博客 507

(转)一种进制数及其应用全排列Hash实现

加上上文的康拓展开,此文,还有组合数学上的“序数法”,一共三种映射排列到数字的方法 我们经常使用的(9php.com)数的(9php.com)进制为“常数进制”,即始终逢p进1。例如,p进制数K可表示为     K = a0*p^0 + a1*p^1 + a2*p^2 + ... + an*p^n (其中0 它可以表示任何一个自然数。 对于这种常数进制表示法,以及各种进制之间的(9p

Kizuki 662

一种进制数及其应用

进制数 我们经常使用的数的进制为“常数进制”,即始终逢p进1。例如,p进制数K可表示为     K = a0*p^0 + a1*p^1 + a2*p^2 + ... + an*p^n (其中0 ), 它可以表示任何一个自然数。 对于这种常数进制表示法,以及各种进制之间的转换大家应该是很熟悉的了,但大家可能很少听说进制数。这里我要介绍一种特殊的进制数,它能够被用来实现全排列的Has

asd_wolf的专栏 1063

全排列Hash实现

zz http://hi.baidu.com/sonwkuni/blog/item/8be877af32b069cb7dd92a30.html 我们经常使用的数的进制为“常数进制”,即始终逢p进1。例如,p进制数K可表示为 K = a0*p^0 + a1*p^1 + a2*p^2 + ... + an*p^n (其中0 它可以表示任何一个自然数。 对于这种常数进制表示法,以及各种进制

ivgwt的专栏 2285

全排列hash算法

参考的别人的点击打开链接 p进制数K可表示为K = a0*p^0 + a1*p^1 + a2*p^2 + ... + an*p^n (其中0 它可以表示任何一个自然数。 一种特殊的进制数,它能够被用来实现 全排列Hash函数,并且该Hash函数能够实现完美的防碰撞和空间利用(不会发生碰撞,且所有空间被完全使用,不多不少)。 我们考查这样一种进制数:第1位逢2进1,第2位逢3进1

ACMer_AK的博客 960

全排列Hash函数(JAVA)

我们经常使用的数的进制为“常数进制”,即始终逢p进1。例如,p进制数K可表示为 K = a0*p^0 + a1*p^1 + a2*p^2 + ... + an*p^n (其中0

心中的利剑:HTML5 Canvas游戏动画(JavaScript) 198

全排列Hash算法

<br />全排列Hash算法<br />OI 2009-08-28 15:43:39 阅读69 评论0   字号:大中小 订阅<br />我们经常使用的数的进制为“常数进制”,即始终逢p进1。例如,p进制数K可表示为<br />K = a0*p^0 + a1*p^1 + a2*p^2 + ... + an*p^n (其中0 <= ai <= p-1),<br />它可以表示任何一个自然数。<br /><br />对于这种常数进制表示法,以及各种进制之间的转换大家应该是很熟悉的了,但大家可能很少听说进制

kongming_acm的专栏 1039

【转】康托展开(全排序到自然数空间的映射)

1. 概述 康托展开是将n个数的全排列映射到自然数空间{0, 1, ... , n!-1}的双射。在介绍康托展开之前,先介绍几个概念:进制数、逆序对。 1.1 进制 我们经常使用进制有:二进制、十进制、十六进制。这些进制称为“常数进制”,有一个共同点,即逢p进1;比如,十六进制是每位逢16进1,十进制数每位逢10进1。p进制数K可表示为 K = a1*p^1 + a2*p^2 + .....

weixin_30716141的博客 190

进制数

全排列哈希 我们熟知的数一般都是常进制数,所谓常进制数就是该数的每一位都是常数进制: k进制数上的每一位都逢k进一, 第i位的位权是kik^iki 这里要介绍一种进制数,用来表示字符串的排列状态 这种数的第i位逢i进一,第i位的位权是i!i!i! 用d[i]来表示一个进制数第i位上的数字 一个n位进制数的值就为∑i=0n−1d[i]×i!\sum\limits^{n - 1}_{i=0}d[i]×i!i=0∑n−1​d[i]×i! 这是一个最大的9位进制数 876543210 它对应的十进制

weixin_44368514的博客 714

全排列Hash实现 - 进制数 【转】

<br />我们经常使用的数的进制为“常数进制”,即始终逢p进1。例如,p进制数K可表示为<br />K = a0*p^0 + a1*p^1 + a2*p^2 + ... + an*p^n (其中0 <= ai <= p-1),<br />它可以表示任何一个自然数。<br /><br />对于这种常数进制表示法,以及各种进制之间的转换大家应该是很熟悉的了,但大家可能很少听说进制数。这里我要介绍一种特殊的进制数,它能够被用来实现 全排列Hash函数,并且该Hash函数能够实现完美的防碰撞和空间利用(不会

bobten2008的专栏 1560

全排列hash

zz http://hi.baidu.com/sonwkuni/blog/item/8be877af32b069cb7dd92a30.html 我们经常使用的数的进制为“常数进制”,即始终逢p进1。例如,p进制数K可表示为 K = a0*p^0 + a1*p^1 + a2*p^2 + ... + an*p^n (其中0 它可以表示任何一个自然数。 对于这种常数进制表示法,以及各种

醉江楼的专栏 763

全排列Hash函数

/*全排列哈希 这里要介绍一种进制数,用来表示字符串的排列状态 这种数的第i位逢i进一,第i位的位权是i! 用d[i]来表示一个进制数第i位上的数字 一个n位进制数的值就为∑[i:0~n-1] (d[i]×i!) */ int permutation_hash(string s, int n) //求长度为n的字符串某种排列的哈希值 { //n不能太大,通常不超过12,否...

1262934560的博客 680
上一篇: Visual C++进程间数据通信的实现
下一篇: 关于位图排序
inter999
博客等级 码龄19年 18粉丝 9原创
评论 1
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值