上位 10 の古典的なソート アルゴリズムの詳細な説明: シェル ソート、マージ ソート、クイック ソート

上位 10 の古典的なソート アルゴリズムの詳細な説明: シェル ソート、マージ ソート、クイック ソート

[[378304]]

上位 10 の古典的なソート アルゴリズム - シェル ソート、マージ ソート、クイック ソート 序文 これは、上位 10 の古典的なソート アルゴリズムの詳細な説明の 2 番目の記事です。 これは、最初の記事へのリンクです: 上位 10 の古典的なソート アルゴリズムの詳細な説明 (I) バブル ソート、選択ソート、挿入ソート。 まだ読んでいない友人は、見てみてください。

毎回説明するときは、まずテキストを使用してアルゴリズムの基本的な考え方を理解していただきます。次に、画像を使用してソート アルゴリズムの動的な実行プロセスを分析し、アルゴリズムをよりよく理解していただきます。

毎回絵を描くのはとても面倒で時間がかかります。ですから、この記事がうまく書かれている、あるいは役に立つと思ったら、トリプルクリックするか、私の公開アカウント「萌萌哒的擤擤」をフォローしてください。これは私にとって本当に重要です。皆さん、ありがとうございます。

さっそく本文を始めましょう!

1- シェルソート

アルゴリズムのアイデア:

実際、シェル ソートの考え方は非常に単純です。シェル ソートの基本的な考え方は、最初の記事で説明した挿入ソートの基本的な考え方と同じですが、「シェル ソートには、挿入ソートに比べてステップが 1 つ多くあります。それは、ステップ長を決定することです。」 以前の挿入ソート プロセスでは、ステップ長は 1 に固定されていましたが、シェル ソートではステップ長は固定されておらず、「配列の長さの半分から始まり、ステップ長が 1 に達するまで、各グループのソート後にステップ長が半分になります。」 この時点で、ソートが完了します。

そうは言っても、シェルソートをよりよく理解できるように図を使用しましょう。

上の図を読んだ後、誰もがヒルソートアルゴリズムの考え方を基本的に理解したと思いますので、ヒルソートアルゴリズムの特徴を分析してみましょう。

  • Shell ソート アルゴリズムは不安定です。ここで、このような疑問が生じるかもしれません。Shell ソート アルゴリズムの本質は挿入ソートですが、ステップの長さを決定するための追加のステップがあります。挿入ソートは安定しているのに、Shell ソートは不安定なのはなぜでしょうか。実は、鍵となるのはグループ化後です。グループ内の挿入ソートが間違いなく安定していることは誰もが知っています。鍵となるのは、Shell ソート プロセスで複数のグループ化が行われることです。すると、前のグループ化では安定しますが、次のグループ化では不安定になります。

あまり多くを語るよりも、直接例を挙げた方が理解しやすいでしょう。

  • シェルソートは、時間計算量が O(N*N) を突破した「最初の」アルゴリズムであり、非常に意義深いものです。時間計算量はわずか O(N*log N) です。

アルゴリズム図:

ここに画像の説明を挿入

コード例:

「アルゴリズムの考え方に従って変更されないバージョン」:

  1. 公共 静的void main(String[] args) {
  2. int []数値 ={7,4,9,3,2,1,8,6,5,10};
  3. 長い開始時間 = System.currentTimeMillis();
  4. //ステップの長さを指定する
  5. ( intステップ = 数値の長さ / 2; ステップ > 0; ステップ / = 2) {
  6. System.out.println ( "ステップサイズ" +step+ "でのグループソート:" );
  7. //ステップサイズが決定したら、グループをバッチで挿入してソートする必要があります
  8. ( int l=0;l<step;l++)の場合{
  9. // 挿入ソートコード
  10. ( int i=l+step;i<num.length;i+=step)の場合{
  11. 整数  temp =数値[i];
  12. 整数j = i;
  13. while(j>0&& temp <num[j-step]) {
  14. num[j]=num[jステップ];
  15. j-=ステップ;
  16. //ここで注意すべきことは、jステップが境界を越える可能性があるため、引き続き判断する必要があることです。
  17. // 以前の挿入ソートでは、ステップサイズは常に 1 だったので、while ループでブロックしていましたが、ステップサイズは変更されます。
  18. //したがって、ここで事前に判断する必要があります。そうしないと、Jinhui は範囲外の配列を持つことになります。
  19. if(jステップ<0)
  20. 壊す;
  21. }
  22. もし(j!=i) {
  23. num[j] =一時;
  24. }
  25. }
  26. System.out.println ( "" +l+ "グループソート:" );
  27. ( int k=0;k<num.length;k++)の場合
  28. システム.out.print (num[k]+ "" );
  29. System.out.println( ) ;
  30. }
  31. System.out.println( ) ;
  32. }
  33. 長い endTime=System.currentTimeMillis();
  34. System.out.println ( "プログラム実行時間:" +(endTime-startTime)+ "ms" );
  35. }

