配列の反転をカウントする


108

指定された配列:私は、次の手順を実行するアルゴリズムを設計していますA[1... n]、すべてのためにi < j、そのすべての反転ペアは、このような見つけますA[i] > A[j]。マージソートを使用して配列Aを配列Bにコピーしてから2つの配列を比較していますが、これを使用して反転の数を見つける方法を確認するのに苦労しています。ヒントやヘルプをいただければ幸いです。

回答:


139

だからここにJavaのO(n log n)ソリューションがあります。

long merge(int[] arr, int[] left, int[] right) {
    int i = 0, j = 0, count = 0;
    while (i < left.length || j < right.length) {
        if (i == left.length) {
            arr[i+j] = right[j];
            j++;
        } else if (j == right.length) {
            arr[i+j] = left[i];
            i++;
        } else if (left[i] <= right[j]) {
            arr[i+j] = left[i];
            i++;                
        } else {
            arr[i+j] = right[j];
            count += left.length-i;
            j++;
        }
    }
    return count;
}

long invCount(int[] arr) {
    if (arr.length < 2)
        return 0;

    int m = (arr.length + 1) / 2;
    int left[] = Arrays.copyOfRange(arr, 0, m);
    int right[] = Arrays.copyOfRange(arr, m, arr.length);

    return invCount(left) + invCount(right) + merge(arr, left, right);
}

これはほぼ通常のマージソートであり、マジック全体がマージ機能に隠されています。並べ替えアルゴリズムでは反転が削除されることに注意してください。マージアルゴリズムは、削除された反転の数をカウントしますが(整理すると、言うかもしれません)。

反転が削除されるのは、アルゴリズムが配列の右側から要素を取得してメイン配列にマージするときだけです。この操作によって削除される反転の数は、マージされる左側の配列から残っている要素の数です。:)

説明が十分であることを願っています。


2
これを実行してみましたが、正しい答えが得られませんでした。開始するには、main内でinvCount(intArray)を呼び出す必要がありますか?intArrayはintのソートされていない配列ですか?多くの整数の配列を使用して実行し、私の答えとして-1887062008を取得しました。何が悪いのですか?
2013

4
+ 1、C ++ 11の同様のソリューションを参照してください。これには、一般的なイテレーターベースのソリューションと、5〜25要素のシーケンスを使用したサンプルのランダムテストベッドが含まれます。楽しい!。
WhozCraig 14

3
これは解決策ではありません。実行してみましたが、結果が正しくありません。
ミルジー2014

2
初心者の質問には申し訳ありませんleft.length - iが、反転カウンターに追加するとどうなりますか?2つのサブ配列の比較で、左の配列要素が右の配列要素よりも大きいという論理的なケースに陥ったため、1を追加するだけでも理にかなっていると思います。誰でも私に5だと説明してもらえますか?
Alfredo Gallegos

2
@AlfredoGallegos、マレクの答えの簡単なイラスト。[6、8]と[4、5]の2つの配列を考えます。6が4より大きいことがわかったら、4を取り、それをに配置しarrます。しかし、これは1つの反転ではありません。左の配列で6より大きいすべての要素の反転が見つかりました。この場合、8も含まれています。したがって、2がに追加さcountleft.length - iます。これはと同じです。
イリヤ

86

次の方法でO(n * log n)時間で見つけました。

  1. ソート配列Aをマージしてコピーを作成(配列B)
  2. A [1]を取得し、バイナリ検索を介して、ソートされた配列Bでその位置を見つけます。この要素の反転の数は、Aの最初の要素の後に現れるすべての小さい数が反転になるため、Bにおけるその位置のインデックス番号よりも1つ少なくなります。

    2a。変数num_inversionsをカウンターするために反転の数を累積します。

    2b。配列Aおよび配列Bの対応する位置からA [1]を削除します

  3. Aに要素がなくなるまで、ステップ2から再実行します。

このアルゴリズムの実行例を次に示します。元の配列A =(6、9、1、14、8、12、3、2)

1:ソートをマージして配列Bにコピー

B =(1、2、3、6、8、9、12、14)

2:A [1]とバイナリ検索を使用して、配列Bでそれを見つけます。

A [1] = 6

B =(1、2、3、6、8、9、12、14)

6は配列Bの4番目の位置にあるため、3つの反転があります。これは、6が配列Aの最初の位置にあるため、わかっています。したがって、その後配列Aに現れるより低い値の要素のインデックスはj> iになります(この場合、iは1であるため)。

2.b:配列Aおよび配列Bの対応する位置からA [1]を削除します(太字の要素は削除されます)。

A =(6、9、1、14、8、12、3、2)=(9、1、14、8、12、3、2)

B =(1、2、3、6、 8、9、12、14)=(1、2、3、8、9、12、14)

3:新しいAおよびBアレイで手順2から再実行します。

A [1] = 9

B =(1、2、3、8、9、12、14)

9は配列Bの5番目の位置にあるため、4つの反転があります。9が配列Aの最初の位置にあったため、これがわかります。したがって、その後に表示されるより低い値の要素は、j> iのインデックスを持ちます(この場合もiが1であるため)。配列Aおよび配列Bの対応する位置からA [1]を削除します(太字の要素は削除されます)

A =(9、1、14、8、12、3、2)=(1、14、8、12、3、2)

B =(1、2、3、8、9、12、14)=(1、2、3、8、12、14)

この流れを続けると、ループが完了すると、配列Aの反転の総数がわかります。

ステップ1(ソートのマージ)を実行するには、O(n * log n)が必要です。ステップ2はn回実行され、実行ごとにO(log n)を実行するバイナリ検索が実行され、合計でO(n * log n)になります。したがって、合計実行時間はO(n * log n)+ O(n * log n)= O(n * log n)になります。

ご協力いただきありがとうございます。一枚の紙にサンプル配列を書き出すことは、問題を視覚化するのに本当に役立ちました。


1
なぜクイックソートではなくマージソートを使用するのですか?
Alcott、2011年

5
@Alcottクイックソートは、リストがすでにソートされており、ラウンドごとに最初のピボットが選択されている場合、O(n ^ 2)の実行時間が最悪です。マージソートの最悪のケースはO(n log n)です
user482594

29
標準配列からの削除ステップでは、値をシフトするため、アルゴリズムはO(n ^ 2)になります。(それが挿入ソートがO(n ^ 2)である理由です)
カイルバット

配列Bの最初の要素から開始し、配列Aでその前の要素をカウントしても、回答で説明したようにそれらを削除した場合、同じ結果が得られます。
tutak 2013年

@el diablo n ^ 2の複雑さを回避するために要素を削除する方法??
2014

26

Pythonで

# O(n log n)

def count_inversion(lst):
    return merge_count_inversion(lst)[1]

def merge_count_inversion(lst):
    if len(lst) <= 1:
        return lst, 0
    middle = int( len(lst) / 2 )
    left, a = merge_count_inversion(lst[:middle])
    right, b = merge_count_inversion(lst[middle:])
    result, c = merge_count_split_inversion(left, right)
    return result, (a + b + c)

def merge_count_split_inversion(left, right):
    result = []
    count = 0
    i, j = 0, 0
    left_len = len(left)
    while i < left_len and j < len(right):
        if left[i] <= right[j]:
            result.append(left[i])
            i += 1
        else:
            result.append(right[j])
            count += left_len - i
            j += 1
    result += left[i:]
    result += right[j:]
    return result, count        


#test code
input_array_1 = []  #0
input_array_2 = [1] #0
input_array_3 = [1, 5]  #0
input_array_4 = [4, 1] #1
input_array_5 = [4, 1, 2, 3, 9] #3
input_array_6 = [4, 1, 3, 2, 9, 5]  #5
input_array_7 = [4, 1, 3, 2, 9, 1]  #8

print count_inversion(input_array_1)
print count_inversion(input_array_2)
print count_inversion(input_array_3)
print count_inversion(input_array_4)
print count_inversion(input_array_5)
print count_inversion(input_array_6)
print count_inversion(input_array_7)

13
これがなんと+13に達したことに困惑しています。私は特にPythonに熟練していませんが2年前提示さたJavaバージョンとほとんど同じように見えますが、これには説明がまったくありません。他のすべての言語で回答を投稿することは、IMOに有害です-おそらく何千もの言語が存在します-質問に何千もの回答を投稿する必要があると誰もが主張しないことを願っています-Stack Exchangeはそのために作られていません。
Bernhard Barker 14

1
@tennenrishinわかりました。たぶん数千ではありません。しかし、どこに線を引くのでしょうか。現在、私が数えているように、すでに同じアプローチをしている10の答えがあります。これは回答の約43%(非回答を除く)です。ここで紹介する他のアプローチが6ダースあることを考えると、かなりのスペースを取る必要があります。同じアプローチの回答が2つしかない場合でも、回答が不必要に希釈されます。そして、私はこの回答についてかなり前向きな議論をしましたが、私の以前のコメントでは特に役に立ちませんでした。
Bernhard Barker

3
@Dukelingあなたと同じように、私はPythonに不慣れで、Javaに精通しています。この解決策は、Javaの解決策よりもはるかに読みにくくなっています。そのため、一部の人々にとっては、その逆が同じ程度に当てはまる可能性があるというのは当然のことです。
ミューズフル2014年

3
大多数のユーザーにとって、Pythonはsudoコードに近いです。正直なところ、説明はありませんが、Javaよりもはるかに読みやすくなっています。それが一部の読者を助けるならば、私はそれほどイライラする必要はないと思います。
Francisco Vargas

2
このソリューションは、Pythonユーザーにとって完全に細かく、読みやすいものです。人々は、他の人がこれをどのようにPythonで実装したかを知りたいと思っています。
Aerin 2017年

24

なぜバイナリ索引ツリーについてまだ誰も言及していないのでしょうか。1つを使用して、順列要素の値のプレフィックス合計を維持できます。次に、右から左に進み、すべての要素について、それよりも小さい要素の数を右に数えます。

def count_inversions(a):
  res = 0
  counts = [0]*(len(a)+1)
  rank = { v : i+1 for i, v in enumerate(sorted(a)) }
  for x in reversed(a):
    i = rank[x] - 1
    while i:
      res += counts[i]
      i -= i & -i
    i = rank[x]
    while i <= len(a):
      counts[i] += 1
      i += i & -i
  return res

複雑度はO(n log n)で、定数係数は非常に低くなります。


おそらく最良のアプローチ:)
Nilutpal Borgohain '29年

@NilutpalBorgohainありがとう:)少なくともO(n log n)候補の中で最も少ないコードが必要なようです。
Niklas B.

1
これをありがとう。i -= i & -iラインの意味は何ですか?そして同様にi += i & -i
ジェラールコンドン

1
@GerardCondonは基本的にBITデータ構造です。それを説明するリンクは答えにあります
Niklas B.

フェンウィックの木についてのTIL。ありがとう!この質問に対するすべてのPythonの回答を比較する回答を投稿しtimeitので、コードが含まれています。タイミング結果を確認することに興味があるかもしれません。
PM 2Ring 2017

14

宿題については、これと似たような質問がありました。O(nlogn)の効率が必要であると制限されていました。

