ソートアルゴリズム
一般的な並べ替えアルゴリズム
クイックソート
1 はじめに
クイックソートは、Tony Hall によって開発された並べ替えアルゴリズムです。平均して、n 個の項目を並べ替えるには O(nlogn) 個の比較が必要です。最悪の場合、Ο(n2) の比較が必要になりますが、これは一般的ではありません。実際、クイックソートは、その内部ループがほとんどのアーキテクチャで効率的に実装できるため、通常、他の Ο(nlogn) アルゴリズムよりも大幅に高速です。
クイックソートは分割統治戦略を使用して、リストを 2 つのサブリストに分割します。
クイック ソートは、ソート アルゴリズムにおける分割統治の考え方のもう 1 つの典型的な応用です。本質的に、クイック ソートはバブル ソートに基づく再帰的な分割統治法と見なされるべきです。
クイックソートの名前はシンプルで失礼ですが、名前を聞いたらすぐにその存在の意味がわかり、高速かつ効率的です。これは、ビッグデータの最も高速な並べ替えアルゴリズムの 1 つです。ワーストケースの時間計算量は O(n²) に達しますが、それらは優れており、ほとんどの場合、平均時間計算量が O(n logn) の並べ替えアルゴリズムよりも優れたパフォーマンスを発揮します。
クイック ソートの最悪の実行ケースは、順次配列のクイック ソートなど、O(n²) です。しかし、その償却期待時間は O(nlogn) であり、O(nlogn) 表記に含まれる定数係数は小さく、複雑さが O(nlogn) で安定しているマージ ソートよりもはるかに小さくなります。したがって、順序が弱い乱数シーケンスの大部分では、マージ ソートよりもクイック ソートの方が常に優れています。
2. 思考分析
クイック ソート アルゴリズムは、複数の比較と交換によるソートを実装します。そのソート プロセスは次のとおりです。
(1) まず、カットオフ値を設定し、カットオフ値によって配列を左右の部分に分割します。
(2) カットオフ値以上のデータを配列の右側に集め、カットオフ値未満のデータを配列の左側に集めます。このとき、左側の各要素はカットオフ値未満であり、右側の各要素はカットオフ値以上である。
(3) すると、左右のデータを独立してソートすることができます。左側の配列データの場合、境界値をとってデータのこの部分を左右に分割し、小さい値を左側に配置し、大きい値を右側に配置します。右側の配列データも同様に処理できます。
(4) 上記のプロセスを繰り返すと、これが再帰的定義であることがわかります。左部分を再帰的にソートした後、右部分の順序を再帰的にソートします。左部分と右部分のデータのソートが完了すると、配列全体のソートも完了します。
3. 図表
3.1 スタート
arr 配列の両端からそれぞれ開始します検出。まず右から左に 8 より小さい数を見つけ、次に左から右に 8 より大きい数を見つけて、それらを交換します。ここでは、シーケンスの左端と右端をそれぞれ指す 2 つの変数 L (左) と R (右) を使用できます。最初に、L が配列の左端、つまり数値 8 を指すようにします。R が配列の右端、つまり数値 11 を指すものとします。
3.2 右から左にベンチマークよりも小さい数値に遭遇する
3.3 右側で停止し、左から右にベンチマークより大きい数値を探します。
3.4 LとRに対応する番号を交換する
交換後
3.5 上記の方法の続き
まず R がディスパッチを開始します。ここで設定する基数は一番左の数字なので、Rを先に行かせることが非常に重要です(理由は自分で考えてください)。センチネル R は、8 未満の数字を見つけて停止するまで、左に一歩ずつ移動します (つまり、R–-)。次に、センチネル L は、8 より大きい数値を見つけて停止するまで、右に一歩ずつ移動します (つまり、L++)。
交換
交換後
このときRは左に移動し続けます
するとLが動きます
L == R の場合、この位置は 8 の位置であることを意味し、交換
得る:
これで最初の検出ラウンドが終了します。このとき、基準番号8を分割点とし、8の左側の数字はすべて8以下、8の右側の数字はすべて8以上となる。先ほどのプロセスを振り返ると、実際には、R の使命はベンチマークの数値より小さい数値を見つけることであり、L の使命は L と R が一致するまでベンチマークの数値よりも大きい数値を見つけることです。
3.6 左側の数字の扱い
3.7 右側の数値の処理
得る:
4. コードの実装
/**
* 快速排序
* @author 尹稳健~
* @version 1.0
* @time 2022/9/10
*/
public class QuickSort {
public static void main(String[] args) {
int[] arr = {
8, 12, 19, -1, 45, 0, 14, 4, 11};
quickSort(arr,0,arr.length-1);
for (int i = 0; i < arr.length; i++) {
System.out.print(arr[i]+" ");
}
}
public static void quickSort(int[] arr,int start, int end){
// 什么时候结束排序?
if (start>end){
return;
}
// 左指针
int L = start;
// 右指针
int R = end;
// 基准
int pivot = arr[start];
// 只要L!=R就继续循环
while (L < R){
// 右边大于基准,从右向左移动
while (L<R && arr[R] >= pivot){
R --;
}
// 左边小于基准,从左向右移动
while (L<R&& arr[L] <= pivot){
L ++;
}
// 说明右边有小于基准的数,左边有大于基准的数,交换
if (L<R){
int temp = arr[L];
arr[L] = arr[R];
arr[R] = temp;
}
}
// L 与 R碰面 找到了 基准的位置
arr[start] = arr[L];
arr[L] = pivot;
// 左边排序
quickSort(arr,start,R-1);
// 右边排序
quickSort(arr,L+1,end);
}
}
クイック ソート この記事はよく書かれており、他の人のクイック ソートの書き方と理解を参照することができます。
マージソート
1 はじめに
マージ ソートは、マージ操作に基づいた効果的で安定したソート アルゴリズムであり、分割統治 (Divide and Conquer) の非常に典型的なアプリケーションです。順序付けられたサブシーケンスを結合して、完全に順序付けられたシーケンスを取得します。つまり、最初に各サブシーケンスを順番に作成し、次にサブシーケンス セグメントを順番に作成します。2 つのソート済みリストを 1 つのソート済みリストにマージすることを、双方向マージと呼びます。
2. 思考分析
マージソートは分割統治の考え方を使用しており、ルールが難しいのが特徴です
- マージされたシーケンスを格納するために使用される、2 つの並べ替えられたシーケンスの合計サイズになるようにスペースを適用します。
- 初めに分解シーケンス、シーケンスを各グループのシーケンスに分解します。数量は1です、次にマージソートを実行します
- 設定2つのポインター、初期位置は、ソートされた 2 つのシーケンスの開始位置です。
- 2 つのポインターが指す要素を比較し、比較的小さい要素を選択してマージ スペースに配置し、ポインターを次の位置に移動します。
- 特定のポインターがシーケンスの最後に到達するまで、前のステップを繰り返します。
- 別のシーケンスの残りの要素をすべて、マージされたシーケンスの最後に直接コピーします。
3. 図表
3.1 すべてのシーケンスを分割、分解する
3.2 並べ替えと結合の開始
充填するたびにポインタが 1 ビット移動します
左の配列に要素がない場合、右の配列が直接埋められます。
得る
4. コードの実装
/**
* 归并排序
* @author 尹稳健~
* @version 1.0
* @time 2022/9/12
*/
public class MergeSort {
public static void main(String[] args) {
int[] arr = {
1, 5, 6, 3, 2, 8, 7, 4};
int[] temp = new int[arr.length];
mergeSort(arr,0,arr.length-1,temp);
for (int i = 0; i < arr.length; i++) {
System.out.print(arr[i]+" ");
}
}
/** 分解,并合并排序 */
public static void mergeSort(int[] arr,int left ,int right,int[] temp){
if (left<right){
//中间索引
int mid = (left+right)/2;
//向左递归分解
mergeSort(arr,left,mid,temp);
//向右递归分解
mergeSort(arr,mid+1,right,temp);
//合并
merge(arr,left,mid,right,temp);
}
}
/**
* 合并排序
* @param arr 未排序的数组
* @param left 左边有序子数组的初始索引
* @param mid 中间索引
* @param right 右边最大索引
* @param temp 临时数组
*/
public static void merge(int[] arr,int left,int mid,int right,int[] temp){
// 左边指针索引
int leftPoint = left;
// 右边指针索引
int rightPoint = mid+1;
// 临时数据的指针
int tempLeft = 0;
// 两个序列都不为空时
while (leftPoint <= mid && rightPoint <= right){
//如果第一个序列的元素小于第二序列的元素,就将其放入temp中
if (arr[leftPoint] <= arr[rightPoint]){
temp[tempLeft] = arr[leftPoint];
leftPoint++;
tempLeft ++;
}else{
temp[tempLeft] = arr[rightPoint];
rightPoint++;
tempLeft ++;
}
}
// 右序列为空,直接将做序列的所有元素填充进去
while (leftPoint <= mid){
temp[tempLeft] = arr[leftPoint];
leftPoint++;
tempLeft++;
}
// 左序列为空,直接将右序列的所有元素填充进去
while (rightPoint <= right){
temp[tempLeft] = arr[rightPoint];
rightPoint++;
tempLeft++;
}
//将临时数组中的元素放回数组arr中
tempLeft = 0;
leftPoint = left;
while (leftPoint <= right){
arr[leftPoint] = temp[tempLeft];
leftPoint++;
tempLeft++;
}
}
}
基数ソート
1 はじめに
基数ソート(基数ソート)は、「バケットソート」やビンソートとも呼ばれる「分散ソート」に属し、その名の通り、キー値の部分情報でソート対象の要素を、順番にいくつかの「バケット」に分散します。ソートの役割を達成するために、基数ソート方法は安定したソートであり、その時間計算量は O (nlog®m) です。ここで、r は使用される基数、m はヒープの数です。ある時点で、基数ソートは次のようになります。他の安定性ソートよりも効率的です。
2. 思考分析
- 比較するすべての値(正の整数)を同じ桁長に統一し、桁数を短くしてゼロ埋めする数値
- 下位ビットから順に並べ替えます
- 最下位の桁から最上位の桁へ(一の位→十の位→百の位→…→最上位の桁)ソートが完了すると、番号列は順序付けされたシーケンスになります。
- 最大値を取得するために必要な桁数
- 最大数を String 型に変更して、その長さを調べることができます
3. 図表
まず、[0-9] を表すバケットが 10 個必要です。
3.1 最初のラウンドは単位に従ってバケットに入れられます
バケツの中に複数の要素がある場合は、先に入った要素が最初に取り出されます。
3.2 第 2 ラウンドは 10 の単位に従ってバケットに入れられます。
10の位がない場合は、1の位の前に0を追加します
3.3 百の位に従って第 3 ラウンドがバレルに入れられます。
一つ一つ取り出したら
3.34 第 4 ラウンドは数千に従ってバケツに入れられます
取り出して次のものを入手します。
4. コードの実装
/**
* 基数排序
* @author 尹稳健~
* @version 1.0
* @time 2022/9/13
*/
public class BaseSorted {
public static void main(String[] args) {
int[] arr = {
43, 52, 1, 89, 190};
sort(arr);
for (int i = 0; i < arr.length; i++) {
System.out.print(arr[i] + " ");
}
}
public static void sort(int[] arr){
int maxLength = getMaxLength(arr);
// 定义个二维数组桶,存储元素
int[][] bucket = new int[10][arr.length];
// 存储每个桶中有多少个元素
int[] elementCounts = new int[10];
for (int times = 1,step = 1; times < maxLength + 1; times++,step *= 10) {
// 遍历数组,将元素存入桶中
for (int i = 0; i < arr.length; i++) {
// 获取位数上的数
int digits = arr[i] / step % 10;
bucket[digits][elementCounts[digits]] = arr[i];
elementCounts[digits] ++;
}
//将桶中的元素重新放回到数组中
//用于记录应该放入原数组的哪个位置
int index = 0;
for (int i = 0; i < 10; i++) {
// 从桶中按放入顺序依次取出元素,放入原数组
int position = 0;
while ( elementCounts[i] > 0){
arr[index] = bucket[i][position];
position++;
elementCounts[i]--;
index++;
}
}
}
}
/** 获取数组中元素长度最长的长度 */
public static int getMaxLength(int[] arr){
int max = arr[0];
for (int i = 1; i < arr.length; i++) {
if (arr[i] > max ){
max = arr[i];
}
}
return (max + "").length();
}
}