しかし、ヒルソートの考え方に本当に従うと、3層のforループでは明らかに時間の計算量がこれまで経験した最悪のケース、つまりO(N*N*N)に達することがわかります。そのため、主にグループ化ソートプロセスを改善するための改善が必要です。以前は、ステップサイズを決定した後、forループを使用して循環グループをソートしていました。ここでは、次のforループと一緒に循環グループ化を直接実行するように変更します。

改善されたコード:

  1. 公共 静的void main(String[] args) {
  2. int []数値 ={7,4,9,3,2,1,8,6,5,10};
  3. 長い開始時間 = System.currentTimeMillis();
  4. ( intステップ = 数値の長さ / 2; ステップ > 0; ステップ / = 2) {
  5. System.out.println ( "ステップサイズ" +step+ "でのグループソート:" );
  6. System.out.println ( "ループグループのソート:" );
  7. ( int j = ステップ; j < 数値の長さ; j++) {
  8. 整数  temp =数値[j];
  9. 整数k=j;
  10. while(kステップ>=0&& temp <num[kステップ]) {
  11. num[k]=num[kステップ];
  12. k-=ステップ;
  13. }
  14. num[k] =温度;
  15. ( int l=0;l<num.length;l++)の場合
  16. システム.out.print (num[l]+ " " );
  17. System.out.println( ) ;
  18. }
  19. }
  20. 長い endTime=System.currentTimeMillis();
  21. System.out.println ( "プログラム実行時間:" +(endTime-startTime)+ "ms" );
  22. }

改良されたアルゴリズムは2層のforループを使用するため、時間の計算量はO(N*log N)に達する。

計算量分析: ヒルソートの基本的な考え方を理解した後、その時間計算量と空間計算量を分析する必要があります。

  • 時間計算量

それぞれのケースにおけるシェルソートの時間計算量は、主に要素の数とグループ化の数に依存します。グループ化の数はちょうどlog Nであることがわかったので、シェルソートの時間計算量はO(N*log N)だけであることがわかります。

  • 空間の複雑さ

ソート処理全体を通じてキーの保存場所を 1 つだけ追加するだけなので、シェル ソートの空間計算量は O(1) という一定レベルであることがわかります。

2-マージソート

アルゴリズムの考え方:マージソートの考え方の本質は分割することです。シーケンス全体を複数のシーケンスに分割し、各シーケンスを最初にソートします。これは「分割の考え方で分割する」という考え方であり、「マージソートで戻す」という考え方でもあります。

次に、すべてのシーケンスを統合します。これは「ソート内のマージ」であり、「ソート内のマージ」でもあります。アイデアはこれで終わりですが、問題を解決することはできません。理解を助けるために、次の図を使用しましょう。

図を見ると、上記の分割プロセスとマージプロセスは再帰と非常に似ていることがわかります。これらはすべて、特定の終了条件に従って実行されます。つまり、これはマージソートが「再帰」という考え方を通じて記述できることを示唆しています。

これでマージソートの基本的な考え方はほぼ理解できました。次に、マージソートの特徴を見てみましょう。

上記のデモからわかるように、マージソートは安定しています。

マージソートは大量のメモリ空間を消費します。このメモリ空間はバブルソートなどのソートアルゴリズムと比較されます。バブルソートのメモリ空間は一定レベルでしか存在しないのに対し、マージソートは線形メモリ空間を消費するため、「大量」という形容詞が使われます。消費されるメモリ空間はソートするシーケンスの長さに相当します。つまり、O(n) の複雑さです。