Mergesortを使用するというあなたが提案したアイデアを使用しました。それはすでに正しい効率であるからです。基本的には、マージ関数にいくつかのコードを挿入しました。右側の配列の数値が出力配列に追加されるたびに、反転の総数に、左側の配列に残っている数値の量を追加します。

これは私が十分に考えた今、私には多くの意味があります。数字の前に、より大きな数字がある回数を数えます。

hth。


6
第二右の配列の要素が第一の左列に残っている要素の数だけ=>インクリメント反転カウンタ出力配列にコピーされるとき、私はあなたの答えをサポートし、マージソートの本質的な違いは、マージ機能である
Alex.Salnikov

10

この回答の主な目的は、ここにあるさまざまなPythonバージョンの速度を比較することですが、私自身もいくつか貢献しています。(FWIW、私は重複検索を実行中にこの質問を発見しました)。

CPythonで実装されたアルゴリズムの相対的な実行速度は、アルゴリズムの単純な分析や他の言語での経験から期待されるものとは異なる場合があります。これは、PythonがCで実装された多くの強力な関数とメソッドを提供し、完全にコンパイルされた言語で得られる速度に近い速度でリストや他のコレクションを操作できるため、これらの操作は、Pythonで「手動で」実装された同等のアルゴリズムよりもはるかに高速に実行されるためです。コード。

これらのツールを利用するコードは、多くの場合、コレクションの個々のアイテムに対するPython操作ですべてを実行しようとする理論的に優れたアルゴリズムよりも優れています。もちろん、処理されるデータの実際の量もこれに影響を与えます。ただし、中程度の量のデータの場合、C速度で実行されるO(n²)アルゴリズムを使用するコードは、個々のPython操作でその処理の大部分を実行するO(n log n)アルゴリズムを簡単に打ち負かすことができます。

この反転カウントの質問に対する投稿された回答の多くは、mergesortに基づくアルゴリズムを使用しています。理論的には、配列のサイズが非常に小さい場合を除いて、これは良い方法です。しかし、Pythonの組み込みTimSort(マージソートと挿入ソートから派生したハイブリッドの安定したソートアルゴリズム)はCの速度で実行され、Pythonで手作業でコーディングされたマージソートは速度と競合することを期待できません。

ここでより興味深いソリューションの1つは、Niklas Bが投稿た回答で、組み込みの並べ替えを使用して配列項目のランキングを決定し、バイナリインデックスツリー(別名Fenwickツリー)を使用して、反転の計算に必要な累積合計を格納します。カウント。このデータ構造とNiklasのアルゴリズムを理解しようとする過程で、私は自分でいくつかのバリエーションを作成しました(以下に投稿)。しかし、リストのサイズが中程度の場合、Pythonの組み込み関数を使用する方が、素敵なフェンウィックツリーよりも速いことも発見しましたsum

def count_inversions(a):
    total = 0
    counts = [0] * len(a)
    rank = {v: i for i, v in enumerate(sorted(a))}
    for u in reversed(a):
        i = rank[u]
        total += sum(counts[:i])
        counts[i] += 1
    return total

最終的に、リストのサイズが500前後になると、そのループsum内での呼び出しのO(n²)の側面がfor醜い頭を引き戻し、パフォーマンスが急激に低下し始めます。

Mergesortは唯一のO(nlogn)ソートではなく、他のいくつかを使用して反転カウントを実行できます。prasadvkの答えはバイナリツリーソートを使用していますが、彼のコードはC ++またはその派生物の1つであるようです。そこで、Pythonバージョンを追加しました。最初はクラスを使用してツリーノードを実装していましたが、dictが著しく高速であることを発見しました。私は最終的にリストを使用しましたが、コードは少し読みにくくなりますが、さらに高速です。

treesortの1つのボーナスは、mergesortよりも反復的に実装する方がはるかに簡単であることです。Pythonは再帰を最適化せず、再帰の深さの制限があります(ただし、本当に必要な場合は増やすことができます)。そしてもちろん、Python関数呼び出しは比較的遅いので、速度を最適化しようとするときは、実用的な場合は関数呼び出しを回避することをお勧めします。

もう1つのO(nlogn)ソートは、由緒ある基数ソートです。大きな利点は、キーを互いに比較しないことです。欠点は、整数の連続したシーケンス、理想的range(b**m)bは通常2 である整数の順列で最もよく機能することです。私は、カウントインバージョン、オフライン直交範囲カウント、および関連する問題を読んだ後、基数ソートに基づいていくつかのバージョンを追加しました順列の「反転」の数の計算にリンクされています

基数ソートを効果的に使用してseq、長さnの一般的なシーケンスで反転をカウントrange(n)するには、と同じ数の反転を持つ置換を作成できますseq。TimSortを介して(最悪の場合)O(nlogn)時間でそれを行うことができます。トリックはseq、ソートによってのインデックスを並べ替えることseqです。小さな例でこれを説明する方が簡単です。

seq = [15, 14, 11, 12, 10, 13]
b = [t[::-1] for t in enumerate(seq)]
print(b)
b.sort()
print(b)

出力

[(15, 0), (14, 1), (11, 2), (12, 3), (10, 4), (13, 5)]
[(10, 4), (11, 2), (12, 3), (13, 5), (14, 1), (15, 0)]

(値、インデックス)のペアを並べ替えることにより、並べ替えられた順序から元の順序に戻すために必要なスワップと同じ数のseqインデックスを並べ替えました。適切なキー関数でソートすることにより、その順列を作成できます。seqseqrange(n)

print(sorted(range(len(seq)), key=lambda k: seq[k]))

出力

[4, 2, 3, 5, 1, 0]

のメソッドlambdaを使用することでそれを回避できます。seq.__getitem__

sorted(range(len(seq)), key=seq.__getitem__)

これは少しだけ高速ですが、取得できるすべての速度向上を探しています。;)


以下のコードtimeitは、このページにある既存のすべてのPythonアルゴリズムと、いくつかの独自のPythonアルゴリズムのテストを実行します。ブルートフォースO(n²)バージョンのいくつか、Niklas Bのアルゴリズムのいくつかのバリエーション、そしてもちろん、mergesortに基づくもの(私は既存の答えを参照せずに書いた)。また、prasadvkのコードから大まかに派生したリストベースのツリーソートコードと、基数ソートに基づくさまざまな関数があり、マージソートアプローチと同様の戦略を使用するものsumや、Fenwickツリーを使用するものもあります。

このプログラムは、一連のランダムな整数のリストで各関数の実行時間を測定します。また、各関数が他の関数と同じ結果を与えること、および入力リストを変更しないことも確認できます。

timeit呼び出しは、3つの結果を含むベクトルを提供します。ここで調べる主な値は最小値です。他の値はtimeitモジュールドキュメントの Noteで説明されているように、その最小値の信頼性を示すだけです。

残念ながら、このプログラムの出力は大きすぎてこの回答に含めることができないため、独自の(コミュニティーWiki)回答に投稿しています

出力は、古いDebian派生ディストリビューションでPython 3.6.0を実行している私の古代の32ビットシングルコア2GHzマシンでの3回の実行からのものです。YMMV。テスト中、CPUに対する他のタスクの影響を最小限に抑えるために、Webブラウザーをシャットダウンし、ルーターから切断しました。

最初の実行では、リストサイズが5〜320、ループサイズが4096〜64のすべての関数をテストします(リストサイズが2倍になるため、ループサイズは半分になります)。各リストの作成に使用されるランダムプールはリスト自体のサイズの半分であるため、大量の重複が発生する可能性があります。一部の反転カウントアルゴリズムは、他のアルゴリズムよりも重複に敏感です。

2回目の実行では、より大きなリスト(640〜10240、および8の固定ループサイズ)を使用します。時間を節約するために、最も遅い関数のいくつかをテストから除外します。マイブルートフォースO(n²)関数は、単にある方法使用していることを、私のコードこれらのサイズで遅すぎる、と前述のようにsumリストを緩和するために、小さなに非常によくない、ただの大きなリストに追いつくことはできません。

最後の実行では、20480〜655360のリストサイズと4の固定ループサイズをカバーし、8つの関数が最も高速です。リストのサイズが40,000未満の場合、Tim Babychのコードが最も効果的です。よくやったティム!Niklas Bのコードは、オールラウンドの優れたパフォーマーでもありますが、小さいリストでは負けてしまいます。「python」の2分割ベースのコードもかなりうまくいきますが、重複の多い膨大なリストでは少し遅いように見えますが、これはおそらく、複製whileをステップオーバーするために使用する線形ループが原因です。

ただし、リストのサイズが非常に大きい場合、2分割ベースのアルゴリズムは、真のO(nlogn)アルゴリズムと競合できません。

#!/usr/bin/env python3

''' Test speeds of various ways of counting inversions in a list

    The inversion count is a measure of how sorted an array is.
    A pair of items in a are inverted if i < j but a[j] > a[i]

    See /programming/337664/counting-inversions-in-an-array

    This program contains code by the following authors:
    mkso
    Niklas B
    B. M.
    Tim Babych
    python
    Zhe Hu
    prasadvk
    noman pouigt
    PM 2Ring

    Timing and verification code by PM 2Ring
    Collated 2017.12.16
    Updated 2017.12.21
'''

from timeit import Timer
from random import seed, randrange
from bisect import bisect, insort_left

seed('A random seed string')

# Merge sort version by mkso
def count_inversion_mkso(lst):
    return merge_count_inversion(lst)[1]

def merge_count_inversion(lst):
    if len(lst) <= 1:
        return lst, 0
    middle = len(lst) // 2
    left, a = merge_count_inversion(lst[:middle])
    right, b = merge_count_inversion(lst[middle:])
    result, c = merge_count_split_inversion(left, right)
    return result, (a + b + c)

def merge_count_split_inversion(left, right):
    result = []
    count = 0
    i, j = 0, 0
    left_len = len(left)
    while i < left_len and j < len(right):
        if left[i] <= right[j]:
            result.append(left[i])
            i += 1
        else:
            result.append(right[j])
            count += left_len - i
            j += 1
    result += left[i:]
    result += right[j:]
    return result, count

# . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
# Using a Binary Indexed Tree, aka a Fenwick tree, by Niklas B.
def count_inversions_NiklasB(a):
    res = 0
    counts = [0] * (len(a) + 1)
    rank = {v: i for i, v in enumerate(sorted(a), 1)}
    for x in reversed(a):
        i = rank[x] - 1
        while i:
            res += counts[i]
            i -= i & -i
        i = rank[x]
        while i <= len(a):
            counts[i] += 1
            i += i & -i
    return res

# . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
# Merge sort version by B.M
# Modified by PM 2Ring to deal with the global counter
bm_count = 0

def merge_count_BM(seq):
    global bm_count
    bm_count = 0
    sort_bm(seq)
    return bm_count

def merge_bm(l1,l2):
    global bm_count
    l = []
    while l1 and l2:
        if l1[-1] <= l2[-1]:
            l.append(l2.pop())
        else:
            l.append(l1.pop())
            bm_count += len(l2)
    l.reverse()
    return l1 + l2 + l

def sort_bm(l):
    t = len(l) // 2
    return merge_bm(sort_bm(l[:t]), sort_bm(l[t:])) if t > 0 else l

# . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
# Bisection based method by Tim Babych
def solution_TimBabych(A):
    sorted_left = []
    res = 0
    for i in range(1, len(A)):
        insort_left(sorted_left, A[i-1])
        # i is also the length of sorted_left
        res += (i - bisect(sorted_left, A[i]))
    return res

