【问题标题】:Metaheuristic shuffle元启发式洗牌
【发布时间】:2025-12-07 11:20:07
【问题描述】:

我目前正在研究一个 NP 完全问题,并为此实施了个人遗传算法。结果超出了我的预期。凭借精心设计的适应度函数和精心调整的几个种群/突变,我猜 GA 在某些情况下可能是一个出色的工具。

无论如何,我现在正在寻找一种能够产生最佳改组输出的元启发式(GA,模拟退火...)。

我的意思是,在这种情况下,我的意思是有限集的无偏(à la Fisher-Yates)随机排列。就像卡组一样。一个巨大的(〜500!排列)。

这个集合的值都是不同的。预计不会发生碰撞。

由于这种限制,我在实施 GA 解决方案时遇到了一些困难。事实上,洗牌后的值不能用作基因。很容易看出原因:

#include <iostream>
#include <vector>
#define SPLICING 50 // 50|50 one-point crossover
int crossover(int gene, int DNA_length, int A, int B)
{if (gene < (SPLICING*DNA_length)/100) return A; else return B;}

int main() {
    std::vector<int> A, B, C;
    A = { 3, 4, 8, 12, 2, 0, 9, 7, 10, 20 };
    B = { 8, 10, 3, 4, 20, 0, 7, 9, 2, 12 };
    int DNA_length = int(A.size());
    for (int i=0; i<DNA_length; i++) {
        C.push_back(crossover(i, DNA_length, A[i], B[i]));
                    if (i == DNA_length/2) std::cout << "| ";
                    std::cout << C[i] << " ";}
            }

输出:3 4 8 12 2 | 0 7 9 2 12

有两次碰撞 (2, 12)。

我的预期输出是这样的:3 4 8 12 2 | 0 7 9 10 20(没有碰撞,原始集合的完美洗牌)。

然后,我需要对这些值的顺序进行编码以避免这种困难。

一种天真的方法是使用唯一键标识每个值。但随后创建的集合是一个序数,因为它指的是值的顺序。

我认为交叉函数必须处理父母 DNA 的序数。但我无法解决将序数集(整个 DNA)的两个非线性有序序数子集(父母的 DNA 切片)混合而不会发生碰撞的问题!

也许我只能依靠变异来收敛。没有选择,没有父母/孩子,只有同一集合中的交换功能(个人的 DNA)。简而言之:不是很有说服力。

在唯一的有限集中置换序数确实很容易(例如,平凡的:第一个变成第七个;第二个,第十个等等)。但是我不确定当集合 B 的第二个 变成新集合的第十个时,说集合 A 的第一个 变成第七个是否有意义。

那么,我的问题是:

在您看来,可以在遗传算法的上下文中使用交叉函数来打乱集合的序数吗?如果不是,您能否建议一种比蛮力、爬山技术或遗传算法更有效的元启发式方法?

谢谢。

【问题讨论】:

  • 染色体的交配通常会导致后代发生碰撞,从而产生许多不合适的解决方案。您是否考虑过操纵后代(可能在突变之前或之后)以确保解决方案中没有碰撞?也许碰撞基因可以根据具有原始位置的父母随机分配或连续分配。这不是 GA 的理想选择,但它可能有助于删除所有不合适的解决方案。另外,使用变异不是说洗牌时可以用其他牌替换牌组中的某些牌吗?
  • 确实有这种可能,但恐怕会大大增加随机性因素。想象一下,您将来自父母 1 [1, 4, 3, 2 |.然后你必须修改父 2 的 DNA 部分 | 2, 7, 5, 0] 以使孩子的 DNA 中的所有值都是唯一的 [1, 4, 3, 2 | 6*、7、5、0]。因此,该 DNA 的第二部分并不能真正反映父母 2 的 DNA 的品质。如果我们将其视为一种突变,那么这种突变就太重要了,在我的例子中,可能 > 整个 DNA 的 5%。所以比遗传选择更具随机性。无论如何感谢您的回答!

标签: c++ genetic-algorithm shuffle heuristics


【解决方案1】:

您正在寻找的是基于顺序的遗传算法。您有许多基于顺序的交叉和变异运算符,旨在解决此类问题。最简单的交叉算子工作原理如下:

  1. 选择交叉点
  2. 将交叉点内 parent1 的部分复制到第一个儿子。
  3. 列出parent1在交叉点之外的元素
  4. 将未使用列表的元素按照与 parent2 中使用的顺序相同的顺序放置
  5. 按照步骤 4 中建立的顺序将这些元素复制到第一个儿子。

你可以在下图中看到我书中的一个例子(对不起,描述是葡萄牙语 - 请与上面的列表相关联):

您可以在网络上搜索基于订单的运算符,或者如果您愿意,请查看我在My Geneetic Algorithm book 的书中的数字。你感兴趣的是第10章的那些(你可以用谷歌翻译来理解图例)。

您不必介意本书使用序列号 - 如果您没有重复,所有解释的概念都对您的问题有效。

希望对你有帮助。

【讨论】:

  • 非常感谢。我刚刚实施了这个解决方案(单点交叉),它给出了有希望的结果!我希望它对处于相同情况的人有用。
  • 值得一提的是:有不同的基于排列/顺序的交叉运算符,它们旨在继承不同的信息。假设你有一个像1 4 5 0 2 3 6 7 这样的人,那很好。它有什么好处?例如,是 1 在 4 旁边,无论它们出现在字符串中的哪个位置,还是 4 正好在第二个位置?您可以查看不同的运算符来保留好父母的不同属性,这会严重影响性能。
  • 这就是为什么基于 IMO 顺序的遗传算法很难调整的原因。我们可以看到两种集合ordinality:最优序数subsets(这个数字必须在这个之后三个位置),和一个最优全局序数(这个数字将在该组的第十位)。如果我们只关注前者,那么 n!变大,解更有可能是次优的;仅在后者上,并且不太可能在合理的时间内找到解决方案(因为亲本 2 的 DNA 的准随机化)。也许关键是混合这两种方法(序数的序数)。