JavaScript で実装された 7 つのソート アルゴリズムの概要 (推奨!)

JavaScript で実装された 7 つのソート アルゴリズムの概要 (推奨!)

序文

いわゆるソート アルゴリズムは、特定のアルゴリズム要素を通じて、事前に決定されたパターンに従って 1 つ以上のデータ グループを再ソートすることです。この新しいシーケンスは特定のルールに従い、一定の規則性を反映しているため、処理されたデータのスクリーニングと計算が容易になり、計算効率が大幅に向上します。ソートを行うには、まず一定の安定性が必要です。つまり、一定のソート アルゴリズムの後に、シーケンス内に 2 つの同一要素が同時に出現した場合、ソート前後の 2 つの相対的な位置は変化しません。つまり、2 つの要素がまったく同じであっても、ソート処理中に区別され、混乱は許されません。

バブルソート

バブルソートは初級レベルのアルゴリズムですが、興味深い使い方もいくつかあります。一般的に言えば、バブルソートを記述する方法は 3 つあります。

一度に 2 つずつ比較して交換し、最大値/最小値を最後の桁までバブルします。
最適化された書き込み方法: 変数を使用して、現在の比較ラウンドで交換が発生したかどうかを記録します。交換が発生していない場合は、順序がすでに整っていることを意味し、それ以上のソートは実行されません。

基本アルゴリズム

空間計算量はO(1)、時間計算量はO(n2)である。

定数ソート = (arr) => {
    (i = 0, len = arr.length; i < len-1; i++) の場合{
        (j = 0; j < len-1-i; j++) の場合 {
            もし(arr[j] > arr[j+1])であれば{
                [arr[j], arr[j+1]] = [arr[j+1], arr[j]];
            }
        }
    }
    リターン
}

最も外側の for ループの各ラウンドの後に、残りの数字の最大値が現在のラウンドの最後の桁に移動され、いくつかの隣接する数字が途中で交換されて整然とします。比較の総数は(n-1)+(n-2)+(n-3)+…+1(n−1)+(n−2)+(n−3)+…+1です。

2 番目の書き方は、基本的なアルゴリズムに基づいて改良されています。

定数ソート = (arr) => {
    (i = 0, len = arr.length; i < len - 1; i++) の場合 {
        isSwap = false とします
        (j = 0; j < len - 1 - i; j++) の場合 {
            もし(arr[j] > arr[j + 1]) {
                [arr[j], arr[j + 1]] = [arr[j + 1], arr[j]];
                isSwap = true
            }
        }
        スワップの場合
            壊す;
        }
    }
    arr を返します。
};

空間計算量は O(1)、時間計算量は O(n2) - できれば O(n) です。

最も外側の for ループがラウンドを通過するたびに、残りの数字の最大値は現在のラウンドの最後の桁に移動されます。この方法が最初の方法に比べて優れている点は、比較ラウンドで交換が行われなかった場合、この時点で残りの数字が順番になっているはずなので、ソートが直ちに停止することです。

選択ソート

選択ソートの考え方は、配列を二重ループで走査し、比較の各ラウンドの後に最小の要素の添え字を見つけて、それを最初の場所に交換することです。

基本アルゴリズム

定数ソート = (arr) => {
    (i = 0, len = arr.length; i < len - 1; i++) の場合 {
        minIndex = i とします
        (j = i+1; j < len; j++) の場合 {
            もし(arr[i] > arr[j]) {
                最小インデックス = j
            }
        }
        [arr[i], arr[minIndex]] = [arr[minIndex], arr[i]];
    }
    arr を返します。
};

バイナリ選択ソート - 最適化

選択ソート アルゴリズムも最適化できます。各トラバース ラウンドで最小値が見つかるので、最大値も探してみませんか?これがバイナリ選択ソートの考え方です。

バイナリ選択ソートを使用すると、選択の各ラウンドで最小値と最大値を記録することで、走査する必要がある配列の範囲を半分に減らすことができます。

