バブルソートに加えて、Python の組み込みソートアルゴリズムをご存知ですか?

バブルソートに加えて、Python の組み込みソートアルゴリズムをご存知ですか?

プログラミング アルゴリズムに関して、多くの読者が学校で最初に学ぶのはバブル ソートかもしれませんが、Python の組み込みソート アルゴリズム list.sort() の原理を本当にご存知ですか? これは、時間計算量が O(n log n) の高速で安定したソート アルゴリズム Timsort を使用します。このアルゴリズムの目的は、大規模な実データを処理することです。

Timsort は、実際のデータに対して非常に効率的なソート アルゴリズムです。 Tim Peters は 2001 年に Python プログラミング言語用に Timsort を作成しました。 Timsort は、まずソートしようとしているリストを分析し、その分析に基づいて適切なソリューションを選択します。

Timsort は発明されて以来、Python、Java、Android プラットフォーム、GNU Octave のデフォルトのソート アルゴリズムとなっています。

画像ソース: http://bigocheatsheet.com/

Timsort のソート時間は Mergesort と似ており、他のほとんどのソート アルゴリズムよりも高速です。 Timsort は、実際には挿入ソートとマージソートの手法を借用しています。その具体的な処理については、後ほど詳しく説明します。

Peters 氏は、実際のデータ セットに存在する「自然実行」と呼ばれる多数の順序付けられた要素を利用するために Timsort を設計しました。要約すると、Timsort は最初にすべてのデータを走査し、データ内のソートされたパーティションを見つけます。各パーティションは実行と呼ばれ、次にルールに従ってこれらの実行を 1 つにマージします。

配列の要素数が 64 未満です。

ソートする配列の要素数が 64 未満の場合、Timsort は挿入ソートを実行します。挿入ソートは、小さなリストに対して最も効率的な単純なソートです。大きなリストでは遅くなりますが、小さなリストでは速くなります。挿入ソートの考え方は次のとおりです。

  • 要素を一つずつ表示する
  • 正しい位置に要素を挿入してソートされたリストを作成します

次のトレーステーブルは、挿入ソートがリスト[34, 10, 64, 51, 32, 21]をどのようにソートするかを示しています。

この例では、左から右へのソートを開始します。太字の数字は、新しくソートされたサブ配列を表します。元の配列の各要素をソートする際に、ソートされたサブ配列を右から左に比較し、適切な位置に挿入します。挿入ソートを説明するために動画を使用します。

自然に順序付けられたブロック: 実行

リストが 64 要素より大きい場合、Timsort アルゴリズムは最初にリストを走査し、「厳密に」昇順または降順の部分を探します (実行)。セクションが減少している場合、Timsort はそのセクションを反転します。したがって、run が減少している場合は、次のようになります (run は太字で表示されます)。

減少がない場合は、次のようになります。

minrun のサイズは配列のサイズに基づいて決定されます。 Timsort アルゴリズムは、ランダム配列内のほとんどの実行が minruns になるようにこれを選択します。実行の長さ N が 2 の倍数と等しいかわずかに小さい場合、2 つの配列を結合する方が効率的です。 Timsort は、minrun が 2 の倍数と等しいか、わずかに小さいことを保証するために minrun を選択します。

アルゴリズムは minrun の範囲を 32 ~ 64 に選択します。 minrun で割ると、元の配列の長さは 2 の倍数と等しくなるか、わずかに小さくなります。

実行の長さが minrun より短い場合は、minrun から実行の長さを引いた値が計算されます。実行後に新しい要素を run (minrun - run) の外側に配置し、挿入ソートを実行して、長さが minrun と同じ新しい実行を作成できます。

minrun が 63 で、run の長さが 33 の場合、63 - 33 = 30 個の新しい要素を取得できます。そして、これらの30個の新しい要素はrunの末尾に新しい要素として追加されるため、runの34番目の要素であるrun[33]には30個の子要素があります。 ***長さ 63 の新しい実行を作成するには、最後の 30 要素に対して挿入ソートのみが必要です。

この部分が完了すると、リスト内に並べ替えられた実行セットが作成されます。

マージ

Timsort は、実行をマージするためにマージ ソートを実行する必要があり、マージ ソートの実行中に安定性とバランスが維持されるようにする必要があります。安定性を維持するために、等しい値を持つ 2 つの要素は交換しないでください。これにより、リスト内の元の位置が維持されるだけでなく、アルゴリズムも高速化されます。

Timsort が実行を見つけると、それらはスタックに追加されます。単純なスタックは次のようになります。

皿が積み重ねられているところを想像してください。皿を下から取ることはできません。上から取る必要があります。積み重ねた場合も同様です。

異なる実行をマージする場合、Timsort は 2 つの競合する要件のバランスを取ろうとします。一方では、後で出現する可能性のあるパターンを活用するために、マージをできるだけ遅らせたいと考えています。しかし、メモリ階層でまだ高いランクにある、先ほど発見した実行を活用するために、できるだけ早くマージすることを希望します。また、マージされていない実行を記憶するとメモリが消費され、スタックのサイズが固定されているため、マージを「あまり」遅らせることもできません。

