交叉算子

在常见的交叉算子中,经常会有选取基因片段的操作,有的是连续选取,有的是不连续选取,对于连续选取的交叉算子,我使用的都是环形选择,具体操作是这样的:首先给定起点l和终点m,以及基因长度L,如果l<m,则选取l到m这段基因;如果l>m,则选取0到m和l到L这两段基因,这样可以保证每一“碱基对”被选到的概率是相等的。

交叉算子这里我主要参考的是: https://blog.csdn.net/u012750702/article/details/54563515

以下大部分内容都摘自该网站,当然也有一小部分我自己的理解。

Partial-Mapped Crossover(PMX)

这个交叉算子使用的是连续选取,具体过程是这样的: 第一步,随机选择一对染色体(父代)中几个基因的起止位置(两染色体被选位置相同):

第二步,交换这两组基因的位置:

第三步,做冲突检测,根据交换的两组基因建立一个映射关系,如图所示,以1-6-3这一映射关系为例,可以看到第二步结果中子代1存在两个基因1,这时将其通过映射关系转变为基因3,以此类推至没有冲突为止。最后所有冲突的基因都会经过映射,保证形成的新一对子代基因无冲突:

最终结果:

这个交叉算子最关键的就是第三部的冲突检测了,我采取的方法是先用一个字典来储存每个“碱基”对应的“碱基”,比如在上面例子中,我储存的字典为

对于这样的一个映射表,我想找出“1”对应的数字,我只需要循环嵌套判断,“1”对应的数字是“6”,“6”对应的数字是“3”,“3”对应的数字是“-1”,循环停止,所以我找到了“1”最终对应的数字是“3”,如此便实现了冲突检测。

/// <summary>
/// PMX交叉算子
/// </summary>
/// <param name="parent1">父类基因一</param>
/// <param name="parent2">父类基因二</param>
/// <returns>基因片段</returns>
public static List<int[]> PMX(int[] parent1, int[] parent2)
{
    List<int[]> ans = new List<int[]>();
    int[] a = new int[parent1.Length], ans1 = new int[parent1.Length], ans2 = new int[parent1.Length];
    int l, m, num;
    for (int i = 0; i < parent1.Length; i++)
    {
        a[i] = i;
    }

    a = a.OrderBy(c => Guid.NewGuid()).ToArray();
    l = a[0];
    m = a[1];
    Dictionary<int, int> dict1 = new Dictionary<int, int>(), dict2 = new Dictionary<int, int>();
    for (int i = 1; i <= parent1.Length; i++)
    {
        dict1[i] = -1;
        dict2[i] = -1;
    }

    if (l < m)
    {
        for (int i = l; i < m; i++)
        {
            ans1[i] = parent2[i];
            ans2[i] = parent1[i];
            dict1[parent2[i]] = parent1[i];
            dict2[parent1[i]] = parent2[i];
        }

        for (int i = 0; i < l; i++)
        {
            num = parent1[i];
            while (dict1[num] != -1)
            {
                num = dict1[num];
            }

            ans1[i] = num;

            num = parent2[i];
            while (dict2[num] != -1)
            {
                num = dict2[num];
            }

            ans2[i] = num;
        }

        for (int i = m; i < parent1.Length; i++)
        {
            num = parent1[i];
            while (dict1[num] != -1)
            {
                num = dict1[num];
            }

            ans1[i] = num;

            num = parent2[i];
            while (dict2[num] != -1)
            {
                num = dict2[num];
            }

            ans2[i] = num;
        }
    }
    else
    {
        for (int i = 0; i < m; i++)
        {
            ans1[i] = parent2[i];
            ans2[i] = parent1[i];
            dict1[parent2[i]] = parent1[i];
            dict2[parent1[i]] = parent2[i];
        }

        for (int i = l; i < parent1.Length; i++)
        {
            ans1[i] = parent2[i];
            ans2[i] = parent1[i];
            dict1[parent2[i]] = parent1[i];
            dict2[parent1[i]] = parent2[i];
        }

        for (int i = m; i < l; i++)
        {
            num = parent1[i];
            while (dict1[num] != -1)
            {
                num = dict1[num];
            }

            ans1[i] = num;

            num = parent2[i];
            while (dict2[num] != -1)
            {
                num = dict2[num];
            }

            ans2[i] = num;
        }
    }

    ans.Add(ans1);
    ans.Add(ans2);
    return ans;
}