定数ソート = (arr) => {
    (i = 0, len = arr.length; i < len / 2; i++) の場合 {
        minIndex = i とします。
        maxIndex = i とします。
        (j = i + 1; j < len-i; j++) の場合 {
            (arr[minIndex] > arr[j])の場合{
                最小インデックス = j;
            }
            (arr[maxIndex] < arr[j])の場合{
                最大インデックス = j;
            }
        }
        minIndex === maxIndexの場合、break;
        [arr[i], arr[minIndex]] = [arr[minIndex], arr[i]];
        (最大インデックス === i)の場合{
            最大インデックス = 最小インデックス;
        }
        定数 lastIndex = len - i - 1;
        [arr[最大インデックス]、arr[最終インデックス]] = [arr[最終インデックス]、arr[最大インデックス]];
    }
    arr を返します。 
};

挿入ソート

挿入ソートの考え方は非常にシンプルです。人生では非常に一般的なシナリオがあります。ポーカーをプレイするとき、カードを引いたときにそれをソートします。カードを引くたびに、それを手札にある既存のカードの適切な位置に挿入し、徐々にソート全体を完了します。

挿入ソートを記述する方法は 2 つあります。

  • 交換方法: 新しい数字を挿入するときは、適切な位置が見つかるまで、以前の数字と交換し続けます。
  • 移動方法: 新しい数字を挿入するプロセスでは、常に前の数字と比較され、前の数字は常に後方に移動します。新しい数字がその位置を見つけると、1 回挿入できます。

スワップによる挿入ソート

定数ソート = (arr) => {
    (i = 1、len = arr.length、i < len、i++ とします) {
        j = iとします。
        (j >= 1 && arr[j] < arr[j - 1]) の場合 {
            [arr[j], arr[j - 1]] = [arr[j - 1], arr[j]];
            じーー
        }
    }
    arr を返します。
};

数字が 2 つ未満の場合は、ソートの問題はなく、もちろん挿入も必要ないので、2 番目の数字から直接挿入します。

モバイル方式

交換法挿入ソートでは、数字を毎回交換する必要があることがわかりました。しかし、実際には、新しく挿入された数字は、交換される数字の位置に必ずしも適合するとは限りません。つまり、新しい位置に短時間移動しただけであり、次の比較後に再度交換する必要がある場合は、すぐに前の数字の位置に移動されます。

このことから、最適化の解決策を考えることができます。まず、新しく挿入された数字を比較し、その数字よりも大きい数字を常に後方に移動して、新しい数字に適した位置が見つかるまで、新しい数字を挿入します。

このソリューションでは、新しく挿入された数字を一時的に保存する必要があります。コードは次のとおりです。

定数ソート = (arr) => {
    (i = 1、len = arr.length、i < len、i++ とします) {
        j = i-1 とします。
        cur = arr[i]とします。
        (j >= 0 && cur < arr[j]) の間 {
            arr[j+1] = arr[j]
            j--;
        }
        arr[j+1] = cur
    }
    arr を返します。
};

シェルソート

1959 年 7 月、シンシナティ大学で数学の博士号を取得したドナルド シェルは、Communications of the ACM でシェル ソート アルゴリズムを発表しました。このアルゴリズムは、時間の計算量を O(n2) 未満に削減した最初のアルゴリズムの 1 つとなりました。元のシェルソートの最悪の時間計算量は依然として O(n2) ですが、最適化されたシェルソートは O(n1.3) または O(n7/6) に達することもあります。

シェル ソートは、本質的には挿入ソートの最適化です。挿入ソートのシンプルさを活用し、一度に 2 つの隣接する要素しか交換しないという挿入ソートの欠点を克服します。その基本的な考え方は次のとおりです。

  • ソート対象となる配列を一定の間隔で複数のサブ配列に分割し、各グループを個別に挿入してソートします。ここで、間隔によるグループ化とは、連続した配列を取得することではなく、特定の間隔で値を取得してグループを形成することを意味します。
  • 次のソートの間隔を徐々に短くする
  • 最後のラウンドでは、間隔は 11 に設定され、これは挿入ソートを直接使用するのと同等です。しかし、前の「マクロ制御」の後、配列は基本的に順序付けられているため、この時点での挿入ソートは、完了するために少量のスワッピングのみを必要とします。たとえば、Shell が配列 [8, 3, 34, 6, 4, 1, 44, 45, 43, 2, 23] をソートするプロセスは次のとおりです。

