順列と置換
概要
順列 (permutation) には次の 2 つの意味がある。
複数の要素を直線的な順序で配置した状態
個の異なる要素からなる集合 は 通りの順列を構成することができる。例えば 3 個の要素からなる集合 は , , , , , の 通りの順列を生成できる。 順序付けられた集合の順序を並び替える操作
状態としての順列と意図的に区別するためしばしば置換と呼ばれる。
個の要素の並び を に変換する置換を と表す。例えば を に変換する置換は と表すことができる。
順序の定義された集合
任意の
順列における辞書式順序 (lexicographical order) は、順列を構成する要素の大小に基づいて順列の大小を決定し、辞書の単語順と同様の昇順での並びである。辞書式順序は美的ではあるがそれを使うのに特に利点が無いことが多い [1]。
Table of Contents
生成アルゴリズム
順列の生成アルゴリズムは、ある集合に対して可能なすべての並べ替えを効率的に列挙する手法である。順列を生成するアルゴリズムは組み合わせ最適化や経路探索、暗号など多くの応用分野で重要な役割を果たす。
逐次更新法
次順列アルゴリズム (next permutation algorithm) は、ある順列を指定されたときに辞書式順序で次に相当する順列を生成するアルゴリズムである。C 言語の std::next_permutation() は次順列アルゴリズムを使って与えられた順列の辞書順での次の順列を生成する。次順序アルゴリズムを使ってある集合のすべての順列を生成する方法は次のようになる。
最初の順列は集合の各要素を昇順にソートした状態から開始する。
次の順列を取得する。
現在の状態の最右端から順に隣り合う 2 つの要素を参照し、最初に降順になっていない位置
を見つける。すべての隣り合う 2 要素が降順になっている場合、現在の状態は辞書順で最後の状態であることを意味する。 再右端から順に、位置
の要素より大きい要素の位置 を探索して と の要素と入れ替える。 位置
以降の要素を逆順に並べる。
例えば初期状態で
の順列は、最初のイテレーションで右端から見て の並びが降順になっていないため の位置 が見つかり、右端から見て より大きい と入れ替えて を生成する。2 回目のイテレーションでは右端から見て降順でない の位置 が見つかり右端から より大きい と入れ替えて 以降を逆順に並べた を生成する。 上記 2. を繰り返してすべての順列を生成する。
function nextPermutation(p) {
if (p.length < 2) {
return false;
}
// 1. 右から左に走査し、最初に p[k] < p[k+1] となる位置 k を探す。すべての要素が p[k] < p[k+1] であれば辞書順で最後の順列。
let k = p.length - 2;
while (p[k] >= p[k + 1]) {
if (k === 0) {
return false;
}
k--;
}
// 2. k 以降で右から左に操作し、最小の p[j] > p[k] となる位置 j を探して p[k] と p[j] を入れ替える。
let j = p.length - 1;
while (j >= 0 && p[j] <= p[k]) {
j--;
}
[p[k], p[j]] = [p[j], p[k]];
// 3. k+1 以降を逆順にして次の順列にする。
let left = k + 1;
let right = p.length - 1;
while (left < right) {
[p[left], p[right]] = [p[right], p[left]];
left++;
right--;
}
return true;
}
このアルゴリズムによる各順列の生成にかかる計算量は
ランクとアンランク
ランク (rank) とは順列のそれぞれに割り当てられた
例として、辞書式順序での並びの位置をランクとし、与えられた順列と一致するまで 0 から next_permutaion を繰り返して数え上げるランク関数が考えられる。ただしこれは一回の算出に
順列において、ある位置
逆転ベクトルは対応する順列を一意に決定することができる [3]。したがって逆転ベクトルを
ランク
逆転の発生数を単純に数える方法では、逆転ベクトルの計算量は
以下は [2] で紹介されている
// 階乗の事前計算 (n≦18)
const FACTORIALS = ((max) => {
let factorial = 1;
const result = [factorial]; // 0! = 1
for (let n = 1; /* */ ; n++) {
factorial *= n;
if (factorial > max) {
break;
}
result.push(factorial);
}
return result;
})(Number.MAX_SAFE_INTEGER);
// 順列のランクを基に元の順列を復元する関数
function unrank(r, n) {
const p = Array.from({
length: n
}, (_, i) => i); // 恒等順列 [0, 1, ..., n-1]
for ( /* */ ; n > 0; n--) {
const s = Math.floor(r / FACTORIALS[n - 1]);
[p[n - 1], p[s]] = [p[s], p[n - 1]];
r %= FACTORIALS[n - 1];
}
return p;
}
// 順列のランクを計算する関数 (p は書き換えられる)
function rank(p) {
const pinv = new Array(p.length); // 順列 p から逆順列 pinv を生成
for (let i = 0; i < p.length; i++) {
pinv[p[i]] = i;
}
let r = 0;
for (let n = p.length; n > 1; n--) {
const s = p[n - 1];
[p[n - 1], p[pinv[n - 1]]] = [p[pinv[n - 1]], p[n - 1]];
[pinv[s], pinv[n - 1]] = [pinv[n - 1], pinv[s]];
r += s * FACTORIALS[n - 1];
}
return r;
}
console.log(rank([0, 2, 1, 3]));
console.log(unrank(21, 4));
実行例
参考文献
- Steven S. Skiana. アルゴリズム設計マニュアル 下. 丸善出版 (2024)
- MYRVOLD, Wendy; RUSKEY, Frank. Ranking and unranking permutations in linear time. Information Processing Letters, 2001, 79.6: 281-284.
- KNUTH, Donald E. The Art of Computer Programming, Volume 3: Searching and Sorting. Reading MA: Addison-Wisley, 1973, 543-583.