# Slightly faster, except for very small lists
def solutionE_TimBabych(A):
    res = 0
    sorted_left = []
    for i, u in enumerate(A):
        # i is also the length of sorted_left
        res += (i - bisect(sorted_left, u))
        insort_left(sorted_left, u)
    return res

# . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
# Bisection based method by "python"
def solution_python(A):
    B = list(A)
    B.sort()
    inversion_count = 0
    for i in range(len(A)):
        j = binarySearch_python(B, A[i])
        while B[j] == B[j - 1]:
            if j < 1:
                break
            j -= 1
        inversion_count += j
        B.pop(j)
    return inversion_count

def binarySearch_python(alist, item):
    first = 0
    last = len(alist) - 1
    found = False
    while first <= last and not found:
        midpoint = (first + last) // 2
        if alist[midpoint] == item:
            return midpoint
        else:
            if item < alist[midpoint]:
                last = midpoint - 1
            else:
                first = midpoint + 1

# . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
# Merge sort version by Zhe Hu
def inv_cnt_ZheHu(a):
    _, count = inv_cnt(a.copy())
    return count

def inv_cnt(a):
    n = len(a)
    if n==1:
        return a, 0
    left = a[0:n//2] # should be smaller
    left, cnt1 = inv_cnt(left)
    right = a[n//2:] # should be larger
    right, cnt2 = inv_cnt(right)

    cnt = 0
    i_left = i_right = i_a = 0
    while i_a < n:
        if (i_right>=len(right)) or (i_left < len(left)
            and left[i_left] <= right[i_right]):
            a[i_a] = left[i_left]
            i_left += 1
        else:
            a[i_a] = right[i_right]
            i_right += 1
            if i_left < len(left):
                cnt += len(left) - i_left
        i_a += 1
    return (a, cnt1 + cnt2 + cnt)

# . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
# Merge sort version by noman pouigt
# From https://stackoverflow.com/q/47830098
def reversePairs_nomanpouigt(nums):
    def merge(left, right):
        if not left or not right:
            return (0, left + right)
        #if everything in left is less than right
        if left[len(left)-1] < right[0]:
            return (0, left + right)
        else:
            left_idx, right_idx, count = 0, 0, 0
            merged_output = []

            # check for condition before we merge it
            while left_idx < len(left) and right_idx < len(right):
                #if left[left_idx] > 2 * right[right_idx]:
                if left[left_idx] > right[right_idx]:
                    count += len(left) - left_idx
                    right_idx += 1
                else:
                    left_idx += 1

            #merging the sorted list
            left_idx, right_idx = 0, 0
            while left_idx < len(left) and right_idx < len(right):
                if left[left_idx] > right[right_idx]:
                    merged_output += [right[right_idx]]
                    right_idx += 1
                else:
                    merged_output += [left[left_idx]]
                    left_idx += 1
            if left_idx == len(left):
                merged_output += right[right_idx:]
            else:
                merged_output += left[left_idx:]
        return (count, merged_output)

    def partition(nums):
        count = 0
        if len(nums) == 1 or not nums:
            return (0, nums)
        pivot = len(nums)//2
        left_count, l = partition(nums[:pivot])
        right_count, r = partition(nums[pivot:])
        temp_count, temp_list = merge(l, r)
        return (temp_count + left_count + right_count, temp_list)
    return partition(nums)[0]

# . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
# PM 2Ring
def merge_PM2R(seq):
    seq, count = merge_sort_count_PM2R(seq)
    return count

def merge_sort_count_PM2R(seq):
    mid = len(seq) // 2
    if mid == 0:
        return seq, 0
    left, left_total = merge_sort_count_PM2R(seq[:mid])
    right, right_total = merge_sort_count_PM2R(seq[mid:])
    total = left_total + right_total
    result = []
    i = j = 0
    left_len, right_len = len(left), len(right)
    while i < left_len and j < right_len:
        if left[i] <= right[j]:
            result.append(left[i])
            i += 1
        else:
            result.append(right[j])
            j += 1
            total += left_len - i
    result.extend(left[i:])
    result.extend(right[j:])
    return result, total

def rank_sum_PM2R(a):
    total = 0
    counts = [0] * len(a)
    rank = {v: i for i, v in enumerate(sorted(a))}
    for u in reversed(a):
        i = rank[u]
        total += sum(counts[:i])
        counts[i] += 1
    return total

# Fenwick tree functions adapted from C code on Wikipedia
def fen_sum(tree, i):
    ''' Return the sum of the first i elements, 0 through i-1 '''
    total = 0
    while i:
        total += tree[i-1]
        i -= i & -i
    return total

def fen_add(tree, delta, i):
    ''' Add delta to element i and thus 
        to fen_sum(tree, j) for all j > i 
    '''
    size = len(tree)
    while i < size:
        tree[i] += delta
        i += (i+1) & -(i+1)

def fenwick_PM2R(a):
    total = 0
    counts = [0] * len(a)
    rank = {v: i for i, v in enumerate(sorted(a))}
    for u in reversed(a):
        i = rank[u]
        total += fen_sum(counts, i)
        fen_add(counts, 1, i)
    return total

def fenwick_inline_PM2R(a):
    total = 0
    size = len(a)
    counts = [0] * size
    rank = {v: i for i, v in enumerate(sorted(a))}
    for u in reversed(a):
        i = rank[u]
        j = i + 1
        while i:
            total += counts[i]
            i -= i & -i
        while j < size:
            counts[j] += 1
            j += j & -j
    return total

def bruteforce_loops_PM2R(a):
    total = 0
    for i in range(1, len(a)):
        u = a[i]
        for j in range(i):
            if a[j] > u:
                total += 1
    return total

def bruteforce_sum_PM2R(a):
    return sum(1 for i in range(1, len(a)) for j in range(i) if a[j] > a[i])

# Using binary tree counting, derived from C++ code (?) by prasadvk
# https://stackoverflow.com/a/16056139
def ltree_count_PM2R(a):
    total, root = 0, None
    for u in a:
        # Store data in a list-based tree structure
        # [data, count, left_child, right_child]
        p = [u, 0, None, None]
        if root is None:
            root = p
            continue
        q = root
        while True:
            if p[0] < q[0]:
                total += 1 + q[1]
                child = 2
            else:
                q[1] += 1
                child = 3
            if q[child]:
                q = q[child]
            else:
                q[child] = p
                break
    return total

# Counting based on radix sort, recursive version
def radix_partition_rec(a, L):
    if len(a) < 2:
        return 0
    if len(a) == 2:
        return a[1] < a[0]
    left, right = [], []
    count = 0
    for u in a:
        if u & L:
            right.append(u)
        else:
            count += len(right)
            left.append(u)
    L >>= 1
    if L:
        count += radix_partition_rec(left, L) + radix_partition_rec(right, L)
    return count

# The following functions determine swaps using a permutation of 
# range(len(a)) that has the same inversion count as `a`. We can create
# this permutation with `sorted(range(len(a)), key=lambda k: a[k])`
# but `sorted(range(len(a)), key=a.__getitem__)` is a little faster.

# Counting based on radix sort, iterative version
def radix_partition_iter(seq, L):
    count = 0
    parts = [seq]
    while L and parts:
        newparts = []
        for a in parts:
            if len(a) < 2:
                continue
            if len(a) == 2:
                count += a[1] < a[0]
                continue
            left, right = [], []
            for u in a:
                if u & L:
                    right.append(u)
                else:
                    count += len(right)
                    left.append(u)
            if left:
                newparts.append(left)
            if right:
                newparts.append(right)
        parts = newparts
        L >>= 1
    return count

def perm_radixR_PM2R(a):
    size = len(a)
    b = sorted(range(size), key=a.__getitem__)
    n = size.bit_length() - 1
    return radix_partition_rec(b, 1 << n)

def perm_radixI_PM2R(a):
    size = len(a)
    b = sorted(range(size), key=a.__getitem__)
    n = size.bit_length() - 1
    return radix_partition_iter(b, 1 << n)

# Plain sum of the counts of the permutation
def perm_sum_PM2R(a):
    total = 0
    size = len(a)
    counts = [0] * size
    for i in reversed(sorted(range(size), key=a.__getitem__)):
        total += sum(counts[:i])
        counts[i] = 1
    return total

# Fenwick sum of the counts of the permutation
def perm_fenwick_PM2R(a):
    total = 0
    size = len(a)
    counts = [0] * size
    for i in reversed(sorted(range(size), key=a.__getitem__)):
        j = i + 1
        while i:
            total += counts[i]
            i -= i & -i
        while j < size:
            counts[j] += 1
            j += j & -j
    return total

# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
# All the inversion-counting functions
funcs = (
    solution_TimBabych,
    solutionE_TimBabych,
    solution_python,
    count_inversion_mkso,
    count_inversions_NiklasB,
    merge_count_BM,
    inv_cnt_ZheHu,
    reversePairs_nomanpouigt,
    fenwick_PM2R,
    fenwick_inline_PM2R,
    merge_PM2R,
    rank_sum_PM2R,
    bruteforce_loops_PM2R,
    bruteforce_sum_PM2R,
    ltree_count_PM2R,
    perm_radixR_PM2R,
    perm_radixI_PM2R,
    perm_sum_PM2R,
    perm_fenwick_PM2R,
)

def time_test(seq, loops, verify=False):
    orig = seq
    timings = []
    for func in funcs:
        seq = orig.copy()
        value = func(seq) if verify else None
        t = Timer(lambda: func(seq))
        result = sorted(t.repeat(3, loops))
        timings.append((result, func.__name__, value))
        assert seq==orig, 'Sequence altered by {}!'.format(func.__name__)
    first = timings[0][-1]
    timings.sort()
    for result, name, value in timings:
        result = ', '.join([format(u, '.5f') for u in result])
        print('{:24} : {}'.format(name, result))

    if verify:
        # Check that all results are identical
        bad = ['%s: %d' % (name, value)
            for _, name, value in timings if value != first]
        if bad:
            print('ERROR. Value: {}, bad: {}'.format(first, ', '.join(bad)))
        else:
            print('Value: {}'.format(first))
    print()

#Run the tests
size, loops = 5, 1 << 12
verify = True
for _ in range(7):
    hi = size // 2
    print('Size = {}, hi = {}, {} loops'.format(size, hi, loops))
    seq = [randrange(hi) for _ in range(size)]
    time_test(seq, loops, verify)
    loops >>= 1
    size <<= 1

#size, loops = 640, 8
#verify = False
#for _ in range(5):
    #hi = size // 2
    #print('Size = {}, hi = {}, {} loops'.format(size, hi, loops))
    #seq = [randrange(hi) for _ in range(size)]
    #time_test(seq, loops, verify)
    #size <<= 1

#size, loops = 163840, 4
#verify = False
#for _ in range(3):
    #hi = size // 2
    #print('Size = {}, hi = {}, {} loops'.format(size, hi, loops))
    #seq = [randrange(hi) for _ in range(size)]
    #time_test(seq, loops, verify)
    #size <<= 1

出力はこちらをご覧ください


ありがとう、それは非常に面白いものでした:) Cモジュールを使用することの利点を明らかに示しています-二等分です。
Tim Babych

問題は、勝者が(理論的には)二次アルゴリズムを使用することです。サイズ〜100 000の場合、他のユーザーに打ち負かされます。私は自分の投稿を編集して、Python準線形C速度ソリューションを配置しました。
BM

@BMもちろんですが、ティムの2分割アプローチは、45,000程度のサイズになるまでは非常に優れています。翌日以降にここに追加する解決策がいくつかあります。
PM 2Ring '20 / 12/17

@TimBabych bisectCって言ってるの?Pythonだと確信しています。
Stefan Pochmann、2018年

1
PythonのbisectモジュールはCで記述されています。github.com / python
Tim Babych

10

反転の数は、マージソートのマージプロセスを分析することで確認できます。 マージプロセス

2番目の配列からマージ配列(この例では9)に要素をコピーすると、他の要素との相対的な位置が維持されます。要素を最初の配列からマージ配列(ここでは5)にコピーすると、すべての要素が2番目の配列にとどまったまま反転します(3と4で2反転)。したがって、マージソートを少し変更することで、O(n ln n)の問題を解決できます。
例としては、以下のmergesort pythonコードの2行のコメントを外すだけでカウントできます。

def merge(l1,l2):
    l = []
    # global count
    while l1 and l2:
        if l1[-1] <= l2[-1]:
            l.append(l2.pop())
        else:
            l.append(l1.pop())
            # count += len(l2)
    l.reverse()
    return l1 + l2 + l

def sort(l): 
    t = len(l) // 2
    return merge(sort(l[:t]), sort(l[t:])) if t > 0 else l

count=0
print(sort([5,1,2,4,9,3]), count)
# [1, 2, 3, 4, 5, 9] 6

編集1

同じタスクは、クイックソートの安定したバージョンで実現できます。

def part(l):
    pivot=l[-1]
    small,big = [],[]
    count = big_count = 0
    for x in l:
        if x <= pivot:
            small.append(x)
            count += big_count
        else:
            big.append(x)
            big_count += 1
    return count,small,big

def quick_count(l):
    if len(l)<2 : return 0
    count,small,big = part(l)
    small.pop()
    return count + quick_count(small) + quick_count(big)

最後の要素としてピボットを選択すると、反転がうまくカウントされ、実行時間は上記のものをマージするよりも40%良くなります。

編集2

Python、numpy&numbaバージョンでのパフォーマンス:

最初に、argsort O(n ln n)を使用する派手な部分:

def count_inversions(a):
    n = a.size
    counts = np.arange(n) & -np.arange(n)  # The BIT
    ags = a.argsort(kind='mergesort')    
    return  BIT(ags,counts,n)

そして、効率的なBITアプローチのnumba部分:

@numba.njit
def BIT(ags,counts,n):
    res = 0        
    for x in ags :
        i = x
        while i:
            res += counts[i]
            i -= i & -i
        i = x+1
        while i < n:
            counts[i] -= 1
            i += i & -i
    return  res  

この質問に対するPythonのすべての回答を比較する回答を投稿しtimeitので、コードが含まれています。タイミング結果を確認することに興味があるかもしれません。
PM 2Ring 2017

この投稿にはパフォーマンスの問題はありません...しばらくしてみます。Numpy numbaが許可されました;)?
BM