最初のパス(5間隔ソート):サブ配列を5の間隔に従って5つのグループに分割します。つまり、[8、1、23]、[3、44]、[34、45]、[6、43]、[4、2]です。これらをソート順に挿入します。ソート後、それぞれ [1, 8, 23]、[3, 44]、[34, 45]、[6, 43]、[2, 4] になります。配列全体は [1, 3, 34, 6, 2, 8, 44, 45, 43, 4, 23] になります。

2 回目のパス (2 間隔ソート): 間隔 2 に従ってサブ配列を 2 つのグループに分割します ([1、34、2、44、43、23]、[3、6、8、45、4])。挿入ソートすると、ソート後は [1, 2, 23, 34, 43, 44]、[3, 4, 6, 8, 45] となり、配列全体は [1, 3, 2, 4, 23, 6, 34, 8, 43, 45, 44] となります。ここで非常に重要な特性があります。2 間隔のソートを完了した後も、配列は 5 間隔の順序のままです。つまり、より狭い間隔でソートしても、前のステップの結果は悪化しません。

3 番目のパス (11 間隔ソート、直接挿入ソートと同じ): 間隔 1 に従ってサブ配列をグループ (配列全体) に分割します。挿入ソートを実行します。最初の 2 回のソートの後、配列は基本的に順序どおりになっているため、この手順ではソートを完了するために少量のスワップのみが必要です。ソート後、配列は[1, 2, 3, 4, 6, 8, 23, 34, 43, 44, 45]となり、ソートが完了します。

定数ソート = arr => {
    定数len = arr.length;
    長さが2以下の場合
        arr を返します。
    }
    ギャップを Math.floor(len / 2) とします。
    ギャップ > 0 の場合
        (i = ギャップとします; i < len; i++) {
            j = iとします。
            cur = arr[i]とします。
            (j >= 0 && cur < arr[j - ギャップ]) {
                arr[j] = arr[j - ギャップ];
                j -= ギャップ;
            }
            arr[j] = cur;
        }
        ギャップ = Math.floor(ギャップ / 2);
    }
    arr を返します。
}

ヒープソート

ヒープソートのプロセスは次のとおりです。

  1. シーケンスを使用して大きなトップヒープを作成し、ヒープの先頭にある数字を取り出します (ソートする配列の最後に配置します)。
  2. 残りの数字を調整し、新しい大きな山を作り、山の一番上の数字をもう一度取り出します。
  3. このプロセスを何度も繰り返して、ソートプロセス全体を完了します。
関数sort(arr) {
    (i = Math.floor(arr.length / 2) - 1 とします; i >= 0; i--) {
        ヒープを調整します(arr, i, arr.length)
    }
    (j = arr.length - 1; j > 0; j--) の場合 {
        [arr[0], arr[j]] = [arr[j], arr[0]]
        ヒープを調整(arr, 0, j)
    }
}

関数adjustHeap(arr, i, length) {
    tmp = arr[i]とする
    (k = i * 2 + 1 とします; k < 長さ; k = k * 2 + 1) {
        (k + 1 < 長さ && arr[k] < arr[k + 1]) の場合 {
            関数
        }
        (arr[k] > tmp)の場合{
            arr[i] = arr[k];
            私 = k;
        } それ以外 {
            壊す;
        }
        arr[i] = tmp;
    }
}

クイックソート

