谁养鱼(二):如何将排列映射到整数域

今天又考虑了一下谁养鱼的问题,觉得用遗传算法来解题值得一试,但是主要的难点有三:

  1. 如何让计算机理解线索?即如何表示绑定、相邻等关系?比如丹麦人喝茶的绑定关系,绿房子在白房子左边的左相邻关系等。
  2. 问题空间和遗传空间如何转换?问题空间是红黄蓝绿白这样的排列,如何将其转换成遗传空间的二进制编码?也就是编码问题。
  3. 遗传算法如何设计?包括种群规模,初始化,适应度函数,选择、交差、变异算子,终止条件等。

遗传算法本身是很好理解的,今天首先是给出第二个问题的解决办法。

 

房子1

房子2

房子3

房子4

房子5

颜色

 颜色A

颜色B

颜色C

颜色D

颜色E

国籍


 

 

 

 

饮料

 

 


 

 

宠物

 

 

 

 

 

香烟

 

 

 

 

 

要使用遗传算法,就必须先给出一个问题空间到遗传空间的编码方案。如表所示,五个房子是按顺序摆放的,每个房子都有五种属性:颜色、国籍、饮料、宠物、香烟,房子之间同一种属性都不能重复。以颜色属性这一行为例,在问题空间中,这一行是五种颜色的排列,而在遗传空间中必须转换为二进制编码。易知排列共有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,这和例子开始是一致的,编码和解码互相验证了正确性。

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;
	}
	
}

©️2020 CSDN 皮肤主题: 大白 设计师: CSDN官方博客 返回首页
实付0元
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值