私はNumbaを使用したことがありませんが、Numpyを少し使用して、Numpyバージョンを自分で追加することを考えましたが、テストを標準ライブラリのみを使用するソリューションに制限することにしました。しかし、Numpyソリューションがどのように比較されるかを見るのは興味深いでしょう。私はそれが小さなリストで速くなることはないと思います。
PM 2Ring 2017

100倍のスピードアップが印象的です!しかし、Numbaを持っていないので実行できません。そして、先に述べたように、それを私のtimeitコレクションに含めるのは公平ではありません。
PM 2Ring '20 / 12/17

8

ジェフリー・アービングの答えは間違っていることに注意してください。

配列の反転数は、配列をソートするために要素を移動する必要がある距離の合計の半分です。したがって、配列をソートし、結果の順列p [i]を維持し、abs(p [i] -i)/ 2の合計を計算することで計算できます。これにはO(n log n)時間かかりますが、これは最適です。

別の方法がhttp://mathworld.wolfram.com/PermutationInversion.htmlにあります。この方法は、max(0、p [i] -i)の合計に相当します。これは、abs(p [i] -i])/ 2の合計に等しく、要素が左に移動する距離の合計は、合計距離要素は右に移動します。

例として、シーケンス{3、2、1}を取り上げます。(3、2)、(3、1)、(2、1)の3つの反転があるため、反転番号は3です。ただし、引用された方法によると、答えは2でした。


代わりに、隣接するスワップの必要な最小数を数えることで正しい答えを見つけることができます。:議論を参照してくださいstackoverflow.com/questions/20990127/...
アイザック・ターナー


4

バイナリツリーのバリエーションを使用した1つの可能な解決策を次に示します。これは、rightSubTreeSizeというフィールドを各ツリーノードに追加します。配列に現れる順に、バイナリツリーに数値を挿入し続けます。数値がノードのlhsになる場合、その要素の反転カウントは(1 + rightSubTreeSize)になります。これらの要素はすべて現在の要素よりも大きく、配列の最初の方に表示されていたからです。要素がノードのrhsに移動する場合は、そのrightSubTreeSizeを増やしてください。コードは次のとおりです。

Node { 
    int data;
    Node* left, *right;
    int rightSubTreeSize;

    Node(int data) { 
        rightSubTreeSize = 0;
    }   
};

Node* root = null;
int totCnt = 0;
for(i = 0; i < n; ++i) { 
    Node* p = new Node(a[i]);
    if(root == null) { 
        root = p;
        continue;
    } 

    Node* q = root;
    int curCnt = 0;
    while(q) { 
        if(p->data <= q->data) { 
            curCnt += 1 + q->rightSubTreeSize;
            if(q->left) { 
                q = q->left;
            } else { 
                q->left = p;
                break;
            }
        } else { 
            q->rightSubTreeSize++;
            if(q->right) { 
                q = q->right;
            } else { 
                q->right = p;
                break;
            }
        }
    }

    totCnt += curCnt;
  }
  return totCnt;

これは興味深いアプローチであり、非常に高速のようです。ただし、if(p->data < q->data)そうでない場合、その比較は重複する必要があり、正しく処理されません。qループの先頭でテストする必要はありませんwhile。無条件ループは問題なく機能します。また、あなたはこれがどんな言語であるかについて言及することを怠っていました。:)そして、あなたの関数はヘッダー行を失ったようです。
PM 2Ring 2017

私はあなたのツリーアルゴリズムに基づくPythonバージョンを私の答えに追加しました。もちろん、完全にコンパイルされたバージョンほど高速ではありませんが、他のバージョンのPythonと比較するとかなり高速です。
PM 2Ring 2017

3
public static int mergeSort(int[] a, int p, int r)
{
    int countInversion = 0;
    if(p < r)
    {
        int q = (p + r)/2;
        countInversion = mergeSort(a, p, q);
        countInversion += mergeSort(a, q+1, r);
        countInversion += merge(a, p, q, r);
    }
    return countInversion;
}

public static int merge(int[] a, int p, int q, int r)
{
    //p=0, q=1, r=3
    int countingInversion = 0;
    int n1 = q-p+1;
    int n2 = r-q;
    int[] temp1 = new int[n1+1];
    int[] temp2 = new int[n2+1];
    for(int i=0; i<n1; i++) temp1[i] = a[p+i];
    for(int i=0; i<n2; i++) temp2[i] = a[q+1+i];

    temp1[n1] = Integer.MAX_VALUE;
    temp2[n2] = Integer.MAX_VALUE;
    int i = 0, j = 0;

    for(int k=p; k<=r; k++)
    {
        if(temp1[i] <= temp2[j])
        {
            a[k] = temp1[i];
            i++;
        }
        else
        {
            a[k] = temp2[j];
            j++;
            countingInversion=countingInversion+(n1-i); 
        }
    }
    return countingInversion;
}
public static void main(String[] args)
{
    int[] a = {1, 20, 6, 4, 5};
    int countInversion = mergeSort(a, 0, a.length-1);
    System.out.println(countInversion);
}

3
これは、すでに投稿されているJavaおよびPythonのソリューションと大きく異なりますか?また、コードのみの回答は特に良いIMOではありません。特に、この質問が言語を指定していないことを考えると、
Bernhard Barker 14

2

これは古い質問なので、Cで回答を提供します。

#include <stdio.h>

int count = 0;
int inversions(int a[], int len);
void mergesort(int a[], int left, int right);
void merge(int a[], int left, int mid, int right);

int main() {
  int a[] = { 1, 5, 2, 4, 0 };
  printf("%d\n", inversions(a, 5));
}

int inversions(int a[], int len) {
  mergesort(a, 0, len - 1);
  return count;
}

void mergesort(int a[], int left, int right) {
  if (left < right) {
     int mid = (left + right) / 2;
     mergesort(a, left, mid);
     mergesort(a, mid + 1, right);
     merge(a, left, mid, right);
  }
}

void merge(int a[], int left, int mid, int right) {
  int i = left;
  int j = mid + 1;
  int k = 0;
  int b[right - left + 1];
  while (i <= mid && j <= right) {
     if (a[i] <= a[j]) {
       b[k++] = a[i++];
     } else {
       printf("right element: %d\n", a[j]);
       count += (mid - i + 1);
       printf("new count: %d\n", count);
       b[k++] = a[j++];
     }
  }
  while (i <= mid)
    b[k++] = a[i++];
  while (j <= right)
    b[k++] = a[j++];
  for (i = left, k = 0; i <= right; i++, k++) {
    a[i] = b[k];
  }
}

-1他のすべての言語での回答は、絶望的にあまりにも多くの回答につながり、そのすべてが、他の回答ですでに提示された情報を本質的に複製するためです。さらに、これは本質的には説明のないコードのみの回答であり、せいぜい、主にその言語に関する実際の質問に主に適しています。
Bernhard Barker 14

2

ここにC + +ソリューションがあります

/**
*array sorting needed to verify if first arrays n'th element is greater than sencond arrays
*some element then all elements following n will do the same
*/
#include<stdio.h>
#include<iostream>
using namespace std;
int countInversions(int array[],int size);
int merge(int arr1[],int size1,int arr2[],int size2,int[]);
int main()
{
    int array[] = {2, 4, 1, 3, 5};
    int size = sizeof(array) / sizeof(array[0]);
    int x = countInversions(array,size);
    printf("number of inversions = %d",x);
}

int countInversions(int array[],int size)
{
    if(size > 1 )
    {
    int mid = size / 2;
    int count1 = countInversions(array,mid);
    int count2 = countInversions(array+mid,size-mid);
    int temp[size];
    int count3 = merge(array,mid,array+mid,size-mid,temp);
    for(int x =0;x<size ;x++)
    {
        array[x] = temp[x];
    }
    return count1 + count2 + count3;
    }else{
        return 0;
    }
}