Order Crossover (OX)

这个算子使用的也是连续选取,具体过程如下:

第一步,与PMX相同,随机选择一对染色体(父代)中几个基因的起止位置(两染色体被选位置相同):

第二步,生成一个子代,并保证子代中被选中的基因的位置与父代相同:

第三步(可再分两小步),先找出第一步选中的基因在另一个父代中的位置,再将其余基因按顺序放入上一步生成的子代中:

需要注意的是,这种算法同样会生成两个子代,另一个子代生成过程完全相同,只需要将两个父代染色体交换位置,第一步选中的基因型位置相同,本例中的另一个子代为:254913678

与PMX不同的是,不用进行冲突检测工作(实际上也只有PMX需要做冲突检测)。

/// <summary>
/// OX交叉算子
/// </summary>
/// <param name="parent1">父类基因一</param>
/// <param name="parent2">父类基因二</param>
/// <returns>基因片段</returns>
public static List<int[]> OX(int[] parent1, int[] parent2)
{
    List<int[]> ans = new List<int[]>();
    int[] a = new int[parent1.Length], ans1 = new int[parent1.Length], ans2 = new int[parent1.Length];
    int l, m;
    for (int i = 0; i < parent1.Length; i++)
    {
        a[i] = i;
    }

    a = a.OrderBy(c => Guid.NewGuid()).ToArray();
    l = a[0];
    m = a[1];

    if (l < m)
    {
        HashSet<int> set1 = new HashSet<int>(), set2 = new HashSet<int>();
        int j = 0, k = 0;
        for (int i = l; i < m; i++)
        {
            ans2[i] = parent2[i];
            set2.Add(parent2[i]);
            ans1[i] = parent1[i];
            set1.Add(parent1[i]);
        }

        for (int i = 0; i < l; i++)
        {
            while (set1.Contains(parent2[j]))
            {
                j++;
            }

            ans1[i] = parent2[j];
            j++;

            while (set2.Contains(parent1[k]))
            {
                k++;
            }

            ans2[i] = parent1[k];
            k++;
        }

        for (int i = m; i < parent1.Length; i++)
        {
            while (set1.Contains(parent2[j]))
            {
                j++;
            }

            ans1[i] = parent2[j];
            j++;

            while (set2.Contains(parent1[k]))
            {
                k++;
            }

            ans2[i] = parent1[k];
            k++;
        }
    }
    else
    {
        HashSet<int> set1 = new HashSet<int>(), set2 = new HashSet<int>();
        int j = 0, k = 0;
        for (int i = 0; i < m; i++)
        {
            ans2[i] = parent2[i];
            set2.Add(parent2[i]);
            ans1[i] = parent1[i];
            set1.Add(parent1[i]);
        }

        for (int i = l; i < parent1.Length; i++)
        {
            ans2[i] = parent2[i];
            set2.Add(parent2[i]);
            ans1[i] = parent1[i];
            set1.Add(parent1[i]);
        }

        for (int i = m; i < l; i++)
        {
            while (set1.Contains(parent2[j]))
            {
                j++;
            }

            ans1[i] = parent2[j];
            j++;

            while (set2.Contains(parent1[k]))
            {
                k++;
            }

            ans2[i] = parent1[k];
            k++;
        }
    }

    ans.Add(ans1);
    ans.Add(ans2);
    return ans;
}

Position-based Crossover (PBX)

这个算子是不连续选取的,具体过程如下:

第一步,随机选择一对染色体(父代)中几个基因,位置可不连续,但两染色体被选位置相同:

第二步,与OX的第二步相同,生成一个子代,并保证子代中被选中的基因的位置与父代相同:

第三步,也与OX的第三步相同,先找出第一步选中的基因在另一个父代中的位置,再将其余基因按顺序放入上一步生成的子代中:

