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の実行順序についてのちょっとした質問

推薦する

ドラミング効果を実現するJavascript

この記事では、ドラミング効果を実現するためのJavascriptの具体的なコードを参考までに紹介しま...

MySQLテーブル名の大文字と小文字を区別しない設定方法の詳細な説明

デフォルトでは、Linux の MySQL はテーブル名の大文字と小文字を区別します。 MySQL ...

CSS の両端揃えを実現する div+css レイアウトの 4 つの方法の概要

2 端揃えを実現する div+css レイアウトは、Web ページの組版でよく使用されます。この記事...

子コンポーネントで vue activated を使用する詳細

ページ: ベース: <テンプレート> <div class="タブコンテ...

WeChatアプレットでのwxsファイルの素晴らしい使い方をいくつか紹介します

目次序文応用フィルタードラッグファイル間での参照の受け渡しwxsはjsロジック層にパラメータを渡しま...

HTML シンボルからエンティティへのアルゴリズムのチャレンジ

チャレンジ:文字列内の文字 &、<、>、" (二重引用符)、および &...

Django が uwsgi+nginx プロキシで静的リソースにアクセスできない問題の解決方法

uwsgi+nginx プロキシ Django をデプロイする場合、uwsgi を使用したアクセスは...

Docker CPU 制限の実装

1. --cpu=<値> 1) コンテナが使用できるCPUリソースの量を指定しますが、コ...

Rx レスポンシブプログラミングについての簡単な説明

目次1. 観察可能2. 高階関数3. エクスプレスボックスモデル3.1. エクスプレスボックスモデル...

HTML フォーマットの json のサンプルコード

さっそく、コードを直接投稿します。具体的なコードは次のとおりです。 <!DOCTYPE htm...

Linux viコマンドの知識ポイントと使い方のまとめ

Linux viコマンドの詳しい説明vi エディタは、すべての Unix および Linux システ...

Docker のタイムゾーンの問題とデータ移行の問題

最新のソリューション: -v /usr/share/zoneinfo/Asia/Shanghai:/...

知っておくべきHTML最適化テクニック

Web ページのパフォーマンスを向上させるために、多くの開発者は、JavaScript、画像の最適化...

MySQL をデプロイするときに発生する「テーブル mysql.plugin が存在しません」という問題の解決方法

今日、MySQL の無料インストール版をデプロイしたところ、テーブル 'mysql.plug...

MySQL の自動増分 ID に関するいくつかの小さな問題の要約

以下の質問はすべて InnoDB ストレージ エンジンに基づいています。 1. 最も大きな ID を...