クイックソートアルゴリズムは、1960 年に CAR Hoare によって提案されました。その時間計算量も O(nlogn) ですが、時間計算量が O(nlogn) であるいくつかのソートアルゴリズムの中では、ほとんどの場合にクイックソートの方が効率的であるため、広く使用されています。さらに、クイックソートが採用している分割統治の考え方は非常に実用的であり、クイックソートが面接官の間で非常に人気があるため、クイックソートの考え方を習得することは特に重要です。

クイックソートアルゴリズムの基本的な考え方は次のとおりです。

  1. 配列からピボットと呼ばれる数値を取ります
  2. 配列を走査し、基数より大きい数値を右側に、基数より小さい数値を左側に配置します。トラバーサルが完了すると、配列は左と右の2つの領域に分割されます。
  3. 左側と右側の領域を 2 つの配列として扱い、ソートが完了するまで最初の 2 つの手順を繰り返します。
  4. 実際、クイックソートの各トラバーサルでは、カーディナリティが最終位置に配置されます。最初の巡回ラウンドでは 1 つの基数が分類され、2 番目の巡回ラウンドでは 2 つの基数が分類され (各領域に 1 つの基数がありますが、領域が空の場合は、このラウンドで分類できる基数は 1 つだけです)、3 番目の巡回ラウンドでは 4 つの基数が分類され (同様に、最悪の場合、分類できる基数は 1 つだけです)、というように続きます。走査の総回数はlogn~n回であり、走査の各ラウンドの時間計算量はO(n)であるため、クイックソートの時間計算量はO(nlogn)~O(n2)であり、平均時間計算量はO(nlogn)であることが簡単に分析できます。
const パーティション = (arr, 開始, 終了) => {
    let pivot = arr[start]; // 最初の数値を基数として取得します。let left = start + 1; // 2 番目の数値から分割を開始します。let right = end; // 右境界 // left と right が出会ったらループを終了します。while (left < right) {
        // カーディナリティより大きい最初の位置を検索します while (left < right && arr[left] <= pivot) left++;
        // これら 2 つの数値を入れ替えて、左のパーティションがカーディナリティ以下になり、右のパーティションがカーディナリティ以上になるようにします。if (left != right) {
            [arr[左], arr[右]] = [arr[右], arr[左]];
            右 - ;
        }
    }
    // 左と右が等しい場合は、arr[right] と pivot を別々に比較します
    if (left == right && arr[right] > pivot) right--;
    // ベースと中央を入れ替えます if (right != start) [arr[left], pivot] = [pivot, arr[left]];
    // 中央の値の添字を返します return right;
}

const クイックソート = (arr, 開始, 終了) => {
    (開始 >= 終了) の場合、戻り値:
    const 中間 = パーティション (arr、開始、終了)
    クイックソート(arr, start, middle - 1);
    クイックソート(arr, 中間 + 1, 終了);
}

定数ソート = arr => {
    クイックソート(arr, 0, arr.length -1);
}

マージソート

マージソートは、マージ操作に基づいた効果的なソートアルゴリズムです。このアルゴリズムは、分割統治法の非常に典型的な応用です。順序付けられたサブシーケンスをマージして、完全に順序付けられたシーケンスを取得します。つまり、最初に各サブシーケンスを順序付けし、次にサブシーケンスのセグメントを順序付けます。 2 つの順序付きリストが 1 つの順序付きリストに結合される場合は、2 方向の結合と呼ばれます。

アルゴリズムの説明

  • 長さ n の入力シーケンスを長さ n/2 の 2 つのサブシーケンスに分割します。
  • これら 2 つのサブシーケンスにそれぞれマージ ソートが適用されます。
  • 2 つのソートされたサブシーケンスを最終的なソートされたシーケンスにマージします。
const マージ = (左、右) => {
    結果 = [] とします。
    (左の長さ>0&右の長さ>0){
        (左[0] <= 右[0])の場合{
            結果をプッシュします(左にシフトします());
        } それ以外 {
            結果を右に押します。
        }
    }
    left.length を result.push(left.shift()); しながら
    right.length を result.push(right.shift()); しながら
    結果を返します。
}

