GAの二点交叉で交叉点を選ぶ具体的な実装方法について

なんかぐぐっても具体的な実装の情報が出てこなかったので。あったら教えて。
以下は二点交叉の例(1と4で交叉)。2箇所適当に選んで遺伝子を入れ替える。

0 1 2 3 4 5
|a|a|a|a|a|    |a|b|b|b|a|
     x      -> 
|b|b|b|b|b|    |b|a|a|a|b|

問題となった点は以下の2つ。

  1. 二点交叉時に選ぶ交叉点に、一番外側(0と5)は含まれるか
  2. 二点交叉で二点を偏り無く選ぶ方法

1.はとりあえず含まないものとした(これも合ってるのか分からないけど)。
今回は2.について。以下に考えられる実装方法を挙げる。

  • 交叉点を1つ適当に選んだ後、それより右側から1つ適当に選ぶ
    • こういう間違い結構あるんじゃないかな。ちゃんと検証してないけど、この方法だと多分右側に交差点が偏る。
  • 考えられる交差点をリストに入れてシャッフルし、先頭から2つ取り出す
    • 時間無かったときに1回この方法で実装した。当然遅い。
  • 交叉点を1つ適当に選び、2つ目は重複しなくなるまで取り出すのを繰り返す
    • 気にならない程度だろうけど多少無駄がありそう。あとこれは乱数にも影響が出そうな気がする。
  • 今回実装した方法
    • 以下にJavaソースと説明を書く(この記事用に書いてるのでミスがあるかも)
/**
 * 指定された長さの遺伝子型に対して交叉点を2つ選び、昇順にして返す。
 *
 * @param genotypeLength 遺伝子型の長さ
 * @param random 乱数生成器
 * @return {左側の交叉点, 右側の交叉点}となっているint型配列
 */
public int[] getCrossoverPoints(int genotypeLength, Random random) {
    int less = random.nextInt(genotypeLength-1) + 1; // (1)
    int more = random.nextInt(genotypeLength-2) + 1; // (2)
    if (more < less) { // (3)
        int tmp = less;
        less = more;
        more = tmp;
    } else { // (4)
        ++more;
    }
    return new int[]{less, more};
}

交叉点の候補は、冒頭の例では1〜4となる。この4つの数値から偏り無く2つを選ぶ。

(1)について

RandomのnextInt(int)メソッドは「 0 <= x < 指定した数値」となるint値xを返すため、
遺伝子長より1つ少ない数値を引数として渡す。
冒頭の例で言う0〜3のint値が返るので、1加えて1〜4に変換してless(暫定的な左側の交叉点)へ代入。

(2)について

(1)よりも候補が1つ減るので、今度は1〜3に変換してmore(暫定的な右側の交叉点)へ代入。

(3)について

(1)、(2)の結果、moreの方がlessよりも小さかった場合、2つを入れ替える。

(4)について

(2)で得られた値と実際に欲しい値との対応付けを行っている。
(1)で得られた値と(2)で得られた値には以下2通りの場合が考えられる。

  1. (1)の値未満の場合→そのまま
  2. (1)の値以上の場合→1つ右側へずらす

例えば冒頭の例について、(1)で2が得られた場合、残りの候補1,3,4に対して、
(2)で得られる可能性がある値1,2,3はそれぞれ以下のように対応付けられる。

1 (2) 3 4
1     2 3

つまり、「(1)で得られた値<=(2)で得られた値」の場合に限り、(2)で得られた値をインクリメントすれば良い。
これは(3)とちょうど逆の条件になっているため、else文の中に入れられる。

終わり。