int merge(int arr1[],int size1,int arr2[],int size2,int temp[])
{
    int count  = 0;
    int a = 0;
    int b = 0;
    int c = 0;
    while(a < size1 && b < size2)
    {
        if(arr1[a] < arr2[b])
        {
            temp[c] = arr1[a];
            c++;
            a++;
        }else{
            temp[c] = arr2[b];
            b++;
            c++;
            count = count + size1 -a;
        }
    }

    while(a < size1)
    {
        temp[c] = arr1[a];
        c++;a++;
    }

while(b < size2)
    {
        temp[c] = arr2[b];
        c++;b++;
    }

    return count;
}

1

これは、カウント逆転のCコードです

#include <stdio.h>
#include <stdlib.h>

int  _mergeSort(int arr[], int temp[], int left, int right);
int merge(int arr[], int temp[], int left, int mid, int right);

/* This function sorts the input array and returns the
   number of inversions in the array */
int mergeSort(int arr[], int array_size)
{
    int *temp = (int *)malloc(sizeof(int)*array_size);
    return _mergeSort(arr, temp, 0, array_size - 1);
}

/* An auxiliary recursive function that sorts the input array and
  returns the number of inversions in the array. */
int _mergeSort(int arr[], int temp[], int left, int right)
{
  int mid, inv_count = 0;
  if (right > left)
  {
    /* Divide the array into two parts and call _mergeSortAndCountInv()
       for each of the parts */
    mid = (right + left)/2;

    /* Inversion count will be sum of inversions in left-part, right-part
      and number of inversions in merging */
    inv_count  = _mergeSort(arr, temp, left, mid);
    inv_count += _mergeSort(arr, temp, mid+1, right);

    /*Merge the two parts*/
    inv_count += merge(arr, temp, left, mid+1, right);
  }
  return inv_count;
}

/* This funt merges two sorted arrays and returns inversion count in
   the arrays.*/
int merge(int arr[], int temp[], int left, int mid, int right)
{
  int i, j, k;
  int inv_count = 0;

  i = left; /* i is index for left subarray*/
  j = mid;  /* i is index for right subarray*/
  k = left; /* i is index for resultant merged subarray*/
  while ((i <= mid - 1) && (j <= right))
  {
    if (arr[i] <= arr[j])
    {
      temp[k++] = arr[i++];
    }
    else
    {
      temp[k++] = arr[j++];

     /*this is tricky -- see above explanation/diagram for merge()*/
      inv_count = inv_count + (mid - i);
    }
  }

  /* Copy the remaining elements of left subarray
   (if there are any) to temp*/
  while (i <= mid - 1)
    temp[k++] = arr[i++];

  /* Copy the remaining elements of right subarray
   (if there are any) to temp*/
  while (j <= right)
    temp[k++] = arr[j++];

  /*Copy back the merged elements to original array*/
  for (i=left; i <= right; i++)
    arr[i] = temp[i];

  return inv_count;
}

/* Driver progra to test above functions */
int main(int argv, char** args)
{
  int arr[] = {1, 20, 6, 4, 5};
  printf(" Number of inversions are %d \n", mergeSort(arr, 5));
  getchar();
  return 0;
}

ここで詳しく説明されています:http : //www.geeksforgeeks.org/counting-inversions/


1

O(n log n)時間、JavaのO(n)空間ソリューション。