与上俩个算法不同的是,选择的基因型位置可以不连续,出来这一点外与OX基本一致,同样有两个子代,本例的另一个为:243519678。

/// <summary>
/// PBX交叉算子
/// </summary>
/// <param name="parent1">父类基因一</param>
/// <param name="parent2">父类基因二</param>
/// <returns>基因片段</returns>
public static List<int[]> PBX(int[] parent1, int[] parent2)
{
    List<int[]> ans = new List<int[]>();
    int[] a = new int[parent1.Length], ans1 = new int[parent1.Length], ans2 = new int[parent1.Length];
    for (int i = 0; i < parent1.Length; i++)
    {
        a[i] = i;
    }

    a = a.OrderBy(c => Guid.NewGuid()).ToArray();
    int r = rand_int(0, parent1.Length);
    HashSet<int> set = new HashSet<int>(), set1 = new HashSet<int>(), set2 = new HashSet<int>();
    for (int i = 0; i < r; i++)
    {
        set.Add(a[i]);
    }


    foreach (var i in set)
    {
        ans2[i] = parent2[i];
        set2.Add(parent2[i]);
        ans1[i] = parent1[i];
        set1.Add(parent1[i]);
    }

    int j = 0, k = 0;
    for (int i = 0; i < parent1.Length; i++)
    {
        if (set.Contains(i))
        {
            continue;
        }

        while (set1.Contains(parent2[j]))
        {
            j++;
        }

        ans1[i] = parent2[j];
        j++;

        while (set2.Contains(parent1[k]))
        {
            k++;
        }

        ans2[i] = parent1[k];
        k++;
    }

    ans.Add(ans1);
    ans.Add(ans2);
    return ans;
}

Order-Based Crossover (OBX)

这个算子是不连续选取的,具体过程如下:

第一步,随机选择一对染色体(父代)中几个基因,位置可不连续,但两染色体被选位置相同:

第二步(分为两小步),先在父代2中找到父代1被选中基因的位置,再用父代2中其余的基因生成子代,并保证位置对应:

第三步,将父代1中被选择的基因按顺序放入子代剩余位置中:

同理,交换父代1、2可得到另一个子代(被选择基因的位置不变),本例结果为:423156798。

OBX与PBX相比生成子代的“基础”基因来源不同一个来自被选中基因,一个来自剩余的,此外思想基本相似。

/// <summary>
/// OBX交叉算子
/// </summary>
/// <param name="parent1">父类基因一</param>
/// <param name="parent2">父类基因二</param>
/// <returns>基因片段</returns>
public static List<int[]> OBX(int[] parent1, int[] parent2)
{
    List<int[]> ans = new List<int[]>();
    int[] a = new int[parent1.Length], ans1 = new int[parent1.Length], ans2 = new int[parent1.Length];
    int l, m;
    for (int i = 0; i < parent1.Length; i++)
    {
        a[i] = i;
    }

    a = a.OrderBy(c => Guid.NewGuid()).ToArray();
    int r = rand_int(0, parent1.Length);
    HashSet<int> set1 = new HashSet<int>(), set2 = new HashSet<int>();
    List<int> set = new List<int>();
    for (int i = 0; i < r; i++)
    {
        set.Add(a[i]);
    }


    foreach (var i in set)
    {
        set1.Add(parent1[i]);
        set2.Add(parent2[i]);
    }

    int j = 0, k = 0;
    for (int i = 0; i < parent2.Length; i++)
    {
        if (!set1.Contains(parent2[i]))
        {
            ans1[i] = parent2[i];
        }
        else
        {
            ans1[i] = parent1[set[j]];
            j++;
        }

        if (!set2.Contains(parent1[i]))
        {
            ans2[i] = parent1[i];
        }
        else
        {
            ans2[i] = parent2[set[k]];
            k++;
        }
    }

    ans.Add(ans1);
    ans.Add(ans2);
    return ans;
}

Cycle Crossover (CX)

这个算子具体过程如下:

第一步,在某个父代上随机选择1个基因,然后找到另一个父代相应位置上的基因编号,再回到第一个父代找到同编号的基因的位置,重复先前工作,直至形成一个环,环中的所有基因的位置即为最后选中的位置:

第二步,用父代1中选中的基因生成子代,并保证位置对应:

第三步,将父代2中剩余基因放入子代中:

与上述算法不同的是,仅需要在一个染色体上随机选择一个位置;本例另一个子代结果为:543926781。

这里的寻找闭环的过程其实和PMX中的冲突检测有点类似,也是循环嵌套判断罢了。

/// <summary>
/// CX交叉算子
/// </summary>
/// <param name="parent1">父类基因一</param>
/// <param name="parent2">父类基因二</param>
/// <returns>基因片段</returns>
public static List<int[]> CX(int[] parent1, int[] parent2)
{
    int r = rand_int(0, parent1.Length);
    List<int[]> ans = new List<int[]>();
    int[] ans1 = new int[parent1.Length], ans2 = new int[parent1.Length];
    Dictionary<int, int> dict = new Dictionary<int, int>();
    for (int i = 0; i < parent1.Length; i++)
    {
        dict[parent1[i]] = i;
    }

    Dictionary<int, int> a = new Dictionary<int, int>();
    HashSet<int> set = new HashSet<int>();
    a[parent1[r]] = parent2[r];
    int num = parent2[r];
    ans1[r] = parent1[r];
    ans2[r] = parent2[r];
    set.Add(dict[parent1[r]]);
    while (parent2[dict[num]] != parent1[r])
    {
        a[num] = parent2[dict[num]];
        ans1[dict[num]] = num;
        ans2[dict[num]] = parent2[dict[num]];
        set.Add(dict[num]);
        num = a[num];
    }

    set.Add(dict[num]);
    ans1[dict[num]] = num;
    ans2[dict[num]] = parent2[dict[num]];

    for (int i = 0; i < parent1.Length; i++)
    {
        if (!set.Contains(i))
        {
            ans1[i] = parent2[i];
            ans2[i] = parent1[i];
        }
    }

    ans.Add(ans1);
    ans.Add(ans2);
    return ans;
}

Subtour Exchange Crossover(SEC)

这个算子是连续选取的,具体过程如下:

第一步,在某个父代上选择1组基因,在另一父代上找到这些基因的位置:

第二步,保持未选中基因不变,按选中基因的出现顺序,交换两父代染色体中基因的位置,一次生成两个子代:

与上述算法不同的是,只在一个染色体上选择基因的位置。

/// <summary>
/// SEC交叉算子
/// </summary>
/// <param name="parent1">父类基因一</param>
/// <param name="parent2">父类基因二</param>
/// <returns>基因片段</returns>
public static List<int[]> SEC(int[] parent1, int[] parent2)
{
    List<int[]> ans = new List<int[]>();
    int[] a = new int[parent1.Length], ans1 = new int[parent1.Length], ans2 = new int[parent1.Length];
    int l, m, t;
    for (int i = 0; i < parent1.Length; i++)
    {
        a[i] = i;
        ans1[i] = parent1[i];
        ans2[i] = parent2[i];
    }

    a = a.OrderBy(c => Guid.NewGuid()).ToArray();
    l = a[0];
    m = a[1];


    Dictionary<int, int> dict = new Dictionary<int, int>();
    for (int i = 0; i < parent1.Length; i++)
    {
        dict[parent2[i]] = i;
    }

    List<int> index = new List<int>();

    if (l < m)
    {
        for (int i = l; i < m; i++)
        {
            index.Add(dict[parent1[i]]);
        }

        index.Sort();

        for (int i = 0; i < m - l; i++)
        {
            t = ans1[l + i];
            ans1[l + i] = ans2[index[i]];
            ans2[index[i]] = t;
        }
    }
    else
    {
        for (int i = 0; i < m; i++)
        {
            index.Add(dict[parent1[i]]);
        }

        for (int i = l; i < parent1.Length; i++)
        {
            index.Add(dict[parent1[i]]);
        }

        index.Sort();

        for (int i = 0; i < m; i++)
        {
            t = ans1[i];
            ans1[i] = ans2[index[i]];
            ans2[index[i]] = t;
        }

        for (int i = 0; i < parent1.Length - l; i++)
        {
            t = ans1[l + i];
            ans1[l + i] = ans2[index[m + i]];
            ans2[index[m + i]] = t;
        }
    }

    ans.Add(ans1);
    ans.Add(ans2);
    return ans;
}

