まず、ソートの分類と特徴について説明します。
1. ソートの分類
1. 内部ソートと外部ソート
(1) 内部: ソート対象のレコードをランダム アクセス メモリ (メモリ) に格納するソート処理)コンピュータの。
(2) 外部: ソート対象のレコード数が非常に多く、一度にすべてのレコードをメモリに保持できないため、ソート処理中に外部メモリにアクセスするソート処理が必要です。
2. 比較ソートと非比較ソート
(1) 比較クラス:要素間の相対的な順序を比較によって決定し、時間計算量が O(nlogn) を超えることができないため、非線形時間比較クラスソートとも呼ばれます。
(2) 非比較クラス: 要素の相対順序は比較によって決定されず、比較ベースのソート時間の下限を突破して線形時間で実行できるため、線形時間非比較ソートとも呼ばれます。
2. 特徴
3. 安定性と不安定性
(1) 安定性: a が元々 b の前にあり、a=b の場合、ソート後も a は b の前にあります。
(2) 不安定: a が元々 b の前にあり、a=b の場合、ソート後に a が b の後ろに現れる可能性があります。つまり、元の相対位置が変化します。
4. 複雑度:
(1) 時間計算量: ソートされたデータに対する操作の総数。これは、n が変化するときの操作数の規則性を反映します。
(2) これは、アルゴリズムがコンピュータで実行されるときに必要な記憶領域の測定値を指し、データ サイズ n の関数でもあります。
3. 8 つの主要なソートの説明
さて、話はやめて、本題に入ります。
まず、8 つの主要なソートと各アルゴリズムの特徴を見てください。
交換ソート: バブルとクイック ソート
1. バブル ソート
(1) アニメーションデモンストレーション:
( 2) アルゴリズム思考分析:
1> 2 つの隣接する数値を比較します。n[j] は n[j+1] と比較されます。n[j]>n[j+1] の場合、2 つの数値を交換します。2> j++ 。その後、上記の手順を繰り返します
。最初のパス、最大の数値が最後の場所で決定されます。これはバブル ソートであり、大きい (小さい) 数値の底とも呼ばれます。3> i++、
i=n-1 になるまで上記の手順を繰り返します。終了、並べ替えが完了しました。
(3) 複雑さの分析:
1> 時間計算量:
元の配列が順序付けされているかどうかに関係なく、すべての数値は他の数値と 1 回、(n-1) 回、2 回比較する必要があるため、時間計算量は O(n2) です。分解: n2+2n-1、低電力と定数を削除し、n2 を残すため、最終的な時間計算量は n2 になります。
2> 空間計算量: 補助変数が 1 つだけ定義されているため、n のサイズとは関係がなく、空間計算量は O(1) です。
(4) Java コード:
import java.util.Scanner;
public class Bubbling {
public static void main(String[] args) {
Scanner sca = new Scanner(System.in);
int len = sca.nextInt();
int temp;
int [] data = new int [len];
for(int i = 0; i < len; i++) {
data[i] = sca.nextInt();
}
//排序
for(int i = 0; i < len; i++) {
for(int j = 0; j < len - 1 - i; j++) {
if(data[j] > data[j + 1]) {
temp = data[j];
data[j] = data[j + 1];
data[j + 1] = temp;
}
}
}
//输出
for(int i = 0; i < len; i++) {
System.out.print(data[i]);
}
sca.close();
}
}
- クイック ソート
(1) アニメーション デモンストレーション:
(2) アルゴリズム思考分析:
クイック ソートのアイデアは、ベースとして数字を選択し (ここでは最初の数字を選択します)、このベースより大きい数字を右側に置き、このベースよりも小さいものを左側に置きます。習慣に応じて、このベースに等しい数字を左または右に配置できます。ここでは左側に置きます。最初のパスの終了後、ベースを置きます。中央の分離位置では、2 番目のパスで配列を底の位置から移動します。2 つの分割された配列を 2 つの半分に分割し、上記の手順を繰り返し、底を選択し、小数を底の左側に配置します。大きな数値を底の右側に配置し、配列が分割できなくなるまで配列を分割し、並べ替えが終了します。
たとえば、小さいものから大きいものへ並べ替えます:
1> 最初のパス、最初の数値はベース温度、2 つのポインターを左 = 0、右 = n.length に設定します
。①n[right]> ベースの場合、右からベース温度と比較します。温度に達したら、右ポインタを 1 ビット進めて、n[右] > 基準温度を満たさなくなるまで基準温度との比較を続ける ②
n[右] を n[左] に代入する
③ 左から開始して基準温度と比較する, n[left]<=base tempの場合、左ポインタは1ビット戻り、n[
left ]<=base tempが満たされなくなるまで、base tempとの比較を続けます。]
⑤①~ステップ④を左==右の最後まで繰り返し、ベース温度をn[左]に代入します。
2> 2パス目、配列を中央から分離し、各配列に対してステップ1の操作を実行します。分割した配列を分割する 分割とクイックソート、
3> 配列が分割できなくなるまで分割とクイックソートを再帰的に繰り返す、つまり要素が 1 つだけ残った時点で再帰は終了し、ソートが完了します。最初の実行プロセスを示します: (3
)
複雑さの分析:
1> 時間計算量:
最悪の場合は、毎回取得される要素が配列内で最小/最大である場合です。この状況は実際にはバブル ソート (1 つの要素の順序が毎回並べられる) です。この場合、時間計算量
は計算は簡単です。これはバブル ソートの時間計算量です: T[n] = n * (n-1) = n^2 + n。
最良の場合、それは O(nlog2n) であり、導出プロセスは次のとおりです: (再帰アルゴリズムの時間計算量の公式: T[n] = aT[n/b] + f(n) ) したがって、平均は時間計算量は O(nlog2n
)
2> スペース計算量: クイック ソートで使用されるスペースは O(1) で、これは一定レベルです。各再帰ではデータを保持する必要があるため、実際のスペース消費は再帰呼び出しです。最適な場合、
空間 計算量は O(log2n); 配列が毎回均等に分割される場合、
最悪の場合の空間計算量は O(n); バブル ソートの場合に縮退するため、
平均空間複雑度は O(log2n)
(4 ) Java コード:
import java.util.Scanner;
//冒泡排序演变而来
public class 快速 {
public static void main(String[] args) {
Scanner sca = new Scanner(System.in);
int len = sca.nextInt();
int [] data = new int [len];
for(int i = 0; i < len; i++) {
data[i] = sca.nextInt();
}
quickSort(data, 0, data.length - 1);//快排
for(int i = 0; i < data.length; i++) {
System.out.print(data[i] + " ");
}
sca.close();
}
public static void quickSort(int [] data, int left, int right) {
int f, t;
int rtemp, ltemp;
ltemp = left; //左指针
rtemp = right; //右指针
f = data[(left + right) / 2]; //传来的每一个子表的中间值
while(ltemp < rtemp) {
//左指针只要比右边小就开始循环
while(data[ltemp] < f) ++ltemp; //从左边开始,只要当前数小于中间值,保留右移
while(data[rtemp] > f) --rtemp; //从右边开始,只要当前数大于中间值,保留左移
if(ltemp <= rtemp) {
//左指针小于等于右指针(等于 ==>是为避免子表只有俩元素)
t = data[ltemp];
data[ltemp] = data[rtemp];
data[rtemp] = t;
--rtemp;
++ltemp;
}
}
if(ltemp == rtemp) ltemp++; //左右指针相等,左指针右移(左右指针都移到了中间)
if(left < rtemp) quickSort(data, left, ltemp - 1); //右指针没有到达左头部,以左指针为右头部形成左子表
if(ltemp < right) quickSort(data, rtemp + 1, right);//左指针没有到达右头部,以右指针为左头部形成右子表
}
}
挿入ソート: 単純な挿入ソートとヒル ソート
1. 単純な挿入ソート:
(1) アニメーション デモンストレーション:
(2) アルゴリズムのアイデアの分析:
など: 小さいものから大きいものへのソート:
1> 2 番目の位置からトラバースを開始、
2> 現在番号 (最初の 1 つの旅行は 2 桁目です) と前の番号を順に入力します。前の番号が現在の番号より大きい場合は、この番号を現在の番号の位置に置きます。現在の番号の添字は -1 です。 3
> 現在の数字が前にある特定の数字を超えなくなるまで上記の手順を繰り返します。この時点で、現在の数字をこの位置に置きます。 **注: **ステップ 1 ~ 3 は、数字が先頭にあることを確認するためのものです
。現在の数値の前が順番に並んでおり、内側のループの目的は、現在の数値を前の順序のシーケンスに挿入することです。
4> 最後の桁まで移動し、最後の桁が適切な位置に挿入されるまで、上記の 3 つの手順を繰り返します。 , 挿入ソートが終了します。
次の図は、アルゴリズムの各実行プロセスをシミュレートするために使用されます。
(3) 複雑さの分析:
1> 時間計算量: 挿入アルゴリズムは、前のシーケンスが適切であることを確認し、現在の数値を特定のシーケンスに挿入するだけで済みます。その前に配置できます。したがって、配列が元々順序付けされている場合、配列の時間計算量は最良の場合 O(n) です。配列がたまたま反転 = reversed になった場合、たとえば、元の配列は 5 4 3 2 1 であり、次の場合小さいものから大きいものへ並べたい場合は、各パスの前の数字を後ろに移動する必要があり、合計は n-1 + n-2 + ... + 2 + 1 = n * (n-1) / 2 = 0.5 * n2 - 0.5 * n 回を実行する必要があり、低電力と係数を削除するため、最悪の場合の時間計算量は O(n2) になります。
平均時間計算量 (n+n2)/2、つまり平均時間計算量は O(n2)
2> 空間計算量:
挿入ソート アルゴリズム。現在の数値、添え字、およびサイズを一時的に保存するために必要な変数は 2 つだけです。 of n 無関係なので、空間複雑度は次のようになります: O(1)
(4) Java コード:
import java.util.Scanner;
public class Insert{
public static void main(String[] args) {
Scanner sca = new Scanner(System.in);
int len = sca.nextInt();
int [] data = new int [len];
int j, t;
for(int i = 0; i < len; i++) {
data[i] = sca.nextInt();
}
for(int i = 1; i < data.length; i++) {
t = data[i];
j = i - 1;
while(j >= 0 && t < data[j]) {
//比当前扫描位置小 并且没有到数组头部,继续往前扫描
data[j + 1] = data[j]; //让上一个单元,等于当前扫描单元的值
j--; //继续往前扫描
}
data[j + 1] = t; //让当前扫描位置的上一个单元等于所要插入的元素
}
for(int i = 0; i < data.length; i++) {
System.out.print(data[i] + " ");
}
sca.close();
}
}
2. ヒルソート:
(1) アニメーションのデモンストレーション:
(2) アルゴリズム分析:
ヒル ソートは、レコードを添え字の一定の増分でグループ化し、直接挿入ソート アルゴリズムを使用して各グループをソートし、増分が徐々に減少するにつれて、各グループに含まれるキーワードをソートします。増分が 1 まで減少すると、ファイル全体が 1 つのグループに分割されるだけとなり、アルゴリズムは終了します。
単純な挿入ソートは非常に規則的で、配列の分布がどのようなものであっても、[5,4,3,2,1,0] のように逆順に要素を段階的に比較、移動、挿入します。配列の末尾の 0 最初の位置に戻るのは非常に手間がかかり、要素の比較と移動には n-1 回かかります。
しかし、Hill ソートは、配列内のグループ化をジャンプする戦略を採用しており、配列要素を一定の増分でいくつかのグループに分割し、グループ化による挿入ソートを実行し、その後徐々に増分を減らして、グループによる挿入ソート操作を実行し続けます。増分が 1 になるまで。ヒルソートでは、この戦略を使用して、初期段階でマクロの観点から配列全体を基本的に整然とし、小さいものを基本的に前に、大きいものを後ろに配置します。次に、増分が 1 になるまで増分を減らします。実際、ほとんどの場合、微調整するだけで済み、大量のデータ移動は必要ありません。
次に、ヒル ソートの基本手順を見てみましょう。ここでは、増分gap=length/2を選択し、gap = gap/2 のように増分を狭め続けます。この増分選択は、シーケンス {n で表すことができます。 / 2,(n/2)/2…1}、インクリメンタル シーケンスと呼ばれます。ヒル ソートの増分シーケンスの選択と証明は数学的問題です。選択された増分シーケンスはより一般的に使用されており、ヒルによって提案された増分でもあります。これはヒル インクリメンタルと呼ばれますが、実際にはこの増分シーケンスは最適ではありませんの。次に、ヒル インクリメントを使用した例を示します。
(3) 複雑さの分析:
1> 時間計算量: 最悪の場合、2 つの数値ごとに 1 回比較して交換する必要があります。その場合、最悪の場合の時間計算量は O(n2) になります。最良の場合、配列は順序付けされます。交換する必要はなく、比較するだけでよい場合、最良の場合の時間計算量は O(n) です。
2> ヒル ソートでは、n のサイズに関係なく、2 つの数値の交換に 1 つの変数のみが必要なため、空間複雑度は O(1) になります。
選択ソート: 単純な選択ソートとヒープソート
(4) Java コード:
import java.util.Scanner;
//插入排序演变而来,缩小增量
public class 希尔 {
public static void main(String[] args) {
Scanner sca = new Scanner(System.in);
int len = sca.nextInt();
int [] data = new int [len];
int i, j;
int r, temp;
for(i = 0; i < len; i++) {
data[i] = sca.nextInt();
}
for(r = data.length / 2; r >= 1; r /= 2) {
//增量 每次缩减一半
for(i = r; i < data.length; i++) {
//让这个增量所在的序列对进行比较(序列对排序就是插入排序)
temp = data[i];
j = i - r; //每次 + 1 的在 '本序列对' 进行扫描
while(j >= 0 && temp < data[j]) {
data[j + r] = data[j];
j -= r;
}
data[j + r] = temp;
}
}
for(int k = 0; k < data.length; k++) {
System.out.print(data[k] + " ");
}
sca.close();
}
}
1. 単純な選択ソート:
(1) アニメーションのデモンストレーション:
(2) アルゴリズム分析:
1> 最初の数値が後続のすべての数値と比較して最初の数値より小さい (または小さい) 場合、小さいほうの数値を一時的に保存します。最初の旅行、最初の数値を一時的に保存されている最小の数値と交換します。最初の数値が最小 (または最大の数値)
2> 添字を 2 番目の位に移動し、2 番目の数値を以下のすべての数値と比較し、
3> ポインタが最後から 2 番目の桁に移動するまで上記の手順を繰り返し、最後から 2 番目の小さい (または最後から 2 番目の) 数値を決定します。最大)の数値を入力し、最後の桁が決定され、ソートが完了します。
(3) 複雑さの分析:
1> 時間計算量: 元の配列が順序付けされているかどうかに関係なく、すべての数値は他の数値と 1 回、(n-1) 回、2 回比較する必要があるため、時間計算量は O(n2) です。分解: n2-2n+1、低電力と定数を削除し、n2 を残すため、最終的な時間計算量は n2 になります。
2> 空間複雑度: 補助変数が 2 つだけ定義されているため、n のサイズとは関係がなく、空間複雑度は O(1) です。
(4) Java コード:
import java.util.Scanner;
public class Choice {
public static void main(String[] args) {
Scanner sca = new Scanner(System.in);
int len = sca.nextInt();
int [] data = new int [len];
int index, temp;
for(int i = 0; i < len; i++) {
data[i] = sca.nextInt();
}
for(int i = 0; i < len - 1; i++) {
index = i;
for(int j = i + 1; j < len; j++) {
if(data[j] < data[index]) {
index = j;
}
}
if(index != i) {
temp = data[i];
data[i] = data[index];
data[index] = temp;
}
}
for(int i = 0; i < len; i++) {
System.out.print(data[i] + " ");
}
sca.close();
}
}
- ヒープのソート:
(1) アニメーションのデモンストレーション:
(2) まずヒープの構造を理解しましょう:
まず、ヒープの関連概念を理解します: ヒープは次のプロパティを持つ完全なバイナリ ツリーです: 各ノードの値は次の値より大きいノードの値はビッグトップヒープと呼ばれ、各ノードの値はその左右の子ノードの値以下であり、スモールトップヒープと呼ばれます。次の図に示すように
、ヒープ内のノードにレイヤーごとに番号を付け、この論理構造を次のように配列にマッピングします。
配列は論理的にはヒープ構造であり、単純な式を使用してヒープを記述します。定義は次のとおりです。
大きなトップヒープ: arr[i] >= arr[2i+1] && arr[i] >= arr[2i+2] 小さなトップヒープ: arr[i] <= arr[
2i+1] && arr [i] <= arr[2i+2]
次に、ヒープ ソートの基本的な考え方と基本的な手順を見てみましょう。
ヒープ ソートの基本的な考え方は次のとおりです。ソートされるシーケンスの大きな上部ヒープを構築します。今回はシーケンス全体の最大値がヒープの先頭にあるルートノードになります。これを末尾の要素と入れ替えると、末尾が最大値となります。次に、残りの n-1 個の要素をヒープに再構築し、n 個の要素のうち 2 番目に小さい値が取得されます。このように繰り返し実行すると、順序付けされたシーケンスを取得できます。
ステップ 1:
初期ヒープを構築します。指定された順序なしシーケンスを大きな上部ヒープに構築します (通常、昇順では大きな上部ヒープが使用され、降順では小さな上部ヒープが使用されます)。
1>. 指定された順序なしシーケンス構造が次のとおりであると仮定します
2>. この時点で、最後の非リーフ ノードから開始します (リーフ ノードは当然調整する必要はありません。最初の非リーフ ノード arr.length/ 2-1=5/ 2-1=1 (以下の 6 つのノード)、左から右、下から上に調整します。
3>. 2 番目の非葉ノードを見つけます 4. [4,9,8] の 9 つの要素が最大であるため、4 と 9 が交換されます。
このとき、交換により子ルート [4,5,6] の構造的な混乱が発生しました。調整を続け、[4,5,6] で 6 が最大となり、4 と 6 を交換します。
この時点で、不要なシーケンスから大きな上部ヒープを構築します。
ステップ 2 先頭要素と末尾要素を交換して、末尾要素を最大にします。次に、ヒープの調整を続けて、先頭の要素を末尾の要素と交換して、2 番目に大きい要素を取得します。交換、再構築、交換を繰り返します。
a. 先頭要素 9 と末尾要素 4 を交換します。
b. 引き続きヒープ定義を満たすように構造を再調整します。
c. 次に、先頭の要素 8 を最後の要素 5 と交換して、2 番目に大きい要素 8 を取得します。フォロー
アップ プロセスでは、調整、交換などを続けます。 (3)ヒープソート
の基本的な考え方を簡単に要約します:
a. 順序付けられていないシーケンスをヒープに構築し、昇順と降順に従って大きい上部ヒープまたは小さい上部ヒープを選択します。順序要件; b
. ヒープの先頭要素と末尾要素を交換し、最大の要素を配列の末尾に「シンク」します;
c. ヒープ定義を満たすように構造を再調整し、ヒープの先頭要素の交換を続けます。現在の終了要素を含むヒープを削除し、シーケンス全体が整うまで調整と交換のステップを繰り返し実行します。
(4) 複雑さの分析:
1> 時間計算量: ヒープ ソートは選択ソートの一種で、全体は主に初期ヒープの構築 + ヒープの最上位要素と最下位要素の交換、およびヒープの再構築の 2 つの部分で構成されます。初期ヒープ構築の推定複雑さは O(n) です。ヒープの交換および再構築のプロセスでは、n-1 回交換する必要があります。ヒープの再構築のプロセスでは、完全なバイナリの性質に従って、ツリー [log2(n-1) ,log2(n-2)...1] は徐々に減少し、およそ nlogn になります。したがって、ヒープソートの時間計算量は、最良の場合でも最悪の場合でも O(nlogn) レベルです。
2> ヒープのソートには補助配列は必要なく、補助変数が 1 つだけ必要です。占有スペースは一定で n とは関係がないため、スペースの複雑さは O(1) です。
(5) Java コード:
import java.util.Arrays;
import java.util.Scanner;
//选择排序思想演变而来
public class Heap{
public static void main(String[] args) {
Scanner sca = new Scanner(System.in);
int len = sca.nextInt();
int [] data = new int [len];
for(int i = 0; i < len; i++) {
data[i] = sca.nextInt();
}
heapSort(data);
System.out.println(Arrays.toString(data));
sca.close();
}
public static void heapSort(int [] data) {
int len = data.length;
//开始位置是最后一个非叶子节点,即最后一个节点的父节点(二叉树特性)
int start = (len - 1) / 2;
//初始化一个大顶堆(以每一个节点形成的堆都是一个大顶堆)
for(int i = start; i >= 0; i--) {
creatMaxHeap(data, len, i);
}
//先把数组中的第0个和堆中的最后一个数交换位置,再把前面的处理为大顶堆
for(int i = len - 1; i > 0; i--) {
int temp = data[0];
data[0] = data[i];
data[i] = temp;
creatMaxHeap(data, i, 0);
}
}
public static void creatMaxHeap(int [] data, int len, int index) {
//左子节点
int leftNode = 2 * index + 1;
//右子节点
int rightNode = 2 * index + 2;
int max = index;
//和两个子节点分别对比,找出最大的节点
if(leftNode < len && data[leftNode] > data[max]) max = leftNode;
if(rightNode < len && data[rightNode] > data[max]) max = rightNode;
//交换位置
if(max != index) {
int temp = data[index];
data[index] = data[max];
data[max] = temp;
//交换位置以后,可能会破坏之前排好的堆,所以,之前排好的堆需要重新调整
creatMaxHeap(data, len, max);
}
}
}
マージ ソート: 双方向マージと多方向マージ (ここでは説明しません)
1. 双方向マージ
(1) アニメーション デモンストレーション:
(2) アルゴリズム分析:
マージ ソートは、元の配列を分割できなくなるまで再帰的に半分に分割します。分割後 (要素が 1 つだけ残っている)、最小の配列から上に向かってマージとソートを開始します。
1> マージして上にソートする場合、ソート用に一時的な保存配列が必要です。
2> マージする 2 つの配列を最初から開始します。比較し、小さい方を一時記憶配列に入れ、ポインタを後方に移動します。
3> 一方の配列が空になるまで、この時点ではどちらの配列が空であるかを判断する必要はありません。2 つの配列の残りの要素を直接追加します。一時記憶配列、4
> 次に、一時記憶配列のソートされた要素を元の配列に入れ、2 つの配列が 1 つに結合され、この旅は終了します。
アイデアの分析によると、各トリップの実行フローは次の図に示されます。
(3) アルゴリズムの複雑さの分析:
1> 時間計算量: 再帰的アルゴリズムの時間計算量の式: T[n] = aT[n/b] + f (n)
元の配列が順序付けされているかどうかに関係なく、再帰的に分離して上方にマージする必要があるため、時間計算量は常に O(nlog2n) 2> 空間計算量: 2 つの配列がマージされ並べ替えられるたびに、それらの配列
は長さ n の配列は、マージされたシーケンスを保持するための補助配列として使用されるため、空間計算量は O(n) になります。
(4) Java コード:
import java.util.Arrays;
import java.util.Scanner;
public class Merge {
public static void main(String[] args) {
Scanner sca = new Scanner(System.in);
int len = sca.nextInt();
int [] data = new int [len];
for(int i = 0; i < len; i++) {
data[i] = sca.nextInt();
}
System.out.println(Arrays.toString(data));
mergeSort(data, 0, data.length - 1);
System.out.println(Arrays.toString(data));
sca.close();
}
public static void mergeSort(int [] data, int low, int high) {
int middle = (low + high) / 2;
//如果最后只剩下一个不再递归
if(low < high) {
//处理左边
mergeSort(data, low, middle);
//处理右边
mergeSort(data, middle + 1, high);
//归并
merge(data, low, middle, high);
}
}
public static void merge(int [] data, int low, int middle, int high) {
//用于存储归并后的临时数组
int [] temp = new int [high - low + 1];
//记录第一个数组中需要遍历的下标
int i = low;
//记录第二个数组中需要遍历的下标
int j = middle + 1;
//记录临时数组的下标
int index = 0;
//遍历两个数组取出小的数字,放入临时数组
while(i <= middle && j <= high) {
if(data[i] < data[j]) {
//把小的数放到数组中
temp[index] = data[i];
//下标移向后一位
i++;
}else {
temp[index] = data[j];
j++;
}
index++;
}
while(i <= middle) {
temp[index] = data[i];
i++;
index++;
}
while(j <= high) {
temp[index] = data[j];
j++;
index++;
}
//把临时数组的中的数据重新存入原数组
for(int k = 0; k < temp.length; k++) {
data[k + low] = temp[k];
}
}
}
非比較ソート: 基数ソート (バケット ソートとカウンティング ソートについては説明されなくなりました)
(1) アニメーション デモンストレーション:
(2) アルゴリズム思考分析:
基数ソートの i 番目のパスは、次の各数値の i ビットを取得します。ソートされる配列 数値が tempj (j=1-10) キューに入れられ、その後データがこれら 10 個のキューから取り出され、i がソートする最大桁数を超えるまで元の配列に戻されます。整理される。
1. 配列の最大桁数が n 桁の場合、n 回ソートする必要があります。たとえば、配列の最大桁数が 3 桁の場合、3 回ソートする必要があります。
2. 配列内に m 個の数値がある場合、i 桁の数値 j を一時的に格納するには、長さ m の 10 個の配列 tempj (j=0-9) が必要です。たとえば、最初のパスでは、各桁は次のようになります。 0はtemp0配列に、1の桁はtemp1配列に割り当てられます...
3. 割り当てが完了すると、tempj配列から最初のデータに続いてデータが順番に取り出されます。 -in-first-out の原則。たとえば、配列 {1, 11 , 2, 44, 4} の場合、最初の割り当て後は、temp1={1,11}、temp2={2}、temp4={44、 4}、順に要素を取り出した後、{1, 11, 2, 44, 4}、最初のパスの終了
4。n パス後にループが終了し、ソートが完了します。
アイデアの分析によると、各トリップの実行フローは次の図に示されています。
配列 {53, 3, 542, 748, 14, 214, 154, 63, 616} を基数ソートによってソートします。
(3) 複雑さ分析:
1> 時間計算量: 各キーワード バケットの割り当てには O(n) 時間の計算量が必要で、割り当て後の新しいキーワード シーケンスの取得には O(n) 時間の計算量が必要です。ソート対象のデータを d 個のキーワードに分割できる場合、基数ソートの時間計算量は O(d 2n) になります。もちろん d は n よりもはるかに小さいため、基本的には線形です。係数2は省略可能で、配列の順序に関係なく1桁から最大桁まで並べる必要があるため、時間計算量は常にO(dn)。このうち、n は配列の長さ、d は最大桁数です。
2> 空間複雑さ:
基数ソートの空間複雑さは O(n+k) です。ここで、k はバケットの数であり、n 個の数値を割り当てる必要があります。
(4) Java コード:
import java.util.Arrays;
import java.util.Scanner;
public class Base {
public static void main(String[] args) {
Scanner sca = new Scanner(System.in);
int len = sca.nextInt();
int [] data = new int [len];
for(int i = 0; i < len; i++) {
data[i] = sca.nextInt();
}
System.out.println(Arrays.toString(data));
radixSort(data);
System.out.println(Arrays.toString(data));
sca.close();
}
public static void radixSort(int [] data) {
//存放数组中最大的数字
int max = Integer.MIN_VALUE;
for(int i = 0; i < data.length; i++) {
if(data[i] > max) {
max = data[i];
}
}
//计算最大数字是几位
int maxLength = (max + "").length();
//用于临时存储数据的数组
int [][] temp = new int [10][data.length];
//用于记录在temp中相应的数组中存放的数字数量
int [] counts = new int [10];
//根据最大长度的数,决定比较的次数
for(int i = 0, n = 1; i < maxLength; i++, n *= 10) {
//把每一个数字分别计算余数
for(int j = 0; j < data.length; j++) {
//计算余数
int ys = data[j] / n % 10;
//把当前遍历的数据放入指定的数组中
temp[ys][counts[ys]] = data[j];
//记录数量
counts[ys]++;
}
//记录取出的元素需要放的位置
int index = 0;
//把数字取出来
for(int k = 0; k < counts.length; k++) {
//记录数量的数组中当前余数记录的数量不为0
if(counts[k] != 0) {
//循环取出元素
for(int h = 0; h < counts[k]; h++) {
//取出元素
data[index] = temp[k][h];
//记录下一个位置
index++;
}
//把数量置为0
counts[k] = 0;
}
}
}
}
}
このブログが 8 つの並べ替えアルゴリズムを学ぶのに役立つ場合は、高評価を押してください。ああ!!!!!!