マージステップ中にマージされた反転の数を維持するための微調整を含むマージソート。(よく説明されたマージソートについては、http: //www.vogella.com/tutorials/JavaAlgorithmsMergesort/article.htmlをご覧ください。

マージソートを行うことができるため、スペースの複雑さはO(1)に改善される可能性があります。

このソートを使用する場合、反転はマージステップでのみ、前半の要素の前に2番目の部分の要素を配置する必要がある場合にのみ発生します。たとえば、

  • 0 5 10 15

と合併

  • 1 6 22

3 + 2 + 0 = 5の反転があります:

  • 1、{5、10、15}
  • 6と{10、15}
  • 22と{}

5つの反転を行った後、新しくマージされたリストは0、1、5、6、10、15、22になります。

CodilityにはArrayInversionCountと呼ばれるデモタスクがあり、ソリューションをテストできます。

    public class FindInversions {

    public static int solution(int[] input) {
        if (input == null)
            return 0;
        int[] helper = new int[input.length];
        return mergeSort(0, input.length - 1, input, helper);
    }

    public static int mergeSort(int low, int high, int[] input, int[] helper) {
        int inversionCount = 0;
        if (low < high) {
            int medium = low + (high - low) / 2;
            inversionCount += mergeSort(low, medium, input, helper);
            inversionCount += mergeSort(medium + 1, high, input, helper);
            inversionCount += merge(low, medium, high, input, helper);
        }
        return inversionCount;
    }

    public static int merge(int low, int medium, int high, int[] input, int[] helper) {
        int inversionCount = 0;

        for (int i = low; i <= high; i++)
            helper[i] = input[i];

        int i = low;
        int j = medium + 1;
        int k = low;

        while (i <= medium && j <= high) {
            if (helper[i] <= helper[j]) {
                input[k] = helper[i];
                i++;
            } else {
                input[k] = helper[j];
                // the number of elements in the first half which the j element needs to jump over.
                // there is an inversion between each of those elements and j.
                inversionCount += (medium + 1 - i);
                j++;
            }
            k++;
        }

        // finish writing back in the input the elements from the first part
        while (i <= medium) {
            input[k] = helper[i];
            i++;
            k++;
        }
        return inversionCount;
    }

}

1

O(n * log(n))perlの実装は次のとおりです。

sub sort_and_count {
    my ($arr, $n) = @_;
    return ($arr, 0) unless $n > 1;

    my $mid = $n % 2 == 1 ? ($n-1)/2 : $n/2;
    my @left = @$arr[0..$mid-1];
    my @right = @$arr[$mid..$n-1];

    my ($sleft, $x) = sort_and_count( \@left, $mid );
    my ($sright, $y) = sort_and_count( \@right, $n-$mid);
    my ($merged, $z) = merge_and_countsplitinv( $sleft, $sright, $n );

    return ($merged, $x+$y+$z);
}

sub merge_and_countsplitinv {
    my ($left, $right, $n) = @_;

    my ($l_c, $r_c) = ($#$left+1, $#$right+1);
    my ($i, $j) = (0, 0);
    my @merged;
    my $inv = 0;

    for my $k (0..$n-1) {
        if ($i<$l_c && $j<$r_c) {
            if ( $left->[$i] < $right->[$j]) {
                push @merged, $left->[$i];
                $i+=1;
            } else {
                push @merged, $right->[$j];
                $j+=1;
                $inv += $l_c - $i;
            }
        } else {
            if ($i>=$l_c) {
                push @merged, @$right[ $j..$#$right ];
            } else {
                push @merged, @$left[ $i..$#$left ];
            }
            last;
        }
    }

    return (\@merged, $inv);
}

1

Pythonでの私の答え:

1-最初に配列をソートし、そのコピーを作成します。私のプログラムでは、Bはソートされた配列を表します。2-元の配列(ソートされていない)を反復処理し、ソートされたリストでその要素のインデックスを見つけます。要素のインデックスも書き留めます。3-要素に重複がないことを確認します。重複している場合は、インデックスの値を-1だけ変更する必要があります。私のプログラムのwhile条件はまさにそれを行っています。4-インデックス値になる反転をカウントし続け、反転を計算したら要素を削除します。

def binarySearch(alist, item):
    first = 0
    last = len(alist) - 1
    found = False

    while first <= last and not found:
        midpoint = (first + last)//2
        if alist[midpoint] == item:
            return midpoint
        else:
            if item < alist[midpoint]:
                last = midpoint - 1
            else:
                first = midpoint + 1

def solution(A):

    B = list(A)
    B.sort()
    inversion_count = 0
    for i in range(len(A)):
        j = binarySearch(B, A[i])
        while B[j] == B[j - 1]:
            if j < 1:
                break
            j -= 1

        inversion_count += j
        B.pop(j)

    if inversion_count > 1000000000:
        return -1
    else:
        return inversion_count

print solution([4, 10, 11, 1, 3, 9, 10])

この質問に対するPythonのすべての回答を比較する回答を投稿しtimeitので、コードが含まれています。タイミング結果を確認することに興味があるかもしれません。
PM 2Ring 2017

1

さて、私は別の解決策を持っていますが、それが異なる配列要素に対してのみ機能するのではないかと心配しています。

//Code
#include <bits/stdc++.h>
using namespace std;

int main()
{
    int i,n;
    cin >> n;
    int arr[n],inv[n];
    for(i=0;i<n;i++){
        cin >> arr[i];
    }
    vector<int> v;
    v.push_back(arr[n-1]);
    inv[n-1]=0;
    for(i=n-2;i>=0;i--){
        auto it = lower_bound(v.begin(),v.end(),arr[i]); 
        //calculating least element in vector v which is greater than arr[i]
        inv[i]=it-v.begin();
        //calculating distance from starting of vector
        v.insert(it,arr[i]);
        //inserting that element into vector v
    }
    for(i=0;i<n;i++){
        cout << inv[i] << " ";
    }
    cout << endl;
    return 0;
}

私のコードを説明するために、配列の最後から要素を追加し続けます。すべての着信配列要素について、着信要素よりも大きいベクトルvの最初の要素インデックスを見つけ、その値を着信要素のインデックスの反転カウントに割り当てますその後、その要素をベクトルvの正しい位置に挿入して、ベクトルvがソートされた順序のままになるようにします。

//INPUT     
4
2 1 4 3

//OUTPUT    
1 0 1 0

//To calculate total inversion count just add up all the elements in output array

1

別のPythonソリューション、短いもの。組み込みのbisectモジュールを使用します。これは、要素をソートされた配列内のその場所に挿入し、ソートされた配列内の要素のインデックスを見つける機能を提供します。

アイデアは、n番目の左側の要素をそのような配列に格納することです。これにより、n番目より大きい要素の数を簡単に見つけることができます。

import bisect
def solution(A):
    sorted_left = []
    res = 0
    for i in xrange(1, len(A)):
        bisect.insort_left(sorted_left, A[i-1])
        # i is also the length of sorted_left
        res += (i - bisect.bisect(sorted_left, A[i]))
    return res

1
この質問に対するPythonのすべての回答を比較する回答を投稿しtimeitので、コードが含まれています。タイミング結果を確認することに興味があるかもしれません。:D
PM 2Ring

1

この回答には、timeit私の主な回答のコードによって生成されたテストの結果が含まれています。詳細はその答えを見てください!

count_inversions speed test results

Size = 5, hi = 2, 4096 loops
ltree_count_PM2R         : 0.04871, 0.04872, 0.04876
bruteforce_loops_PM2R    : 0.05696, 0.05700, 0.05776
solution_TimBabych       : 0.05760, 0.05822, 0.05943
solutionE_TimBabych      : 0.06642, 0.06704, 0.06760
bruteforce_sum_PM2R      : 0.07523, 0.07545, 0.07563
perm_sum_PM2R            : 0.09873, 0.09875, 0.09935
rank_sum_PM2R            : 0.10449, 0.10463, 0.10468
solution_python          : 0.13034, 0.13061, 0.13221
fenwick_inline_PM2R      : 0.14323, 0.14610, 0.18802
perm_radixR_PM2R         : 0.15146, 0.15203, 0.15235
merge_count_BM           : 0.16179, 0.16267, 0.16467
perm_radixI_PM2R         : 0.16200, 0.16202, 0.16768
perm_fenwick_PM2R        : 0.16887, 0.16920, 0.17075
merge_PM2R               : 0.18262, 0.18271, 0.18418
count_inversions_NiklasB : 0.19183, 0.19279, 0.20388
count_inversion_mkso     : 0.20060, 0.20141, 0.20398
inv_cnt_ZheHu            : 0.20815, 0.20841, 0.20906
fenwick_PM2R             : 0.22109, 0.22137, 0.22379
reversePairs_nomanpouigt : 0.29620, 0.29689, 0.30293
Value: 5

Size = 10, hi = 5, 2048 loops
solution_TimBabych       : 0.05954, 0.05989, 0.05991
solutionE_TimBabych      : 0.05970, 0.05972, 0.05998
perm_sum_PM2R            : 0.07517, 0.07519, 0.07520
ltree_count_PM2R         : 0.07672, 0.07677, 0.07684
bruteforce_loops_PM2R    : 0.07719, 0.07724, 0.07817
rank_sum_PM2R            : 0.08587, 0.08823, 0.08864
bruteforce_sum_PM2R      : 0.09470, 0.09472, 0.09484
solution_python          : 0.13126, 0.13154, 0.13185
perm_radixR_PM2R         : 0.14239, 0.14320, 0.14474
perm_radixI_PM2R         : 0.14632, 0.14669, 0.14679
fenwick_inline_PM2R      : 0.16796, 0.16831, 0.17030
perm_fenwick_PM2R        : 0.18189, 0.18212, 0.18638
merge_count_BM           : 0.19816, 0.19870, 0.19948
count_inversions_NiklasB : 0.21807, 0.22031, 0.22215
merge_PM2R               : 0.22037, 0.22048, 0.26106
fenwick_PM2R             : 0.24290, 0.24314, 0.24744
count_inversion_mkso     : 0.24895, 0.24899, 0.25205
inv_cnt_ZheHu            : 0.26253, 0.26259, 0.26590
reversePairs_nomanpouigt : 0.35711, 0.35762, 0.35973
Value: 20

Size = 20, hi = 10, 1024 loops
solutionE_TimBabych      : 0.05687, 0.05696, 0.05720
solution_TimBabych       : 0.06126, 0.06151, 0.06168
perm_sum_PM2R            : 0.06875, 0.06906, 0.07054
rank_sum_PM2R            : 0.07988, 0.07995, 0.08002
ltree_count_PM2R         : 0.11232, 0.11239, 0.11257
bruteforce_loops_PM2R    : 0.12553, 0.12584, 0.12592
solution_python          : 0.13472, 0.13540, 0.13694
bruteforce_sum_PM2R      : 0.15820, 0.15849, 0.16021
perm_radixI_PM2R         : 0.17101, 0.17148, 0.17229
perm_radixR_PM2R         : 0.17891, 0.18087, 0.18366
perm_fenwick_PM2R        : 0.20554, 0.20708, 0.21412
fenwick_inline_PM2R      : 0.21161, 0.21163, 0.22047
merge_count_BM           : 0.24125, 0.24261, 0.24565
count_inversions_NiklasB : 0.25712, 0.25754, 0.25778
merge_PM2R               : 0.26477, 0.26566, 0.31297
fenwick_PM2R             : 0.28178, 0.28216, 0.29069
count_inversion_mkso     : 0.30286, 0.30290, 0.30652
inv_cnt_ZheHu            : 0.32024, 0.32041, 0.32447
reversePairs_nomanpouigt : 0.45812, 0.45822, 0.46172
Value: 98

Size = 40, hi = 20, 512 loops
solutionE_TimBabych      : 0.05784, 0.05787, 0.05958
solution_TimBabych       : 0.06452, 0.06475, 0.06479
perm_sum_PM2R            : 0.07254, 0.07261, 0.07263
rank_sum_PM2R            : 0.08537, 0.08540, 0.08572
ltree_count_PM2R         : 0.11744, 0.11749, 0.11792
solution_python          : 0.14262, 0.14285, 0.14465
perm_radixI_PM2R         : 0.18774, 0.18776, 0.18922
perm_radixR_PM2R         : 0.19425, 0.19435, 0.19609
bruteforce_loops_PM2R    : 0.21500, 0.21511, 0.21686
perm_fenwick_PM2R        : 0.23338, 0.23375, 0.23674
fenwick_inline_PM2R      : 0.24947, 0.24958, 0.25189
bruteforce_sum_PM2R      : 0.27627, 0.27646, 0.28041
merge_count_BM           : 0.28059, 0.28128, 0.28294
count_inversions_NiklasB : 0.28557, 0.28759, 0.29022
merge_PM2R               : 0.29886, 0.29928, 0.30317
fenwick_PM2R             : 0.30241, 0.30259, 0.35237
count_inversion_mkso     : 0.34252, 0.34356, 0.34441
inv_cnt_ZheHu            : 0.37468, 0.37569, 0.37847
reversePairs_nomanpouigt : 0.50725, 0.50770, 0.50943
Value: 369

Size = 80, hi = 40, 256 loops
solutionE_TimBabych      : 0.06339, 0.06373, 0.06513
solution_TimBabych       : 0.06984, 0.06994, 0.07009
perm_sum_PM2R            : 0.09171, 0.09172, 0.09186
rank_sum_PM2R            : 0.10468, 0.10474, 0.10500
ltree_count_PM2R         : 0.14416, 0.15187, 0.18541
solution_python          : 0.17415, 0.17423, 0.17451
perm_radixI_PM2R         : 0.20676, 0.20681, 0.20936
perm_radixR_PM2R         : 0.21671, 0.21695, 0.21736
perm_fenwick_PM2R        : 0.26197, 0.26252, 0.26264
fenwick_inline_PM2R      : 0.28111, 0.28249, 0.28382
count_inversions_NiklasB : 0.31746, 0.32448, 0.32451
merge_count_BM           : 0.31964, 0.33842, 0.35276
merge_PM2R               : 0.32890, 0.32941, 0.33322
fenwick_PM2R             : 0.34355, 0.34377, 0.34873
count_inversion_mkso     : 0.37689, 0.37698, 0.38079
inv_cnt_ZheHu            : 0.42923, 0.42941, 0.43249
bruteforce_loops_PM2R    : 0.43544, 0.43601, 0.43902
bruteforce_sum_PM2R      : 0.52106, 0.52160, 0.52531
reversePairs_nomanpouigt : 0.57805, 0.58156, 0.58252
Value: 1467

Size = 160, hi = 80, 128 loops
solutionE_TimBabych      : 0.06766, 0.06784, 0.06963
solution_TimBabych       : 0.07433, 0.07489, 0.07516
perm_sum_PM2R            : 0.13143, 0.13175, 0.13179
rank_sum_PM2R            : 0.14428, 0.14440, 0.14922
solution_python          : 0.20072, 0.20076, 0.20084
ltree_count_PM2R         : 0.20314, 0.20583, 0.24776
perm_radixI_PM2R         : 0.23061, 0.23078, 0.23525
perm_radixR_PM2R         : 0.23894, 0.23915, 0.24234
perm_fenwick_PM2R        : 0.30984, 0.31181, 0.31503
fenwick_inline_PM2R      : 0.31933, 0.32680, 0.32722
merge_count_BM           : 0.36003, 0.36387, 0.36409
count_inversions_NiklasB : 0.36796, 0.36814, 0.37106
merge_PM2R               : 0.36847, 0.36848, 0.37127
fenwick_PM2R             : 0.37833, 0.37847, 0.38095
count_inversion_mkso     : 0.42746, 0.42747, 0.43184
inv_cnt_ZheHu            : 0.48969, 0.48974, 0.49293
reversePairs_nomanpouigt : 0.67791, 0.68157, 0.72420
bruteforce_loops_PM2R    : 0.82816, 0.83175, 0.83282
bruteforce_sum_PM2R      : 1.03322, 1.03378, 1.03562
Value: 6194

Size = 320, hi = 160, 64 loops
solutionE_TimBabych      : 0.07467, 0.07470, 0.07483
solution_TimBabych       : 0.08036, 0.08066, 0.08077
perm_sum_PM2R            : 0.21142, 0.21201, 0.25766
solution_python          : 0.22410, 0.22644, 0.22897
rank_sum_PM2R            : 0.22820, 0.22851, 0.22877
ltree_count_PM2R         : 0.24424, 0.24595, 0.24645
perm_radixI_PM2R         : 0.25690, 0.25710, 0.26191
perm_radixR_PM2R         : 0.26501, 0.26504, 0.26729
perm_fenwick_PM2R        : 0.33483, 0.33507, 0.33845
fenwick_inline_PM2R      : 0.34413, 0.34484, 0.35153
merge_count_BM           : 0.39875, 0.39919, 0.40302
fenwick_PM2R             : 0.40434, 0.40439, 0.40845
merge_PM2R               : 0.40814, 0.41531, 0.51417
count_inversions_NiklasB : 0.41681, 0.42009, 0.42128
count_inversion_mkso     : 0.47132, 0.47192, 0.47385
inv_cnt_ZheHu            : 0.54468, 0.54750, 0.54893
reversePairs_nomanpouigt : 0.76164, 0.76389, 0.80357
bruteforce_loops_PM2R    : 1.59125, 1.60430, 1.64131
bruteforce_sum_PM2R      : 2.03734, 2.03834, 2.03975
Value: 24959

Run 2

Size = 640, hi = 320, 8 loops
solutionE_TimBabych      : 0.04135, 0.04374, 0.04575
ltree_count_PM2R         : 0.06738, 0.06758, 0.06874
perm_radixI_PM2R         : 0.06928, 0.06943, 0.07019
fenwick_inline_PM2R      : 0.07850, 0.07856, 0.08059
perm_fenwick_PM2R        : 0.08151, 0.08162, 0.08170
perm_sum_PM2R            : 0.09122, 0.09133, 0.09221
rank_sum_PM2R            : 0.09549, 0.09603, 0.11270
merge_count_BM           : 0.10733, 0.10807, 0.11032
count_inversions_NiklasB : 0.12460, 0.19865, 0.20205
solution_python          : 0.13514, 0.13585, 0.13814

Size = 1280, hi = 640, 8 loops
solutionE_TimBabych      : 0.04714, 0.04742, 0.04752
perm_radixI_PM2R         : 0.15325, 0.15388, 0.15525
solution_python          : 0.15709, 0.15715, 0.16076
fenwick_inline_PM2R      : 0.16048, 0.16160, 0.16403
ltree_count_PM2R         : 0.16213, 0.16238, 0.16428
perm_fenwick_PM2R        : 0.16408, 0.16416, 0.16449
count_inversions_NiklasB : 0.19755, 0.19833, 0.19897
merge_count_BM           : 0.23736, 0.23793, 0.23912
perm_sum_PM2R            : 0.32946, 0.32969, 0.33277
rank_sum_PM2R            : 0.34637, 0.34756, 0.34858

Size = 2560, hi = 1280, 8 loops
solutionE_TimBabych      : 0.10898, 0.11005, 0.11025
perm_radixI_PM2R         : 0.33345, 0.33352, 0.37656
ltree_count_PM2R         : 0.34670, 0.34786, 0.34833
perm_fenwick_PM2R        : 0.34816, 0.34879, 0.35214
fenwick_inline_PM2R      : 0.36196, 0.36455, 0.36741
solution_python          : 0.36498, 0.36637, 0.40887
count_inversions_NiklasB : 0.42274, 0.42745, 0.42995
merge_count_BM           : 0.50799, 0.50898, 0.50917
perm_sum_PM2R            : 1.27773, 1.27897, 1.27951
rank_sum_PM2R            : 1.29728, 1.30389, 1.30448

Size = 5120, hi = 2560, 8 loops
solutionE_TimBabych      : 0.26914, 0.26993, 0.27253
perm_radixI_PM2R         : 0.71416, 0.71634, 0.71753
perm_fenwick_PM2R        : 0.71976, 0.72078, 0.72078
fenwick_inline_PM2R      : 0.72776, 0.72804, 0.73143
ltree_count_PM2R         : 0.81972, 0.82043, 0.82290
solution_python          : 0.83714, 0.83756, 0.83962
count_inversions_NiklasB : 0.87282, 0.87395, 0.92087
merge_count_BM           : 1.09496, 1.09584, 1.10207
rank_sum_PM2R            : 5.02564, 5.06277, 5.06666
perm_sum_PM2R            : 5.09088, 5.12999, 5.13512

Size = 10240, hi = 5120, 8 loops
solutionE_TimBabych      : 0.71556, 0.71718, 0.72201
perm_radixI_PM2R         : 1.54785, 1.55096, 1.55515
perm_fenwick_PM2R        : 1.55103, 1.55353, 1.59298
fenwick_inline_PM2R      : 1.57118, 1.57240, 1.57271
ltree_count_PM2R         : 1.76240, 1.76247, 1.80944
count_inversions_NiklasB : 1.86543, 1.86851, 1.87208
solution_python          : 2.01490, 2.01519, 2.06423
merge_count_BM           : 2.35215, 2.35301, 2.40023
rank_sum_PM2R            : 20.07048, 20.08399, 20.13200
perm_sum_PM2R            : 20.10187, 20.12551, 20.12683

Run 3
Size = 20480, hi = 10240, 4 loops
solutionE_TimBabych      : 1.07636, 1.08243, 1.09569
perm_radixI_PM2R         : 1.59579, 1.60519, 1.61785
perm_fenwick_PM2R        : 1.66885, 1.68549, 1.71109
fenwick_inline_PM2R      : 1.72073, 1.72752, 1.77217
ltree_count_PM2R         : 1.96900, 1.97820, 2.02578
count_inversions_NiklasB : 2.03257, 2.05005, 2.18548
merge_count_BM           : 2.46768, 2.47377, 2.52133
solution_python          : 2.49833, 2.50179, 3.79819

Size = 40960, hi = 20480, 4 loops
solutionE_TimBabych      : 3.51733, 3.52008, 3.56996
perm_radixI_PM2R         : 3.51736, 3.52365, 3.56459
perm_fenwick_PM2R        : 3.76097, 3.80900, 3.87974
fenwick_inline_PM2R      : 3.95099, 3.96300, 3.99748
ltree_count_PM2R         : 4.49866, 4.54652, 5.39716
count_inversions_NiklasB : 4.61851, 4.64303, 4.73026
merge_count_BM           : 5.31945, 5.35378, 5.35951
solution_python          : 6.78756, 6.82911, 6.98217

Size = 81920, hi = 40960, 4 loops
perm_radixI_PM2R         : 7.68723, 7.71986, 7.72135
perm_fenwick_PM2R        : 8.52404, 8.53349, 8.53710
fenwick_inline_PM2R      : 8.97082, 8.97561, 8.98347
ltree_count_PM2R         : 10.01142, 10.01426, 10.03216
count_inversions_NiklasB : 10.60807, 10.62424, 10.70425
merge_count_BM           : 11.42149, 11.42342, 11.47003
solutionE_TimBabych      : 12.83390, 12.83485, 12.89747
solution_python          : 19.66092, 19.67067, 20.72204

Size = 163840, hi = 81920, 4 loops
perm_radixI_PM2R         : 17.14153, 17.16885, 17.22240
perm_fenwick_PM2R        : 19.25944, 19.27844, 20.27568
fenwick_inline_PM2R      : 19.78221, 19.80219, 19.80766
ltree_count_PM2R         : 22.42240, 22.43259, 22.48837
count_inversions_NiklasB : 22.97341, 23.01516, 23.98052
merge_count_BM           : 24.42683, 24.48559, 24.51488
solutionE_TimBabych      : 60.96006, 61.20145, 63.71835
solution_python          : 73.75132, 73.79854, 73.95874

Size = 327680, hi = 163840, 4 loops
perm_radixI_PM2R         : 36.56715, 36.60221, 37.05071
perm_fenwick_PM2R        : 42.21616, 42.21838, 42.26053
fenwick_inline_PM2R      : 43.04987, 43.09075, 43.13287
ltree_count_PM2R         : 49.87400, 50.08509, 50.69292
count_inversions_NiklasB : 50.74591, 50.75012, 50.75551
merge_count_BM           : 52.37284, 52.51491, 53.43003
solutionE_TimBabych      : 373.67198, 377.03341, 377.42360
solution_python          : 411.69178, 411.92691, 412.83856

Size = 655360, hi = 327680, 4 loops
perm_radixI_PM2R         : 78.51927, 78.66327, 79.46325
perm_fenwick_PM2R        : 90.64711, 90.80328, 91.76126
fenwick_inline_PM2R      : 93.32482, 93.39086, 94.28880
count_inversions_NiklasB : 107.74393, 107.80036, 108.71443
ltree_count_PM2R         : 109.11328, 109.23592, 110.18247
merge_count_BM           : 111.05633, 111.07840, 112.05861
solutionE_TimBabych      : 1830.46443, 1836.39960, 1849.53918
solution_python          : 1911.03692, 1912.04484, 1914.69786

0

簡単なO(n ^ 2)の答えは、入れ子になったforループを使用して、反転ごとにカウンターをインクリメントすることです

int counter = 0;

for(int i = 0; i < n - 1; i++)
{
    for(int j = i+1; j < n; j++)
    {
        if( A[i] > A[j] )
        {
            counter++;
        }
    }
}

return counter;

ここで、より効率的なソリューションが必要だと思います。それについて考えます。


3
宿題の質問には、実際の解決策ではなく、役立つ提案をするのが最善です。男に魚を教える。
ジョーンズ博士、

3
これは、他のすべての生徒が最初に取得する明白な解決策です。彼らの教師は、より多くのポイントを獲得できるより良い実装を望んでいると思います。
mbillard 2008

必ずしもそうとは限りませんが、それはプログラミングコースのレベルに依存します。初心者にはそれほど簡単ではありません。
ジョーンズ博士、

彼らはおそらくn * log(n)ソリューションを必要としています
aderesh

0

O(N * log(N))時間の複雑さの要件を満たすC ++で考えられる解決策の1つは、次のとおりです。

#include <algorithm>

vector<int> merge(vector<int>left, vector<int>right, int &counter)
{

    vector<int> result;

    vector<int>::iterator it_l=left.begin();
    vector<int>::iterator it_r=right.begin();

    int index_left=0;

    while(it_l!=left.end() || it_r!=right.end())
    {

        // the following is true if we are finished with the left vector 
        // OR if the value in the right vector is the smaller one.

        if(it_l==left.end() || (it_r!=right.end() && *it_r<*it_l) )
        {
            result.push_back(*it_r);
            it_r++;

            // increase inversion counter
            counter+=left.size()-index_left;
        }
        else
        {
            result.push_back(*it_l);
            it_l++;
            index_left++;

        }
    }

    return result;
}

vector<int> merge_sort_and_count(vector<int> A, int &counter)
{

    int N=A.size();
    if(N==1)return A;

    vector<int> left(A.begin(),A.begin()+N/2);
    vector<int> right(A.begin()+N/2,A.end());

    left=merge_sort_and_count(left,counter);
    right=merge_sort_and_count(right,counter);


    return merge(left, right, counter);

}

通常のマージソートとはカウンターのみが異なります。


これは、以前に投稿されたJavaPythonのソリューションが同じアルゴリズムを使用しているように見えるため、ほとんど同じように見えます。
Bernhard Barker 14

0

RubyのO(n log n)ソリューションは次のとおりです。

def solution(t)
    sorted, inversion_count = sort_inversion_count(t)
    return inversion_count
end

def sort_inversion_count(t)
    midpoint = t.length / 2
    left_half = t[0...midpoint]
    right_half = t[midpoint..t.length]

    if midpoint == 0
        return t, 0
    end

    sorted_left_half, left_half_inversion_count = sort_inversion_count(left_half)
    sorted_right_half, right_half_inversion_count = sort_inversion_count(right_half)

    sorted = []
    inversion_count = 0
    while sorted_left_half.length > 0 or sorted_right_half.length > 0
        if sorted_left_half.empty?
            sorted.push sorted_right_half.shift
        elsif sorted_right_half.empty?
            sorted.push sorted_left_half.shift
        else
            if sorted_left_half[0] > sorted_right_half[0]
                inversion_count += sorted_left_half.length
                sorted.push sorted_right_half.shift
            else
                sorted.push sorted_left_half.shift
            end
        end
    end

    return sorted, inversion_count + left_half_inversion_count + right_half_inversion_count
end

そしていくつかのテストケース:

require "minitest/autorun"

class TestCodility < Minitest::Test
    def test_given_example
        a = [-1, 6, 3, 4, 7, 4]
        assert_equal solution(a), 4
    end

    def test_empty
        a = []
        assert_equal solution(a), 0
    end

    def test_singleton
        a = [0]
        assert_equal solution(a), 0
    end

    def test_none
        a = [1,2,3,4,5,6,7]
        assert_equal solution(a), 0
    end

    def test_all
        a = [5,4,3,2,1]
        assert_equal solution(a), 10
    end

    def test_clones
        a = [4,4,4,4,4,4]
        assert_equal solution(a), 0
    end
end

0

最適化された最適な方法は、マージソートを介してそれを解決することです。この場合、自分自身をマージし、左右の配列を比較することで必要な反転数を確認できます。左の配列の要素が右の配列の要素よりも大きい場合は常に、逆になります。

マージソートアプローチ:-

これがコードです。コードはマージソートとまったく同じmergeToParentですが、他の条件で反転をカウントしているメソッドの下のコードスニペットを除きます(left[leftunPicked] < right[rightunPicked])

public class TestInversionThruMergeSort {
    
    static int count =0;

    public static void main(String[] args) {
        int[] arr = {6, 9, 1, 14, 8, 12, 3, 2};
        

        partition(arr);

        for (int i = 0; i < arr.length; i++) {

            System.out.println(arr[i]);
        }
        
        System.out.println("inversions are "+count);

    }

    public static void partition(int[] arr) {

        if (arr.length > 1) {

            int mid = (arr.length) / 2;
            int[] left = null;

            if (mid > 0) {
                left = new int[mid];

                for (int i = 0; i < mid; i++) {
                    left[i] = arr[i];
                }
            }

            int[] right = new int[arr.length - left.length];

            if ((arr.length - left.length) > 0) {
                int j = 0;
                for (int i = mid; i < arr.length; i++) {
                    right[j] = arr[i];
                    ++j;
                }
            }

            partition(left);
            partition(right);
            mergeToParent(left, right, arr);
        }

    }

    public static void mergeToParent(int[] left, int[] right, int[] parent) {

        int leftunPicked = 0;
        int rightunPicked = 0;
        int parentIndex = -1;

        while (rightunPicked < right.length && leftunPicked < left.length) {

            if (left[leftunPicked] < right[rightunPicked]) {
                parent[++parentIndex] = left[leftunPicked];
                ++leftunPicked;

            } else {
                count = count + left.length-leftunPicked;
                if ((rightunPicked < right.length)) {
                    parent[++parentIndex] = right[rightunPicked];
                    ++rightunPicked;
                }
            }

        }

        while (leftunPicked < left.length) {
            parent[++parentIndex] = left[leftunPicked];
            ++leftunPicked;
        }

        while (rightunPicked < right.length) {
            parent[++parentIndex] = right[rightunPicked];
            ++rightunPicked;
        }

    }

}

入力配列をソートされた配列と比較できる別のアプローチ:- このDiabloの実装の回答。配列またはリストからn個の要素を削除することはlog(n ^ 2)であるため、これは推奨されるアプローチではありません。

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;


public class TestInversion {

    public static void main(String[] args) {
        
        Integer [] arr1 = {6, 9, 1, 14, 8, 12, 3, 2};
        
        List<Integer> arr = new ArrayList(Arrays.asList(arr1));
        List<Integer> sortArr = new ArrayList<Integer>();
        
        for(int i=0;i<arr.size();i++){
            sortArr.add(arr.get(i));
         
        }
        
            
        Collections.sort(sortArr);
        
        int inversion = 0;
        
        Iterator<Integer> iter = arr.iterator();
        
        while(iter.hasNext()){
            
            Integer el = (Integer)iter.next();
            int index = sortArr.indexOf(el);
            
            if(index+1 > 1){
                inversion = inversion + ((index+1)-1);
            }
            
            //iter.remove();
            sortArr.remove(el);
            
        }
        
        System.out.println("Inversions are "+inversion);
        
        
        

    }


}

0

サイズのリストで可能な反転の最大数はn、式によって一般化できます。

maxPossibleInversions = (n * (n-1) ) / 2

したがって、サイズの配列の6場合、可能な最大の反転は等しくなり15ます。

複雑さを実現するために、n lognマージソートの反転アルゴリズムを便乗させることができます。

一般的な手順は次のとおりです。

  • 配列を2つに分割します
  • mergeSortルーチンを呼び出します。左側のサブ配列の要素が右側のサブ配列の要素より大きい場合inversionCount += leftSubArray.length

それでおしまい!

これは簡単な例ですが、JavaScriptを使用して作成しました。

var arr = [6,5,4,3,2,1]; // Sample input array

var inversionCount = 0;

function mergeSort(arr) {
    if(arr.length == 1)
        return arr;

    if(arr.length > 1) {
        let breakpoint = Math.ceil((arr.length/2));
        // Left list starts with 0, breakpoint-1
        let leftList = arr.slice(0,breakpoint);
        // Right list starts with breakpoint, length-1
        let rightList = arr.slice(breakpoint,arr.length);

        // Make a recursive call
        leftList = mergeSort(leftList);
        rightList = mergeSort(rightList);

        var a = merge(leftList,rightList);
        return a;
    }
}

function merge(leftList,rightList) {
    let result = [];
    while(leftList.length && rightList.length) {
        /**
         * The shift() method removes the first element from an array
         * and returns that element. This method changes the length
         * of the array.
         */
        if(leftList[0] <= rightList[0]) {
            result.push(leftList.shift());
        }else{
            inversionCount += leftList.length;
            result.push(rightList.shift());
        }
    }

    while(leftList.length)
        result.push(leftList.shift());

    while(rightList.length)
        result.push(rightList.shift());

    console.log(result);
    return result;
}

mergeSort(arr);
console.log('Number of inversions: ' + inversionCount);

0

Swiftでマージソートを使用して配列の反転をカウントする実装:

スワップの数が増加することに注意してください

nSwaps += mid + 1 - iL 

(これは、配列の左側の相対的な長さから左側の現在の要素のインデックスを引いたものです)

...これは、配列の右側の要素がソートされるためにスキップする必要がある要素の数(反転数)だからです。

func merge(arr: inout [Int], arr2: inout [Int], low: Int, mid: Int, high: Int) -> Int {
    var nSwaps = 0;

    var i = low;
    var iL = low;
    var iR = mid + 1;

    while iL <= mid && iR <= high {
        if arr2[iL] <= arr2[iR] {
            arr[i] = arr2[iL]
            iL += 1
            i += 1
        } else {
            arr[i] = arr2[iR]
            nSwaps += mid + 1 - iL
            iR += 1
            i += 1
        }
    }

    while iL <= mid {
        arr[i] = arr2[iL]
        iL += 1
        i += 1
    }

    while iR <= high {
        arr[i] = arr2[iR]
        iR += 1
        i += 1
    }

    return nSwaps
}

func mergeSort(arr: inout [Int]) -> Int {
    var arr2 = arr
    let nSwaps = mergeSort(arr: &arr, arr2: &arr2, low: 0, high: arr.count-1)
    return nSwaps
}

func mergeSort(arr: inout [Int], arr2: inout [Int], low: Int, high: Int) -> Int {

    if low >= high {
        return 0
    }

    let mid = low + ((high - low) / 2)

    var nSwaps = 0;
    nSwaps += mergeSort(arr: &arr2, arr2: &arr, low: low, high: mid)
    nSwaps += mergeSort(arr: &arr2, arr2: &arr, low: mid+1, high: high)
    nSwaps += merge(arr: &arr, arr2: &arr2, low: low, mid: mid, high: high)

    return nSwaps
}

var arrayToSort: [Int] = [2, 1, 3, 1, 2]
let nSwaps = mergeSort(arr: &arrayToSort)

print(arrayToSort) // [1, 1, 2, 2, 3]
print(nSwaps) // 4

0

ほとんどの回答はに基づいてMergeSortいますが、これを解決する唯一の方法はO(nlogn)

いくつかのアプローチについて説明します。

  1. 使う Balanced Binary Search Tree

    • 重複する要素の頻度を格納するためにツリーを拡張します。
    • 目的は、挿入のためにツリーがルートからリーフにトラバースされるとき、より大きなノードを数え続けることです。

このようなもの。

Node *insert(Node* root, int data, int& count){
    if(!root) return new Node(data);
    if(root->data == data){
        root->freq++;
        count += getSize(root->right);
    }
    else if(root->data > data){
        count += getSize(root->right) + root->freq;
        root->left = insert(root->left, data, count);
    }
    else root->right = insert(root->right, data, count);
    return balance(root);
}

int getCount(int *a, int n){
    int c = 0;
    Node *root = NULL;
    for(auto i=0; i<n; i++) root = insert(root, a[i], c);
    return c;
}
  1. 使う Binary Indexed Tree
    • 合計BITを作成します。
    • 最後からループして、より大きな要素の数を見つけ始めます。
int getInversions(int[] a) {
    int n = a.length, inversions = 0;
    int[] bit = new int[n+1];
    compress(a);
    BIT b = new BIT();
    for (int i=n-1; i>=0; i--) {
         inversions += b.getSum(bit, a[i] - 1);
         b.update(bit, n, a[i], 1);
     }
     return inversions;
}
  1. 使う Segment Tree
    • 集計セグメントツリーを作成します。
    • 配列の最後からループし、[0, a[i]-1]更新と更新の間でクエリを実行しますa[i] with 1
int getInversions(int *a, int n) {
    int N = n + 1, c = 0;
    compress(a, n);
    int tree[N<<1] = {0};
    for (int i=n-1; i>=0; i--) {
        c+= query(tree, N, 0, a[i] - 1);
        update(tree, N, a[i], 1);
    }
    return c;
}

また、使用するときBITSegment-Tree、良いアイデアはCoordinate compression

void compress(int *a, int n) {
    int temp[n];
    for (int i=0; i<n; i++) temp[i] = a[i];
    sort(temp, temp+n);
    for (int i=0; i<n; i++) a[i] = lower_bound(temp, temp+n, a[i]) - temp + 1;
}

0

C ++Θ(n lg n)反転カウントを構成するペアを出力するソリューション。

int merge(vector<int>&nums , int low , int mid , int high){
    int size1 = mid - low +1;
    int size2= high - mid;
    vector<int>left;
    vector<int>right;
    for(int i = 0  ; i < size1 ; ++i){
        left.push_back(nums[low+i]);
    }
    for(int i = 0 ; i <size2 ; ++i){
        right.push_back(nums[mid+i+1]);
    }
    left.push_back(INT_MAX);
    right.push_back(INT_MAX);
    int i = 0 ;
    int j = 0;
    int start  = low;
    int inversion = 0 ;
    while(i < size1 && j < size2){
        if(left[i]<right[j]){
            nums[start] = left[i];
            start++;
            i++;
        }else{
            for(int l = i ; l < size1; ++l){
                cout<<"("<<left[l]<<","<<right[j]<<")"<<endl;
            }
            inversion += size1 - i;
            nums[start] = right[j];
            start++;
            j++;
        }
    }
    if(i == size1){
        for(int c = j ; c< size2 ; ++c){
            nums[start] = right[c];
            start++;
        }
    }
    if(j == size2){
        for(int c =  i ; c< size1 ; ++c){
            nums[start] = left[c];
            start++;
        }
    }
    return inversion;
}
int inversion_count(vector<int>& nums , int low , int high){
    if(high>low){
        int mid = low + (high-low)/2;
        int left = inversion_count(nums,low,mid);
        int right = inversion_count(nums,mid+1,high);
        int inversion = merge(nums,low,mid,high) + left + right;
        return inversion;
    }
    return 0 ;
}

-1

出力にコピーされた数値が正しい配列からのものである場合は、マージステップの増分カウンターでmergesortを使用します。


各要素のカウンターを(おそらく1ずつ)増やすと、逆数が少なくなりすぎます。
Bernhard Barker 14

-1

私は最近これをRで行わなければなりませんでした:

inversionNumber <- function(x){
    mergeSort <- function(x){
        if(length(x) == 1){
            inv <- 0
        } else {
            n <- length(x)
            n1 <- ceiling(n/2)
            n2 <- n-n1
            y1 <- mergeSort(x[1:n1])
            y2 <- mergeSort(x[n1+1:n2])
            inv <- y1$inversions + y2$inversions
            x1 <- y1$sortedVector
            x2 <- y2$sortedVector
            i1 <- 1
            i2 <- 1
            while(i1+i2 <= n1+n2+1){
                if(i2 > n2 || i1 <= n1 && x1[i1] <= x2[i2]){
                    x[i1+i2-1] <- x1[i1]
                    i1 <- i1 + 1
                } else {
                    inv <- inv + n1 + 1 - i1
                    x[i1+i2-1] <- x2[i2]
                    i2 <- i2 + 1
                }
            }
        }
        return (list(inversions=inv,sortedVector=x))
    }
    r <- mergeSort(x)
    return (r$inversions)
}

1
@Dukelingコメントを取り下げるが、反対票を取り消すきっかけは何ですか?
ミューズフル2014年
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.