其他交叉算子

以上算法都是针对int数组类型的基因的处理,而对于bool数组类型的基因,处理起来就很简单了,选取基因片段进行交换就行了,同样道理可以连续选取也可以不连续选取。

/// <summary>
/// OX交叉算子
/// </summary>
/// <param name="parent1">父类基因一</param>
/// <param name="parent2">父类基因二</param>
/// <returns>基因片段</returns>
public static List<bool[]> OX(bool[] parent1, bool[] parent2)
{
    List<bool[]> ans = new List<bool[]>();
    int[] a = new int[parent1.Length];
    bool[] ans1 = new bool[parent1.Length], ans2 = new bool[parent2.Length];
    int l, m;
    for (int i = 0; i < parent1.Length; i++)
    {
        a[i] = i;
    }

    a = a.OrderBy(c => Guid.NewGuid()).ToArray();
    l = a[0];
    m = a[1];

    if (l < m)
    {
        for (int i = l; i < m; i++)
        {
            ans1[i] = parent2[i];
            ans2[i] = parent1[i];
        }

        for (int i = 0; i < l; i++)
        {
            ans1[i] = parent1[i];
            ans2[i] = parent2[i];
        }

        for (int i = m; i < parent1.Length; i++)
        {
            ans1[i] = parent1[i];
            ans2[i] = parent2[i];
        }
    }
    else
    {
        for (int i = 0; i < m; i++)
        {
            ans1[i] = parent2[i];
            ans2[i] = parent1[i];
        }

        for (int i = l; i < parent1.Length; i++)
        {
            ans1[i] = parent2[i];
            ans2[i] = parent1[i];
        }

        for (int i = m; i < l; i++)
        {
            ans1[i] = parent1[i];
            ans2[i] = parent2[i];
        }
    }

    ans.Add(ans1);
    ans.Add(ans2);
    return ans;
}

/// <summary>
/// PBX交叉算子
/// </summary>
/// <param name="parent1">父类基因一</param>
/// <param name="parent2">父类基因二</param>
/// <returns>基因片段</returns>
public static List<bool[]> PBX(bool[] parent1, bool[] parent2)
{
    List<bool[]> ans = new List<bool[]>();
    int[] a = new int[parent1.Length];
    bool[] ans1 = new bool[parent1.Length], ans2 = new bool[parent2.Length];
    for (int i = 0; i < parent1.Length; i++)
    {
        a[i] = i;
    }

    a = a.OrderBy(c => Guid.NewGuid()).ToArray();
    int r = rand_int(0, parent1.Length);
    HashSet<int> set = new HashSet<int>(), set1 = new HashSet<int>(), set2 = new HashSet<int>();
    for (int i = 0; i < r; i++)
    {
        set.Add(a[i]);
    }

    for (int i = 0; i < parent1.Length; i++)
    {
        if (set.Contains(i))
        {
            ans1[i] = parent2[i];
            ans2[i] = parent1[i];
        }
        else
        {
            ans1[i] = parent1[i];
            ans2[i] = parent2[i];
        }
    }

    ans.Add(ans1);
    ans.Add(ans2);
    return ans;
}

变异算子

变异操作相对来说就简单很多了,对于int数组类型的基因只需要选取基因片段(连续or不连续)进行重新排列,对于bool数组类型的基因只需要选取基因片段(连续or不连续)进行取反操作即可。