妥協案を見つけるために、Timsort はスタック上の最新の 3 つの項目を追跡し、それらのスタック項目に当てはまる必要がある 2 つのルールを作成します。

  • A > B + C
  • B > C

ここで、A、B、C はスタック内の最新の 3 つの項目です。

ティム・ピーターズ自身の言葉:

良い妥協策は、スタック項目に対して 2 つの不変量を維持することです。ここで、A、B、C は、右端の 3 つのマージされていないフラグメントの長さです。

通常、長さの異なる隣接するランを結合することは困難です。さらに難しいのは、安定性を維持しなければならないことです。この問題を解決するために、Timsort は一時メモリを設定します。 2 つの実行 (runA と runB を同時に呼び出す) のうち小さい方をこの一時メモリに配置します。

GALLOPING(ギャロッピングモード)

Timsort が A と B をマージすると、1 回の実行が複数回連続して「勝利」したことに気付きます。実行 A の値が実行 B の値よりも大幅に小さい場合、実行 A は元の位置に戻ります。これら 2 つの実行をマージすると、膨大な作業量になり、何の結果も得られません。

通常、データには事前に設定された内部構造があります。 Timsort は、実行 A の値が実行 B の値よりもほとんど低い場合、A の値は B よりも低くなる可能性が高いと想定します。

その後、ティムソートはギャロップモードに入ります。 A[0]とB[0]をチェックする代わりに、TimsortはA[0]内でのB[0]の適切な位置をバイナリ検索で探します。このようにして、Timsort はセクション A 全体を所定の位置に移動できます。次に、TimsortはBでA[0]の適切な場所を検索します。その後、Timsort は B 全体を直ちに所定の位置に移動します。

TimsortはB[0](値は5)をチェックし、バイナリ検索を使用してA内の正しい位置を見つけます。

現在、B[0]はAのリストの最後にあり、TimsortはBの正しい位置にA[0](1)があるかどうかをチェックし、1の位置を確認します。この数字は B の前にあります。これで、B は A の後ろにあり、A は B の前にあることがわかります。

B[0]がAの先頭に非常に近い位置にある場合(またはその逆)、この操作は不要です。 Timsort もこれに気づき、連続する A または B の数を増やすことで、ギャロップ モードに入るしきい値を上げます。ギャロッピング モードが意味をなす場合、Timsort を使用すると、そのモードに再び入るのが簡単になります。

簡単に言うと、Timsort は次の 2 つの非常に優れた機能を備えています。

  • 内部構造があらかじめ設定されている配列はパフォーマンスが良好です
  • 安定した選別を維持できる

これまでは、安定したソートを実現するために、リスト内の項目を整数に圧縮し、タプルの配列にソートする必要がありました。

コード

以下のソース コードは、私と Nanda Javarma の作業に基づいています。ソースコードは完全ではなく、Python の公式 sort() ソースコードとも似ていません。これは私が実装した単純化された Timsort ですが、これにより Timsort の全体像を把握することができます。さらに、Python に組み込まれている Timsort アルゴリズムは C で正式に実装されているため、パフォーマンスが向上します。

Timsort のオリジナル ソース コード:

https://github.com/python/cpython/blob/master/Objects/listobject.c をご覧ください。

  1. # このコードに基づいています https://gist.github.com/nandajavarma/a3a6b62f34e74ec4c31674934327bbd3
  2. #ブランドン・スケリット
  3. # https://skerritt.tech
  4.  
  5. def binary_search(配列、項目、開始、終了):
  6. 開始== 終了の場合:
  7. the_array[start] > itemの場合:
  8. 戻る 開始
  9. それ以外:
  10. 開始 + 1 を返す
  11. 開始>終了の場合:
  12. 戻る 開始
  13.  
  14. 中間=ラウンド((開始 + 終了) / 2)
  15.  
  16. 配列[mid] <  アイテム:  
  17. バイナリ検索(配列、項目、中間 + 1、終了)を返します
  18.  
  19. elif the_array[mid] >項目:
  20. binary_search(the_array, item, start, mid - 1) を返します
  21.  
  22. それ以外:
  23. 戻る途中
  24.  
  25. 「」
  26. 配列のサイズが小さい場合や、
  27. 「実行」のサイズが小さい
  28. 「」
  29. def inserting_sort(配列):
  30. l = len (配列)
  31. 範囲(1, l)内のインデックスの場合:
  32. = the_array [インデックス]
  33. pos = binary_search (配列、値、0、インデックス - 1)
  34. the_array the_array = the_array[:pos] + [値] + the_array[pos:index] + the_array[index+1:]
  35. 配列を返す
  36.  
  37. def merge(左、右):
  38. 「2つのソートされたリストを受け取り、それらを比較して1つのソートされたリストを返します。
  39. 要素を 1 つずつ選択します。
  40. [1、2、3、4、5、6]
  41. 「」
  42. 残っていない場合:
  43. 右に戻る
  44. 正しくない場合:
  45. 左に戻る
  46. 左[0]の場合<  [0]:
  47. [left[0]] + merge(left[1:], right) を返す
  48. [right[0]] + merge(left, right[1:]) を返す
  49.  
  50. def timsort(配列):
  51. 実行、 sorted_runs = []、[]
  52. len長さ= len(配列)
  53. new_run = [the_array[0]]
  54.  
  55. # 1から配列の長さまでの範囲のすべてのiについて
  56. iが範囲(1, 長さ)内にある場合:
  57. # i がリストの末尾にある場合
  58. i == 長さ - 1 の場合:
  59. new_run.append(配列[i])
  60. 実行します。追加(new_run)
  61. 壊す
  62. # 配列のi番目の要素がその前の要素より小さい場合
  63. 配列[i] <  配列[i-1]:
  64. # new_run が None (NULL) に設定されている場合
  65. new_runでない場合は:
  66. 実行します。追加([the_array[i]])
  67. new_run.append(配列[i])
  68. それ以外:
  69. 実行します。追加(new_run)
  70. 新しい実行= []
  71. # そうでない場合、等しいかそれ以上の場合
  72. それ以外:
  73. new_run.append(配列[i])
  74.  
  75. # 実行中のすべての項目に対して、挿入ソートを使用して追加します
  76. 実行中のアイテムの場合:
  77. sorted_runs.append(挿入ソート(アイテム))
  78.  
  79. # sorted_runs 内の各実行をマージします
  80. ソートされた配列= []
  81. sorted_runs 内の実行の場合:
  82. sorted_array = merge (sorted_array, 実行)
  83.  
  84. print(ソートされた配列)
  85.  
  86. timsort([2, 3, 1, 5, 6, 7])

