分割統治法で逆序数を求める
事前知識:マージして並べ替えます。
マージソートの正しさを証明しました。マージソートアルゴリズムがわずかに変更されている限り、順列の逆数を見つける時間の複雑さをO(n ^ 2)からO(nlogn)に減らすことができます。
配置PをP_1とP_2の2つの部分にできるだけ均等に分割します。次に、Pの
逆シーケンス番号τ§=τ(P_1)+τ(P_2)+τ_Merge
明らかに、τ(P_1)とτ(P_2)はそれぞれ、再帰的に解かれるP_1とP_2の範囲の逆シーケンス番号を表します。そして、τ_Mergeはすべての見逃されたケースをカバーします。つまり、逆順序対(a_i、a_j)を形成する2つの要素a_i、a_j、i <j、a_i> a_jはa_i∈P_1、a_j∈P_2を満たします。
マージとソートのプロセスでは、2つの順序付けられた(昇順の)シーケンスをマージするステップは次のとおり
です。マージするための一時シーケンスtを設定します。
ポインタiとjは、それぞれ2つのサブ配列の先頭を指します。サブシーケンスの範囲は、_beginから_mid、_mid +1から_endです。
比較のために、2つのサブ配列のそれぞれから要素(つまり、ポインターiとjが指す要素)を常に取得し、小さい方の数値を配列tに入れ、対応するポインターをインクリメントします(2つの要素が等しい場合、アドレスはデフォルトで比較されます。下の配列の数値、つまりiが指す数値はtに入れられます。サブシーケンスの1つが取り出されると、サイクルは終了します。
要素がシーケンスtに取り込まれていないサブシーケンスのすべての要素をコピーします。
長さmとnの序数P_1とP_2の2つのシーケンスから取り出された2つの数が、それぞれx_i、y_jであり、下付き文字が序数のシーケンス内の位置を表すとします。各比較でx_i> y_jが見つかり、シーケンスが昇順であることがわかっている場合、
x_k> y_j、k = i、i + 1、...、m、x_k∈P_1、y_j∈P_2
はmを意味します-i +1ペアが逆の順序で見つかります。つまり、τ_Merge= m-i +1です。
コード:
template<class _Ty> size_t merge(_Ty* _begin, _Ty* _mid, _Ty* _end) {
const auto l = _end - _begin + 1; size_t tau = 0;
_Ty* t = new _Ty[l], * i = _begin, * j = _mid + 1, * k = t;
while (i <= _mid && j <= _end) {
if (*i <= *j) {
*k = *i; ++i; ++k; }
else {
*k = *j; ++j; ++k; tau += _mid - i + 1; }
}
while (i <= _mid) {
*k = *i; ++i; ++k; }
while (j <= _end) {
*k = *j; ++j; ++k; }
std::copy(t, t + l, _begin);
delete[] t;
return tau;
}
template<class _Ty> size_t inversion_count(_Ty* _begin, _Ty* _end) {
size_t tau = 0;
if (_begin < _end) {
_Ty* _mid = _begin + (_end - _begin) / 2;
tau += inversion_count(_begin, _mid);
tau += inversion_count(_mid + 1, _end);
tau += merge(_begin, _mid, _end);
}
return tau;
}