/// <summary>
/// OX变异算子
/// </summary>
/// <param name="gene">基因片段</param>
/// <returns>基因片段</returns>
public static int[] Variation_OX(int[] gene)
{
    int[] a = new int[gene.Length], ans = new int[gene.Length];
    int l, m;
    for (int i = 0; i < gene.Length; i++)
    {
        a[i] = i;
    }

    a = a.OrderBy(c => Guid.NewGuid()).ToArray();
    l = a[0];
    m = a[1];

    List<int> b = new List<int>();
    if (l < m)
    {
        for (int i = l; i < m; i++)
        {
            b.Add(gene[i]);
        }

        b = b.OrderBy(c => Guid.NewGuid()).ToList();
        for (int i = 0; i < l; i++)
        {
            ans[i] = gene[i];
        }

        for (int i = 0; i < m - l; i++)
        {
            ans[l + i] = b[i];
        }

        for (int i = m; i < gene.Length; i++)
        {
            ans[i] = gene[i];
        }
    }
    else
    {
        for (int i = 0; i < m; i++)
        {
            b.Add(gene[i]);
        }

        for (int i = l; i < gene.Length; i++)
        {
            b.Add(gene[i]);
        }

        b = b.OrderBy(c => Guid.NewGuid()).ToList();
        for (int i = 0; i < m; i++)
        {
            ans[i] = b[i];
        }

        for (int i = m; i < l; i++)
        {
            ans[i] = gene[i];
        }

        for (int i = 0; i < gene.Length - l; i++)
        {
            ans[i + l] = b[m + i];
        }
    }

    return ans;
}

/// <summary>
/// OX变异算子
/// </summary>
/// <param name="gene">基因片段</param>
/// <returns>基因片段</returns>
public static bool[] Variation_OX(bool[] gene)
{
    int[] a = new int[gene.Length];
    int l, m;
    bool[] ans = new bool[gene.Length];
    for (int i = 0; i < gene.Length; i++)
    {
        a[i] = i;
        ans[i] = gene[i];
    }

    a = a.OrderBy(c => Guid.NewGuid()).ToArray();
    l = a[0];
    m = a[1];

    if (l < m)
    {
        for (int i = l; i < m; i++)
        {
            ans[i] = !ans[i];
        }
    }
    else
    {
        for (int i = 0; i < m; i++)
        {
            ans[i] = !ans[i];
        }

        for (int i = l; i < gene.Length; i++)
        {
            ans[i] = !ans[i];
        }
    }

    return ans;
}

/// <summary>
/// PBX变异算子
/// </summary>
/// <param name="gene">基因片段</param>
/// <returns>基因片段</returns>
public static int[] Variation_PBX(int[] gene)
{
    int[] a = new int[gene.Length], ans = new int[gene.Length];
    for (int i = 0; i < gene.Length; i++)
    {
        a[i] = i;
    }

    a = a.OrderBy(c => Guid.NewGuid()).ToArray();
    int r = rand_int(0, gene.Length);
    HashSet<int> set = new HashSet<int>();
    for (int i = 0; i < r; i++)
    {
        set.Add(a[i]);
    }

    List<int> b = new List<int>();
    for (int i = 0; i < gene.Length; i++)
    {
        if (set.Contains(i))
        {
            b.Add(gene[i]);
        }
    }

    b = b.OrderBy(c => Guid.NewGuid()).ToList();
    int num = 0;
    for (int i = 0; i < gene.Length; i++)
    {
        if (set.Contains(i))
        {
            ans[i] = b[num];
            num++;
        }
        else
        {
            ans[i] = gene[i];
        }
    }

    return ans;
}

/// <summary>
/// PBX变异算子
/// </summary>
/// <param name="gene">基因片段</param>
/// <returns>基因片段</returns>
public static bool[] Variation_PBX(bool[] gene)
{
    int[] a = new int[gene.Length];
    bool[] ans = new bool[gene.Length];
    for (int i = 0; i < gene.Length; i++)
    {
        a[i] = i;
    }

    a = a.OrderBy(c => Guid.NewGuid()).ToArray();
    int r = rand_int(0, gene.Length);
    HashSet<int> set = new HashSet<int>();
    for (int i = 0; i < r; i++)
    {
        set.Add(a[i]);
    }

    for (int i = 0; i < gene.Length; i++)
    {
        if (set.Contains(i))
        {
            ans[i] = !gene[i];
        }
        else
        {
            ans[i] = gene[i];
        }
    }


    return ans;
}

种群类的代码就是这样啦!接下来就是简短但又核心的遗传算法了!