NP问题
了解启发式算法之前,我们需要先了解下启发式算法所使用的场景,启发式算法主要的使用场景是NP问题,例如一维装箱,旅行商问题。这些问题有一个显著的特征:获得准确解需要付出很大的代价,而启发式算法则是利用有限的资源,尽可能的去计算较优解。
解决NP问题的算法
以一维装箱为例,传统的NP问题解法有:
- 穷举法:穷尽法需要搜索 n! 次,这么搞cpu吃不消;
- 动态规划:搜索次数是能减少,但是一旦输入的参数较大,创建DP所需的数组可能直接OOM了;
- 贪心算法:贪心算法的确能获得一个较好的结构,常见的贪心算法有:
- First-fit:遍历当前箱子,把物品放入第一个能放下该物品的箱子,都放不下则新开一个箱子。
- Best-fit:对每个物品遍历当前箱子,把物品放入最合适放下该物品的箱子。
- Worst-fit:对每个物品遍历当前箱子,把物品放入最不合适放下该物品的箱子。
贪心算法虽然能获取到一个比较好的解,但是上述提到的贪心算法比较依赖于输入顺序,举个栗子,有一堆物品 {14,6,14,6,4,6,6,4},箱子的总容量为30,使用First-fit解得的结果为:{14,6,6,4},{14,6,6,4}。
如果改变下输入顺序,排个序变成{4,4,6,6,6,6,14,14},结果:{4,4,6,6,6},{6,6,6},{14,14}
启发式算法的诞生
既然得到最优解需要耗费很多资源,那退而求其次,获取次优解或者以一定概率获取最优解也是能接受的。那么随机改变数组的输入顺序,就有那么一丝希望能获取到最优解或者次优解。
启发式算法可以这样定义:一个基于直观或经验构造的算法,在可接受的花费(指计算时间和空间)下给出待解决组合优化问题每一个实例的一个可行解,该可行解与最优解的偏离程度一般不能被预计。
第一个版本的启发式算法
第一个版本的启发式算法,乱数搜索,只接受比当前更好的解
private void getMinBoxes(int[] bins) {
// 利用贪心算法计算结果
int best = fitness.getMinimumBins(bins);
for (int i = 0; i < 1000; i++) {
int positionA = random.nextInt(length);
int positionB = random.nextInt(length);
int[] temp = Arrays.copyOf(bins, bins.length);
swap(temp, positionA, positionB);
int cur = fitness.getMinimumBins(temp);
if (cur <= best) {
bins = temp;
best = cur;
}
}
}
这个最初版本的启发式算法有很多的不足,我们只一味的接受更好的解,而有些时候往往会产生一些局限性,导致最优解无法被找到。
更好的启发式算法
现在诞生的启发式算法都是由第一个版本的启发式算法改良得到的,可以参考后续的文章: