康拓展开介绍

康拓展开概念引入

今天在解八数码问题时,用DFS直接穷举暴力搜索,一直爆栈。无奈,只能参考别人的代码,同样是DFS却PASS了。看了下他的代码,DFS深度写死了,如下:

void DFS(int x, int y)
{
    if(step > 8)
        return;
    ...
}

很好奇为啥只需要8步?难道无论怎么摆放,要达到目标状态,一定不会超过8步就可以完成么?我把自己的代码也加了这个条件,直接PASS。。。震惊!!

带着这个问题继续上网搜索相关资料,了解到这个问题可能无解,也有需要30多步才能解决的。那这样看来,就是系统的判定case太弱了,所以深度只需要8就可以AC。。

搜罗了很久,发现网上大部分答案都是BFS,A*…,其中他们用到的康拓展开来保存状态,回退状态。当然我也是把这九个数字组成的状态映射成一个数字来保存,但是感觉就是没康拓展开来的科学。所以决定看下康拓展开是怎么一回事。

定义

康拓展开是一个全排列到一个自然数的双射,常用于构建哈希表时的空间压缩。康拓展开的实质是计算当前排列在所有由小到大全排列中的顺序,并且是可逆的。
比如123的全排列总共6个,通过康拓展开可以计算出由小到大,231排在第几个。

公式

康拓展开就是指把一个字符、数字序列展开成如下形式:
这里写图片描述

举个栗子:

2 5 4 1 3展开为46。因为X=1*4!+33!+2*2!+0*1!+0*0!=46.

解释:
  • 排列的第一位是2,比2小的数有一个,以这样的数开始的排列有4!个,因此第一项为 1*4!。
  • 排列的第二位是5,比5小的数有四个,由于2已经出现,因此共有3个比5小的数,这样的排列有33!个,以此类推,直至00!

可见在八数码问题中,9位数字的排列组合可以通过康拓展开来把他们映射成一个数,方便保存和利用,压缩了空间。

康拓展开代码如下:
const int fac[]={1,1,2,6,24,120,720,5040,40320,362880}; //各阶乘结果0!,1!,2!,3!,4!...

//康拓展开函数
int cantor(int a[], int len)
{
    int ans = 0;
    for(rint i = 0; i < len; i++)  //a数组是给出的数,n是元素个数
    {
        int cnt = 0;               //统计有多少数比a[i]小 
        for(rint j = i+1; j < len; j++)
        {
            if(a[i] > a[j])
                cnt++;
        }
        ans+=cnt*fac[len-i];
    }
    return ans;
}

int main()
{
    int a[4] = {1, 2, 4, 3};
    printf("%d", cantor(a, 4));
    return 0;
}
逆康拓展开

假如给你一个在全排列中排在第几位的数,求出这个排列组合的具体表示,这便是你康拓展开。还是用上面的代码例子。

假如原来的数组1234,求他排在第24位的组合,如何求? 排序是从0开始,那么这里应当首先 24-1 = 23
* 23 / 3! = 3 余 5, 那么表示比他小的数有3个,即 4。
* 5 / 2! = 2 余 1, 那么表示比他小的数有2个,即 3。
* 1 / 1! = 1 余 0, 那么表示比他小的数有1个,即 2。
* 0 / 0! = 0 余 0, 那么表示比他小的数有0个,即 1。
* 算出这个组合便是:4321

逆康拓展开代码如下:
#include<stdio.h>
#define rint register int 
const int fac[]={1,1,2,6,24,120,720,5040,40320,362880}; //各阶乘结果0!,1!,2!,3!,4!...

void cantorReverse(int a[], int len, int k)
{
    int temp;
    int vis[20] = {0};//记录数字的使用情况
    for(rint i = 0; i < len; i++)
    {
        temp = k/fac[len - 1 - i];
        k %= fac[len - 1 - i];
        for(rint j = 0; j <= temp; j++)
        {
            if(vis[j])//如果有比他小的数已经用过了
                temp++;
        }
        a[i] = temp + 1;
        vis[temp] = 1;
    }
}

int main()
{
    int a[4] = {0};
    int k = 16;
    cantorReverse(a, 4, k);
    printf("%d %d %d %d",a[0],a[1],a[2],a[3]);
    return 0;
}

总结

康拓展开和逆展开可以用于把字符,数字的组合排列映射成一个数字,用于哈希表的构造,将字符序列或者数字序列映射成一个值或者状态。

本文原创首发于微信公众号 [ 林里少年 ],欢迎关注第一时间获取更新。

这里写图片描述

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值