定数ソート = (arr) => {
    len = arr.length;とします。
    長さが2以下の場合
        arr を返します。
    }
    const 中間 = Math.floor(len / 2)、
        左 = arr.slice(0, 中央)、
        右 = arr.slice(中央);
    merge(sort(left), sort(right)) を返します。
};

要約する

これで、JavaScript で実装された 7 つのソートアルゴリズムに関するこの記事は終了です。JavaScript ソートアルゴリズムに関するより関連性の高いコンテンツについては、123WORDPRESS.COM の過去の記事を検索するか、以下の関連記事を引き続き参照してください。今後とも 123WORDPRESS.COM をよろしくお願いいたします。

以下もご興味があるかもしれません:
  • JS 面接の質問 --- アルゴリズムの手順に関する質問
  • Vue で crypto-js AES 対称暗号化アルゴリズムを使用して暗号化と復号化を実装する
  • Matlab による JavaScript プログラミング、重心アルゴリズムによる位置決め学習
  • JavaScript でツリー構造を構築するための効率的なアルゴリズムについての簡単な説明
  • JS での多段階ソートアルゴリズムの実装コード
  • JS でシンプルなカレンダー アルゴリズムを実装する方法
  • JavaScript アルゴリズムの面接の質問

<<:  Linux で SVN サーバーをインストールする方法

>>:  MySQLにおけるSQLの実行順序についてのちょっとした質問

推薦する

Nginx で何ができるかの包括的な分析

序文この記事は、サードパーティのモジュールをロードせずにNginxで処理できることのみに焦点を当てて...

vue+drf+サードパーティのスライディング検証コードアクセスの実装

目次1. 背景2. 検証プロセス3. 検証を作成する4. フロントエンドコード4.1 コアjsファイ...

HTML テキストエスケープのヒント

今日、CSDN で HTML テキスト エスケープのちょっとしたトリックを見ましたが、とても簡単です...

MySQL 8.0.15 圧縮版インストール グラフィック チュートリアル

この記事では、参考までにMySQL 8.0.15圧縮版のインストール方法を紹介します。具体的な内容は...

すべてまたは逆の選択機能を実現するJavaScript

この記事では、全選択または選択を反転する機能を実現するためのJavaScriptの具体的なコードを参...

LINUX でプロセスを表示する 4 つの方法 (要約)

プロセスは CPU とメモリ内で実行されるプログラム コードであり、各プロセスは 1 つ以上のプロセ...

Amoeba を使用して MySQL データベースの読み取り/書き込み分離を実装する方法の詳細な説明

MySQL には読み取りと書き込みを分離するアーキテクチャが多数あります。Baidu のそれらのほと...

Vue で AES.js を使用する詳細な手順

AES暗号化の使用データ転送の暗号化と復号化処理 --- AES.js最初のステップ: vue に ...

この SQL 書き込み方法では本当にインデックスが失敗するのでしょうか?

序文インターネット上には、MySQL でインデックスにヒットできないさまざまな状況をまとめた記事がよ...

ルート権限なしでログインするためのDockerソリューション

docker コマンドを初めて使用する場合、権限の問題を確認するメッセージが表示されます。 unix...

CSS の一部のプロパティの前には「*」または「_」が付きます。

CSS の一部のプロパティの前には「*」または「_」が付きます。さまざまなブラウザを識別する例えば...

Linux C++ マルチスレッド同期の非常に詳細な説明

目次1. ミューテックス1. ミューテックスの初期化2. ミューテックスロックの関連特性と分類3. ...

XHTML 入門チュートリアル: リストタグの使用

リストは、類似または関連する一連の項目をリストするために使用されます。順序なしリスト(箇条書きリスト...

MySQLにおける遅いSQLの最適化の方向性について詳しく話しましょう

目次序文SQL文の最適化遅いクエリSQLを記録する設定を変更する方法スロークエリログを表示するSQL...

CSSを使用してTDのINPUTの幅を設定する

最近、C# を使用して Web プログラムを作成していたときに、次のような問題が発生しました。 Te...