アルゴリズム図:

ここに画像の説明を挿入

コード例:

読者が理解しやすいように、重要なコードにコメントを追加しました。

  1. 公共 静的  int []ソート( int []num) {
  2. //分割後の配列に要素が1つしかない場合、
  3. // すると、マージ処理を開始できることになるので、直接戻ります。
  4. 数値の長さ<2の場合
  5. 数値を返します
  6. int中間 = 数値の長さ / 2;
  7. //左と右のシーケンスをインターセプトする
  8. int []=Arrays.copyOfRange(num, 0, middle);
  9. int [] right =Arrays.copyOfRange(num, middle, num.length);
  10. merge(sort( left ), sort( right ))を返します
  11. }
  12. 公共 静的  int [] マージ ( int [] int []) {
  13. int []num = 新しいint [.length +.length];
  14. 整数i=0,j=0,k=0;
  15. //終了条件は&&であり、そのうちの1つが満たされない限り、ループは終了することに注意してください。
  16. while(i<左辺の長さ&&j<右辺の長さ) {
  17. if([i] <[j] )
  18. num[k++] =[i++];
  19. それ以外  
  20. num[k++] =[j++];
  21. }
  22. //上記のループを抜けると、値を持つシーケンスは1つだけであることを意味します
  23. //各シーケンスを再度チェックする必要があり、次の2つのループは相互に排他的であるため、そのうちの1つだけが実行されます。
  24. //またはどちらも実行しない
  25. while(i<左 .長さ) {
  26. num[k++] =[i++];
  27. }
  28. while(j<.長さ) {
  29. num[k++] =[j++];
  30. }
  31. ( int l=0;l<num.length;l++)の場合
  32. システム.out.print (num[l]+ "" );
  33. System.out.println( ) ;
  34. 数値を返します
  35. }
  36. 公共 静的void main(String[] args) {
  37. int []数値 ={7,4,9,3,2,1,8,6,5,10};
  38. 長い開始時間 = System.currentTimeMillis();
  39. num=ソート(num);
  40. // ( int i=0;i<num.length;i++)の場合{
  41. // システム.out.print (num[i]+ "" );
  42. // }
  43. // System.out.println () ;
  44. 長い endTime=System.currentTimeMillis();
  45. System.out.println ( "プログラム実行時間:" +(endTime-startTime)+ "ms" );
  46. }

ここではすべての要素を説明するつもりはありません。区間の左半分の要素を並べ替えるプロセスだけを説明します。右半分は想像力を働かせて考えてください。これは難しくないはずです。

これで、シーケンスの長さが 2 の累乗でない場合、シーケンスのその後の分割によって上記と同様の状況が発生することが誰もが理解できるはずです。結局のところ、区間を分割するプロセス全体は、「区間の中心をバイナリ ツリーに分割する」ことに似ています。

複雑性分析:

マージソートの基本的な考え方を理解した後、その時間計算量と空間計算量を分析する必要があります。

  • 時間計算量

実際、上記のアルゴリズムから、for ループを使用せず、再帰を通じてループ問題を解決していることがはっきりとわかります。したがって、より効率的です。上記のデモンストレーション プロセスで書いたように、この実行のレイヤー数は 2 * log N で、各レイヤーで操作される要素は N であるため、時間計算量は 2 * N * log N ですが、定数は無視できるため、時間計算量は O(N*log N) に抑えられます。以前のアルゴリズムの時間計算量 O(N*N) と比較すると、「時間計算量が大幅に削減されている」ことがわかります。また、この時間計算量は「平均的なケースだけでなく、最悪のケースにも適用できます」。

  • 空間の複雑さ

また、ソート処理全体では、2 次ソート後のシーケンスを保存するために、ソートされたシーケンスの長さに等しいスペースを追加する必要があることがわかります。そのため、必要なスペースの複雑さは線形レベル O(n) です。以前のソート アルゴリズムと比較すると、この複雑さは確かに少し大きくなります。ただし、全体的な時間複雑さが大幅に削減されていることも明らかです。これは、明らかに時間と引き換えにスペースを犠牲にする方法です。最初の記事で説明した HashMap と比較してください。