Timsort は実際には Python に組み込まれているため、このコードは概念的な説明としてのみ機能します。 Timsort を使用するには、Python で次のように記述するだけです。

  1. リスト.ソート()

または:

  1. ソート済み(リスト)

Timsort がどのように動作するかを把握し、理解したい場合は、ぜひ自分で実装してみることをお勧めします。

オリジナルリンク:

https://hackernoon.com/timsort-the-fastest-sorting-algorithm-youve-never-heard-of-36b28417f399

[この記事は51CTOコラム「Machine Heart」、WeChatパブリックアカウント「Machine Heart(id:almosthuman2014)」によるオリジナル翻訳です]

この著者の他の記事を読むにはここをクリックしてください

<<:  二足歩行ロボットは撮影以外にも応用シーンが多すぎて問題になっている

>>:  GoogleのAIオープンソース成果物は3年前に誕生し、想像もつかないような多くの場所で使用されている。

ブログ    
ブログ    
ブログ    
ブログ    
ブログ    

推薦する

AI システムを監査する際に尋ねるべき 9 つの質問

翻訳: ブガッティ企画:千山ほとんどの企業は、記録システムの IT 監査を毎年実施しています。しかし...

AIは意識を発達させ始めているのでしょうか? OpenAI主任科学者の発言が論争を巻き起こし、大物の間で論争を巻き起こした

この記事はAI新メディアQuantum Bit(公開アカウントID:QbitAI)より許可を得て転載...

よく使われる 3 つの C# ソート アルゴリズム

C# アルゴリズムは、C# 言語学習の重要な部分です。C# ソート アルゴリズムは、言語の基礎とデー...

二足歩行ロボット「キャシー」が機械学習を使って5kmのジョギングを完走

ロボット工学の世界では 4 年というのは長い期間ですが、特にオレゴン州立大学 (OSU) が開発した...

AI人材の世界的な需要が急増、一部の職種では年間40万ドル近くを稼ぐ

AI業界の急速な発展に伴い、テクノロジー業界におけるAI人材の需要も高まっています。 USA Tod...

...

映画に騙されないでください。人工知能はどうやって人間を殺すのでしょうか?

どの国が終末的な災害映画を撮影したとしても、人工知能はさまざまな大量破壊兵器を操作して人類と戦い、最...

信じられない! XiaoIceのデジタルツイン仮想人物は70日間ライブ放送されましたが、誰もそれが本物の人間ではないことに気づきませんでした

[[441368]]中国ビジネスニュースは70日間生放送されましたが、アンカーがデジタルツインの仮想...

ダブルイレブンがやって来ます!物流ドローンはどれくらい遠くにあるのでしょうか?

荷物が届かず悲しい思いをしたことはありませんか? 荷物が届くまで長い間待たされるのではないかと不安に...

配達員は失業してしまうのでしょうか?美団、無人配達システム構築のため650億元を調達

最近、国内のインターネット大手はコミュニティグループ購入の分野で激しい競争を繰り広げており、アリババ...

...

人工知能が企業のバックオフィスへの参入を加速

人工知能は、あらゆる種類の企業のバックオフィスに大きく浸透しつつあります。バックオフィスは、ビジネス...

データマイニングの専門家がプログラムアルゴリズムを使って人生の選択をする

[[118153]]毎年、就職活動の時期になると、どうやって内定を選んだらいいのか、テンセントに行く...

...

SQL Server 2005 のデータ マイニング アルゴリズム拡張メソッド

SSAS は 9 つのデータ マイニング アルゴリズムを提供していますが、実際の問題に基づいて適切な...