permutationの実装例

調べてもまとまったものがないので。どちらも辞書順で並ぶ。C言語で実装する。

C++のnext_permutationのような実装

[1,2,4,3]の場合、2の後は逆順なので、2が繰り上がって3になる([1,3,*,*])。後は、*の部分の数字を昇順の数列で埋めれば良い。

ステップとしては、

  1. 後ろから見ていって、逆順の部分列でなくなる境界のkをメモる
  2. x>=arr[k]、つまり、lower boundの場所(i)を探す。Nが小さいので線形探索で実装した。
  3. 繰り上がりの処理。つまりi番目とk番目の要素をswap。0番目からk-1番目まではそのまま。k+1番目から以降は逆順で並んでいる(順列は数が重複してないので、2の方法で入れ替えても逆順の性質は保たれる) ので、これを昇順にreversedする。

reversedは計算量O(N)であんまり嬉しくないが、N自体が小さいので許容することにする。(どうしても許容できないときは双方向リストなどを使って、向きまで考慮するとO(1)でreversedが実装できるはず)

#ifndef PERM_SIZE
#define PERM_SIZE 30
#endif
void swap(int *a, int *b) {
    int tmp = *a;
    *a = *b;
    *b = tmp;
}

// assume that size is smaller..
void reversed(int *a, int size) {
    static int b[PERM_SIZE];
    memcpy(b, a, sizeof(int)*size);
    int i;
    for(i = 0; i < size; i++) {
        a[i] = b[size-1-i];
    }
}

// in-place(C++ like function)
// see also https://cpprefjp.github.io/reference/algorithm/next_permutation.html
int next_permutation(int *arr, int size) {
    int i, k;
    if(!size || size==1) return 0;
    for(k = size-2; k >= 0; k--) {
        if(arr[k+1] > arr[k]) break;
    }
    if(k < 0) {
        // reversed
        reversed(arr, size);
        return 0;
    }
    for(i = size-1; i >= 0; i--) {
        if(arr[i] < arr[k]) continue;
        break;
    }
    swap(&arr[i], &arr[k]);
    reversed(&arr[k+1], size-1-k);
    return 1;
}

例) https://atcoder.jp/contests/abc054/submissions/12291613

例えば、ある1~Nの昇順とは限らない順列があって、「この順列を起点にして、K番目の順列を出力しなさい」的な問題でピッタリ使えそう。

再帰関数を使う

p, usedをglobalとする。pを盤面を保存した配列とみなすとよい。usedは順列の数字を使ったかどうかを記録する(1度しか使えないので、要記録) if(pos == size)に入ったら、順列が生成された状態になっているので、所要の処理を施して、perm関数を抜ける。

蟻本の初級編のpermutationの実装例はこうなっている。

char used[VMAX];
int p[VMAX+1];
 
void perm(int *arr, int pos, int size, int *ans) {
    int i;
    if(pos == size) {
        int flag = 1;
#ifdef DEBUG
        for(i = 0; i < size; i++) {
            printf("%d ", arr[p[i]]);
        }
        putchar('\n');
#endif
        // do something and early return;
    }
    for(i = 0; i < size; i++) {
        if(used[i]) continue;
        used[i] = 1;
        p[pos] = i;
        perm(arr, pos+1, size, ans);
        used[i] = 0;
    }
    return;
}

関数ポインタを引数にしてもよいが、めんどくさいのでperm関数の中に直接処理を書いてみたのが例のリンク。

例) https://atcoder.jp/contests/abc054/submissions/10653617