3- クイックソート

アルゴリズムのアイデア:

クイックソートのアルゴリズムの考え方も理解しやすいです。しかし、言葉で説明すると少し難しいかもしれませんので、できるだけ簡単に説明します。それでも理解できなくても大丈夫です。絵を使ったデモンストレーションで理解を深めていきます。

クイックソートの考え方は、ソートするたびに「参照値を選択する」ことです。この参照値を選択した後、さらに 2 つの「ポインタ」が必要になります。このポインタは C++ のポインタではありませんが、機能は似ています。主に位置をマークするのに役立ちます。「これら 2 つのポインタは、ソートするシーケンスの先頭要素と末尾要素をそれぞれ指します。」

まず、「末尾の要素から始めて、右から左に参照値より大きくない最初の要素を検索します」。それを見つけたら、まず、以前に末尾の要素を指していたポインタを要素の位置にポイントし、次に参照値を先ほど見つけた要素と交換します。

このステップの後は、「先頭要素から始めて、左から右に参照値より小さくない最初の要素を検索する」必要があります。要素を見つけた後も、上記の手順に従います。まず、以前に先頭要素を指していたポインターを要素に向け、次に参照値を要素と交換します。

上記の手順を、「ヘッド ポインターとテール ポインターが出会うまで繰り返します。出会うと、最初のソートが完了したことを意味します」。最初のソートが完了した後、シーケンスが「ベンチマーク値の左側にあるすべての要素がベンチマーク値以下であり、ベンチマーク値の右側にあるすべての要素がベンチマーク値以上である」状態であることがわかります。

後続のソートでは、ベンチマーク値の左側と右側のシーケンスに対して上記の操作を実行するだけです。

さて、アルゴリズムのテキスト説明は完了しました。もちろん、この時点で多くの友人は間違いなく「何を言っているの?」と思うでしょう。それは問題ではありません。いつものように、写真を使用して話しましょう。

ここでは最初のソート処理のみを説明しますが、その後のソート処理は自分で理解できると思います。クイックソートの基本的なアルゴリズムの考え方を理解した後、クイックソートの特徴について少し説明する必要があります。

クイックソート自体は不安定です。まずは不安定なクイックソートがどのようなものかを理解しましょう。

上の図から、クイックソートが不安定な理由がわかります。

「クイック ソートにも極端なケースがあります」。つまり、クイック ソートが「すでに順序付けられたシーケンス」をソートする場合、時間の計算量は O(n*n) に急上昇し、これが最悪の時間の計算量となります。この状況は、実際に自分でシミュレーションすることで知ることができます。ここではあまり詳しく説明しません。アルゴリズム図:

ここに画像の説明を挿入

コード例:

読者が理解しやすいように、重要なコードにコメントを追加しました。

  1. 公共 静的voidソート( int []num, int   int  ) {
  2. if(<) {
  3. 整数 キー=num[];
  4. int i =;
  5. int j =;
  6. //この部分はアルゴリズムの核となる考え方です
  7. i<j の場合
  8. // 右から左へ、参照値より大きくない最初の要素を検索します
  9. while(i<j&&num[j]>=キー) {
  10. j --;  
  11. }
  12. i<jの場合{
  13. num[i] = num[j];
  14. }
  15. // 左から右へ、参照値より小さくない最初の要素を検索します
  16. i<j&&num[i]<=キーの場合
  17. 私は++;
  18. }
  19. i<jの場合{
  20. num[j] = num[i];
  21. }
  22. }
  23. num[i]=キー;
  24. ( int k=0;k<num.length;k++)の場合{
  25. システム.out.print (num[k]+ "" );
  26. }
  27. System.out.println( ) ;
  28. //残りのシーケンスを再帰的にソートし続ける
  29. ソート(num, left , i-1);
  30. ソート(num, i+1,);
  31. }
  32. }
  33. 公共 静的void main(String[] args) {
  34. int []数値 ={7,4,9,3,2,1,8,6,5,10};
  35. 長い開始時間 = System.currentTimeMillis();
  36. ソート(数値、0、数値の長さ-1);
  37. 長い endTime=System.currentTimeMillis();
  38. System.out.println ( "プログラム実行時間:" +(endTime-startTime)+ "ms" );
  39. }

