最近遇到这样的一个问题:如何用p个大小不一的1×ki,i=1...p的方块铺满一个M×N方格,方块不能旋转或横放。
也就是说,给出如下方程组的一个有效解。
以上方程组的第一行表示,方格的每一列都必须被填满;第二行表示,每个方块只能被使用一次;第三行表示,所有方块都必须被使用。
对于求解这个问题,一个最简单的方法就是穷举,但是很可惜,穷举的空间为2p,这意味着当p较大时,需要费很大的劲才能求解出此问题。因此,我的思路是,寻找一种方法,对这个空间进行缩小,在缩小的空间内进行搜索。以下是对我的思路的一种综述。
观察以上方程组,我们可以发现,如果这些方块能铺满在这个方格的话,那意味着,列于列之间是可以交换的,即列于列之间的顺序无关。因此,我们完全可以在这些方块中随便选择一个出来,然后填入方格的第一列,然后根据这一列剩余的空间,再在剩余的方块中选择可能的方块填入,当填完第一列后,重复以上步骤即可。因此,我们的问题可以先缩小为填充一列,即寻找k1x1+...+kpxp=M的有效解。
在给出求解方案前,先给出两个事实:
1)由于xi只能取0或1,因此若在方程中存在ki>M,那么其相应的xi必定为0,否则左边的和必定大于M。
2)对于方程k1x1+...+kpxp=M而言,若存在一组解,第i个值为1,则方程k1x1+...+k(i-1)x(i-1)+k(i+1)x(i+1)+...+kpxp=M-ki的任意一个解与xi=1构成原方程的解。
根据以上两个事实,可以构建如下算法:
0. 设置有效集为{k1, k2, ..., kp};
1. 从有效集中选择一个ki,将其相应的xi设置为1;
2. 计算M-ki,根据此值以及1)可以过滤掉部分必定为0的xj,得到新的有效集{ki1, ki2, ...};
3. 重复1-2步骤,直至M-ki为0,则得到一个有效解,或者M-ki<0,则这条路径不是有效解,退回重新构造解,直到所有可能性被搜索完。
根据以上算法可以知道,若解存在,则可以有效地找出解来,但是若解不存在,则依然需要遍历整个解空间,这是这个算法的最大问题,也是我还不清楚如何解决的一个地方。另外,此算法的关键地方在于2,即如何构建新的有效集。而我的思路是采取快速排序中分组!
根据以上算法,可以写出如下代码(JavaScript):
1 function getResult(array, start, end, target) { 2 var k = partion(array, start, end, target); 3 return _getResult(array, start, k, target); 4 } 5 6 function _getResult(array, start, end, target) { 7 if (target != 0 && end < start) return -2; // -2只是表明当前路径没找到有效解 8 if (target == 0) return -1; // 由于array[start..result]是保存解的地方,而target=0意味着xi全等于0,因此返回-1 9 for (var i = end; i >= start; --i) { 10 var remain = target - array[i]; 11 var k = partion(array, start, i - 1, remain); 12 var result = _getResult(array, start, k, remain); 13 if (result != -2) { 14 swap(array, ++result + start, i); 15 return result; 16 } 17 } 18 return -2; 19 } 20 21 function partion(array, start, end, target) { 22 for (var i = start - 1, j = start; j <= end; ++j) { 23 if (array[j] <= target) { 24 swap(array, ++i, j); 25 } 26 } 27 return i; 28 } 29 30 function swap(array, i, j) { 31 var temp = array[i]; 32 array[i] = array[j]; 33 array[j] = temp; 34 }
这里将数组array[start..result]作为解的存储地方,采取这种方式的原因是,1)不用另外构建数组用于存储结果;2)根据得到的结果result和array可以很方便的实现原问题中每个方块必须使用且只能使用一次的限制,且通过一次循环即可解决原方程组的问题。
测试的代码如下:
1 var a = new Array(1000); 2 var target = 2345; 3 (function() { 4 for (var i = 0; i < a.length; ++i) 5 a[i] = i + 1; 6 var result = getResult(a, 0, a.length - 1, target), sum = 0; 7 for (var i = 0; i <= result; ++i) sum += a[i]; 8 console.log("target: " + target + ", result: " + sum); 9 console.log("[" + a.slice(0, result + 1) + "], length: " + (result + 1) + ", iterations: " + n); 10 })();
结果如下:
target: 2345, result: 2345
[346,999,1000], length: 3, iterations: 4