谁养鱼(二):如何将排列映射到整数域
2011-11-23 14:59
141 查看
今天又考虑了一下谁养鱼的问题,觉得用遗传算法来解题值得一试,但是主要的难点有三:
如何让计算机理解线索?即如何表示绑定、相邻等关系?比如丹麦人喝茶的绑定关系,绿房子在白房子左边的左相邻关系等。
问题空间和遗传空间如何转换?问题空间是红黄蓝绿白这样的排列,如何将其转换成遗传空间的二进制编码?也就是编码问题。
遗传算法如何设计?包括种群规模,初始化,适应度函数,选择、交差、变异算子,终止条件等。
遗传算法本身是很好理解的,今天首先是给出第二个问题的解决办法。
要使用遗传算法,就必须先给出一个问题空间到遗传空间的编码方案。如表所示,五个房子是按顺序摆放的,每个房子都有五种属性:颜色、国籍、饮料、宠物、香烟,房子之间同一种属性都不能重复。以颜色属性这一行为例,在问题空间中,这一行是五种颜色的排列,而在遗传空间中必须转换为二进制编码。易知排列共有5!=120种,而7个bit就可以表示128种状态,所以用1个字节就可以记录下五个房子分别是什么颜色。其它的属性是类似的,因此一共需要五个字节表示所有五个属性的排列。
现在的问题是,如何将颜色的排列一一映射到[0,119]的连续整数空间去呢?或者转换成更一般的形式,对于1,2,3……,n个不同的数,如何将它们的排列一一映射到[0,n!-1]的连续整数空间去,逆映射又如何呢?寻找这个映射f之前,先对映射f做一个限制:映射f应该并不改变数的大小关系。即排列a比排列b小,那么f(a)也比f(b)小。先看n=5的例子。
按照映射的性质,分析右起五位子串,易知:
f(12345) = 0,f(15432) = 1*4!-1 = 23;
f(21345) = 1*4! = 24, f(25431) = 2*4! -1 = 47;
f(31245) = 2*4! = 48, f(35421) = 3*4! -1 = 71;
f(41235) = 3*4! = 72, f(45321) = 4*4! -1 = 95;
f(51234) = 4*4! = 96, f(54321) = 5*4! -1 = 119;
这些等式求出了1xxxx、2xxxx、3xxxx、4xxxx、5xxxx各自的值域。显然,和普通的二进制转换十进制计算一样,这里每一位都是有权值,权值取决于它右边还有多少位,具体就是右边位数的阶乘,但是并不能直接用这一位的值乘以它的权值,需要将它的值转换为它在右起子串中从小到大排第几位再减一。
举个例子32451:3的右边有4位,权值是24,3在右起子串中排第三小,所以是(3-1)*24;2的右边有3位,权值是6,2在右起子串中排第二小,所以是(2-1)*6;4的右边有2位,权值是2,4在右起子串中排第二小,所以是(2-1)*2;5的右边有1位,权值是1,5在右起子串中排第二小,所以是(2-1)*1;将它们加起来就得到f(32451) = (3-1)*24 + (2-1)*6 + (2-1)*2 + (2-1)*1 = 57。
推广到一般情况:
f(a1a2a3……an) = (a1在右起子串的排行 - 1) * (n-1)! + (a2在右起子串的排行 -1) * (n-2)! + …… + (an-1在右起子串的排行 -1)*1 。
注:排行指的是从小到大排在第几位。
有了从问题空间到遗传空间的映射,还需要找出逆映射才行:已知一个[0,119]区间的整数y,求出它代表的是哪一个排列。注意到最右边一位的数字不参与到编码中,所以也不需要参与到解码中,因为所有数字之和是知道的,知道了其余n-1个数字,最后一个数字自然也就知道了。首先必须解一个方程y=24*x1+6*x2+ 2*x3+x4。从映射的算法可以知道,x4只能是0或1,x3只能是0、1、或2,x2只能是0、1、2或3,x1只能是0、1、2、3或4。根据y与2、3、4的模运算就可以求出x1、x2、x3、x4,再将它们全部增一得到在各自右起子串的从小到大排序的位置,但是我们还必须求得它们各自在完整串的从小到大排序的位置。
例如y=57,x4=y%2=1,y=y/2=28;x3=y%3=1,y=y/3=9;x2=y%4=1,y=y/4=2;x1=y=2。x1++;x2++;x3++;x4++。
因为每一位数字的左边都可能存在比它小的数字,所以它们的位置存在一些递增操作,显然最左边的数字就不需要了,它们递增多少取决于它们各自左边存在几个更小的数字。这里利用的最重要的规律是,如果第n位(从左数)数字比第n-1位数字大,那么xn至少等于xn-1,反之则第n位数字比第n-1位数字小。
例如y=57,已经求得x1=3,x2=2,x3=2,x4=2,可知最左边的数是3;x2小于x1,所以第二位数字比第一位数字小不需要递增,第二位数字是2;x3大于或等于x2,所以x3需递增一次,又因为递增一位后x3大于或等于x1,x3再递增一次,最终x3=4,第三位数字是4;x4大于或等于x3(递增前的值),递增,x4+1大于或等于x2,递增,x4+2大于或等于x1,递增,x4=5,第四位数字是5;因为五位数字之和是15,所以最后一个数字是15-3-2-4-5=1。最终解码得到32451,这和例子开始是一致的,编码和解码互相验证了正确性。
如何让计算机理解线索?即如何表示绑定、相邻等关系?比如丹麦人喝茶的绑定关系,绿房子在白房子左边的左相邻关系等。
问题空间和遗传空间如何转换?问题空间是红黄蓝绿白这样的排列,如何将其转换成遗传空间的二进制编码?也就是编码问题。
遗传算法如何设计?包括种群规模,初始化,适应度函数,选择、交差、变异算子,终止条件等。
遗传算法本身是很好理解的,今天首先是给出第二个问题的解决办法。
房子1 | 房子2 | 房子3 | 房子4 | 房子5 | |
颜色 | 颜色A | 颜色B | 颜色C | 颜色D | 颜色E |
国籍 | |||||
饮料 | |||||
宠物 | |||||
香烟 |
现在的问题是,如何将颜色的排列一一映射到[0,119]的连续整数空间去呢?或者转换成更一般的形式,对于1,2,3……,n个不同的数,如何将它们的排列一一映射到[0,n!-1]的连续整数空间去,逆映射又如何呢?寻找这个映射f之前,先对映射f做一个限制:映射f应该并不改变数的大小关系。即排列a比排列b小,那么f(a)也比f(b)小。先看n=5的例子。
按照映射的性质,分析右起五位子串,易知:
f(12345) = 0,f(15432) = 1*4!-1 = 23;
f(21345) = 1*4! = 24, f(25431) = 2*4! -1 = 47;
f(31245) = 2*4! = 48, f(35421) = 3*4! -1 = 71;
f(41235) = 3*4! = 72, f(45321) = 4*4! -1 = 95;
f(51234) = 4*4! = 96, f(54321) = 5*4! -1 = 119;
这些等式求出了1xxxx、2xxxx、3xxxx、4xxxx、5xxxx各自的值域。显然,和普通的二进制转换十进制计算一样,这里每一位都是有权值,权值取决于它右边还有多少位,具体就是右边位数的阶乘,但是并不能直接用这一位的值乘以它的权值,需要将它的值转换为它在右起子串中从小到大排第几位再减一。
举个例子32451:3的右边有4位,权值是24,3在右起子串中排第三小,所以是(3-1)*24;2的右边有3位,权值是6,2在右起子串中排第二小,所以是(2-1)*6;4的右边有2位,权值是2,4在右起子串中排第二小,所以是(2-1)*2;5的右边有1位,权值是1,5在右起子串中排第二小,所以是(2-1)*1;将它们加起来就得到f(32451) = (3-1)*24 + (2-1)*6 + (2-1)*2 + (2-1)*1 = 57。
推广到一般情况:
f(a1a2a3……an) = (a1在右起子串的排行 - 1) * (n-1)! + (a2在右起子串的排行 -1) * (n-2)! + …… + (an-1在右起子串的排行 -1)*1 。
注:排行指的是从小到大排在第几位。
有了从问题空间到遗传空间的映射,还需要找出逆映射才行:已知一个[0,119]区间的整数y,求出它代表的是哪一个排列。注意到最右边一位的数字不参与到编码中,所以也不需要参与到解码中,因为所有数字之和是知道的,知道了其余n-1个数字,最后一个数字自然也就知道了。首先必须解一个方程y=24*x1+6*x2+ 2*x3+x4。从映射的算法可以知道,x4只能是0或1,x3只能是0、1、或2,x2只能是0、1、2或3,x1只能是0、1、2、3或4。根据y与2、3、4的模运算就可以求出x1、x2、x3、x4,再将它们全部增一得到在各自右起子串的从小到大排序的位置,但是我们还必须求得它们各自在完整串的从小到大排序的位置。
例如y=57,x4=y%2=1,y=y/2=28;x3=y%3=1,y=y/3=9;x2=y%4=1,y=y/4=2;x1=y=2。x1++;x2++;x3++;x4++。
因为每一位数字的左边都可能存在比它小的数字,所以它们的位置存在一些递增操作,显然最左边的数字就不需要了,它们递增多少取决于它们各自左边存在几个更小的数字。这里利用的最重要的规律是,如果第n位(从左数)数字比第n-1位数字大,那么xn至少等于xn-1,反之则第n位数字比第n-1位数字小。
例如y=57,已经求得x1=3,x2=2,x3=2,x4=2,可知最左边的数是3;x2小于x1,所以第二位数字比第一位数字小不需要递增,第二位数字是2;x3大于或等于x2,所以x3需递增一次,又因为递增一位后x3大于或等于x1,x3再递增一次,最终x3=4,第三位数字是4;x4大于或等于x3(递增前的值),递增,x4+1大于或等于x2,递增,x4+2大于或等于x1,递增,x4=5,第四位数字是5;因为五位数字之和是15,所以最后一个数字是15-3-2-4-5=1。最终解码得到32451,这和例子开始是一致的,编码和解码互相验证了正确性。
package org.fumin.heredity; /* * 颜色/国籍/饮料/香烟/宠物,每一种排列的情况5!=120,可用1个字节编码。 * 五个字节能表示一个可能解。 * 此类提供将问题空间映射到遗传空间,以及逆映射的方法 * */ public class Coder { /* * 功能:将12345的排列映射到整数域[0,119] * 思想: * 每一位数字的权值,是在它之后的数字个数的阶乘,即权值分别为24,6,2,1,0 * 每一位数字的值,是将以它为首的右边数字串以升序排序后,它所在的位置减一。 * 比如3254,排序后是2345,那么“3”的位置是2,它的值就是1。因此类推。 * encode(32415)=2*24+1*6+1*2+0=56 * */ public byte encode(int permutation){ int x1=0,x2=0,x3=0,x4=0; byte ret=0; x1 = permutation/10000; permutation -= 10000*x1; x2 = permutation/1000; permutation -= 1000*x2; x3 = permutation/100; permutation -= 100*x3; x4 = permutation/10; ret += 24*(x1-1); x2 = x2>x1?x2-1:x2; x3 = x3>x1?x3-1:x3; x4 = x4>x1?x4-1:x4; ret += 6*(x2-1); x3 = x3>x2?x3-1:x3; x4 = x4>x2?x4-1:x4; ret += 2*(x3-1); x4 = x4>x3?x4-1:x4; ret+= x4-1; System.out.printf("%d, ",ret); return ret; } /* * 功能:将整数域[0,119]映射回12345的排列 * 思想: * 每一个整数都是根据y=(x1-1)*24+(x2-1)*6+(x3-1)*2+(x4-1)*1计算出来的 * 对y依次取2,3,4模再除以2,3,4就可以得到x1,x2,x3,x4,x5, * 它们分别代表了各自在排序后子串的位置 * * */ public int decode(byte gene){ int ret=0; int x1=0,x2=0,x3=0,x4=0,x5=0; /*恢复在子串的位置*/ /*gene=24*x1+6*x2+2*x3+1*x4+0*x5;*/ x4=gene%2; gene/=2; /*gene=12*x1+3*x2+x3*/ x3=gene%3; gene/=3; /*gene=4*x1+x2;*/ x2=gene%4; gene/=4; /*gene=x1;*/ x1=gene; x1++;x2++;x3++;x4++; /* * 已获得各自在子串的位置 * 需要求它们分别在完整串的位置 * x1不需要上升位置,它在子串的位置就等于其在完整串的位置 * b2,b3,b4分别记录x2、x3、x4各自需要上升几个位置 * 某个数的位置需要上升几位取决于它左边的数有几个比它小 * */ int b2=0,b3=0,b4=0; b2+=x2+b2>=x1?1:0; b3+=x3+b3>=x2?1:0; b3+=x3+b3>=x1?1:0; b4+=x4+b4>=x3?1:0; b4+=x4+b4>=x2?1:0; b4+=x4+b4>=x1?1:0; x2+=b2; x3+=b3; x4+=b4; /*x5没有参与code,也就不参与decode*/ x5=15-x1-x2-x3-x4; ret=x1*10000+x2*1000+x3*100+x4*10+x5; System.out.printf("%d\n", ret); return ret; } }
相关文章推荐
- 全排列与整数域的映射
- OJ 如何实现一个按输入顺序排列的映射?
- 10G 个整数,乱序排列,要求找出中位数。内存限制为 2G。只写出思路即可
- 如何将本地服务器映射到公网
- 给一个int型整数,如何将这个整数的奇偶位互换
- EF的默认映射以及如何使用Data Annotations和Fluent API配置数据库的映射
- 如何简单使用ngrok,将网站内网映射到外网
- 如何通过Tomcat映射路径访问服务器上的文件?
- 在一个二维数组中,每一行都按照从左到右递增的顺序排列,每一列都按照从上到下递增的顺序排列,请完成一个函数,输入这样的一个二维数组和一个整数,判断数组中是否含有该整数。
- 如何将一组长度不定的整数数据转换
- C++如何读取任意数目的整数
- 如何用C语言获得整数所需的最少二进制位数
- OC--有这么一个 整数 123456789,如何将这个整数的每一位数,从末位开始依次放入数组中,并遍历 倒序输出字符串
- 请问华为ar2220路由如何将内网ip映射到外网ip上 ...
- 如何让div横向排列
- 请问如何避免用判断来实现求一个整数的绝对值
- DDD中的值对象如何用NHibernate进行映射
- 如何对SQL Server数据库中的孤立用户和系统及用户建立映射
- 另一道看上去很吓人的面试题:如何交换a和b两个整数的值,不用额外空间 (Rev. 2)
- 另一道看上去很吓人的面试题:如何交换a和b两个整数的值,不用额外空间 (Rev. 2)