上記の動的デモンストレーション図は非常にわかりやすく示されているため、デモンストレーションは描きません。

複雑性分析:

クイックソートの基本的な考え方を理解した後、その時間計算量と空間計算量を分析する必要があります。

  • 時間計算量

実際、上記のアルゴリズムから、 for ループを使用せず、再帰によってループ問題を解決したことがはっきりとわかります。したがって、より効率的です。

上で示したように、平均時間計算量は O(N * log N) ですが、前述したように、クイック ソートには「すでに順序付けられたシーケンスのクイック ソート」という極端なケースがあり、これはバブル ソートに似ており、時間計算量は O(N * N) です。これは注意を払う必要があることですが、ほとんどの場合、クイック ソートは依然として最も効率的なソート アルゴリズムです。

  • 空間の複雑さ

また、ソートプロセス全体では、各ソートサイクルでキーを格納するためのスペースを追加する必要があることがわかります。クイックソートは実際には上記のマージソートに似ており、バイナリツリーを使用する概念にも似ているため、そのスペース計算量はO(log N)です。

上位 10 の古典的なソートアルゴリズムの詳細な説明の第 2 号が終了しました。UP の記事がうまく書かれている、または役立つと思われる場合は、UP の公式アカウントをフォローできます。新参の UP はあなたのサポートを必要としています!!!

<<:  個人情報を使って死者をデジタルで蘇らせるロボットを作る

>>:  人類の生存に関わる問題ですか? AI システムの説明可能性を調査する理由は何ですか?

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

推薦する

...

...

人工知能がウェブホスティング業界に優位性をもたらす

近年、ウェブホスティング業界は劇的に変化しました。そして、業界を永遠に変える可能性のあるいくつかのト...

C#アルゴリズムのプログラム実装に関する面接の質問

C# アルゴリズムの面接の質問を解く方法はたくさんあります。ここでは 1 つだけ紹介します。まずは質...

疫病流行中に物流の円滑化に全力を尽くし、無人配送市場が活況を呈している

最近、国務院は貨物物流の円滑な流れを確保するために関連業務を展開するよう通知し、各地域と関連部門に主...

...

世界初、上海が人工知能の教科書を出版! 2000年代以降は新たなスキルを使って世界を変えるのでしょうか?

「無力で、自分のやりたいことができない」。これは、世界一の囲碁プレイヤーである柯潔氏が4月27日に...

マッキンゼーの中国人工知能レポートは3つの大きな課題に直面している

BAT の幹部は、先日終了した IT リーダーシップ サミットで人工知能に焦点を当てました。ロビン・...

Pytorch モデルのトレーニングを最適化するためのヒント

この記事では、ディープラーニング モデルのトレーニングを改善するために私が個人的に見つけた 4 つの...

機械学習における欠損値に対処する9つの方法

データサイエンスはデータに関するものです。これは、あらゆるデータ サイエンスや機械学習プロジェクトの...

人工知能の応用は何ですか?

近年の人工知能の波の台頭により、無人運転車が再び話題となり、国内外の多くの企業が自動運転や無人運転車...

ソフトウェアがハードウェアを飲み込むAI時代において、チップがアルゴリズムの進化に追いつけない場合、私たちはどうすればよいのでしょうか?

AI時代の陰の立役者として、チップ業界は徐々にかつ継続的な変化を遂げています。 2008 年以降、...

完全な自動運転まであとどれくらいでしょうか?答えはセンサー技術の発展にある

近年、新エネルギー車が次々と登場し、販売も増加し続けています。テスラ、ウェイラン、小鵬汽車などの新エ...

人工知能(AI)とスポーツスタジアムの融合

新型コロナウイルスCOVID-19の影響は今も続いており、世界中の多くのスポーツスタジアムが麻痺状態...

ニューラルネットワークの発明者、福島邦彦氏が受賞、シュミットフーバー氏とフェイフェイ・リー氏が賛辞を送る

[[429116]]最近、福島邦彦氏が2021年度バウアー賞および科学業績賞を受賞したというニュース...