ソートアルゴリズムの紹介
並べ替えは、
並べ替えアルゴリズムとも呼ばれます。並べ替えは、データのセットを指定された順序で配置するプロセスです。
ソートされたカテゴリ:
1)内部ソート:
ソートのために処理する必要のあるすべてのデータを内部メモリにロードすることを指します。
2)外部ソーティング方法:
データ量が多すぎてすべてをメモリにロードできないため、外部ストレージで並べ替える必要があります。
一般的なソートアルゴリズムの分類を図に示します。
バブルソート
バブルソートの基本的な考え方は、ソートシーケンスを前から後ろに処理し(下付き文字が小さい要素から開始)、隣接する要素の値を順番に比較し、逆の順序が見つかった場合は交換することです。値の大きい要素を作るために前から後ろに徐々に移動し、水中の泡のように徐々に上に上昇します。
元の配列:3、9、-1、10、20
ファーストパスソート
(1)3、9、-1、10、20 //隣接する要素が逆になっている場合は交換
(2)3、-1、9、10、20
(3)3、-1、9、10、20
(4)3、-1、9、10、20
2番目のパスの並べ替え
(1)-1、3、9、10、20 //交換
(2)-1、3、9、10、20
(3)-1、3、9、10、20
3番目のパスの並べ替え
(1)-1、3、9、10、20
(2)-1、3、9、10、20
4番目のパスの並べ替え
(1)-1、3、9、10、20
要約バブリングソートルール
(1)配列のサイズに対して合計-1つの大きなループを実行します
(2)各パスのソート数は徐々に減少しています
(3)一定の選別で交換がない場合は、バブリング選別を早期に終了することができます。これは最適化です
各要素はソートプロセス中に常にその位置に近づいているため、比較で交換がない場合は、順序が正しいため、要素が交換されたかどうかを判断するには、ソートプロセス中にフラグを設定する必要があります。これにより、不要な比較を減らすことができます。(この
ここで説明する最適化は、バブルソートが記述された後に実行できます)
コード
public static void bubbleSort(int[] arr){
int temp = 0;
//标识变量,表示是否进行过交换
boolean flag = false;
//时间复杂度O(n^2)
for (int i = 0; i < arr.length - 1; i++) { //一共要排序几次
for (int j = 0; j < arr.length - 1 - i; j++) {//每次排序需要比较的次数
if (arr[j] > arr[j + 1]){
flag = true;
temp = arr[j + 1];
arr[j + 1] = arr[j];
arr[j] = temp;
}
}
if (flag){//出现过交换,重置flag
flag = false;
}else//在上一趟排序中,一次交换也没有发生过
break;
}
}
ソートを選択
選択的ソートは、指定された規則に従ってソートするデータから要素を選択し、規則に従って位置を交換してソートの目的を達成するという内部ソート方法にも属します。
選択ソートのアイデア
選択ソートも簡単なソート方法です。基本的な考え方は、最初にarr [0] 〜arr [n-1]から最小値を選択し、arr [0]と交換し、2番目にarr [1] 〜arr [n-1]から選択することです。時間最小値、arr [1]と交換、arr [2] 〜arr [n-1]から最小値を3回選択、arr [2]、...と交換、i回目からarr [i-1] 〜arr [n-1]から最小値を選択し、arr [i-1]、...と交換し、arr [n-2] 〜arr [n-1]から最小値を選択しますn-1回目、arr [n-2]と交換します。Exchangeは、合計n-1回、ソートコードに従って小さいものから大きいものへと並べられた順序付けられたシーケンスを取得します。
元の配列:101、34、119、1
第一ラウンドのソーティング: 1、34、119、101
第二ラウンドの選別: 1、34、119、101
第三ラウンドの選別: 1、34、101、119
サマリーの選択と並べ替えのルール
1.並べ替えの合計配列サイズを選択します-1ラウンドの並べ替え
2.ソートのすべてのラウンドは別のサイクルであり、サイクルのルール(コード)
2.1最初に、現在の数が最小の数であると仮定します
2.2次に、次の各数値と比較します。現在の数値よりも小さい数値を見つけた場合は、最小数を再決定して、下付き文字を取得します。
2.3配列の最後まで移動すると、このラウンドの最小数と下付き文字2.4 Exchange [コードを続行する]が表示されます。
コード
public static void selectSort(int[]arr){
for (int i = 0; i < arr.length - 1; i++) {
int minIndex = i;
int min = arr[i];
for (int j = i + 1; j < arr.length; j++) {
if (min > arr[j]){
minIndex = j;
min = arr[j];
}
}
//将最小值放在arr[i],即交换
if (minIndex != i){//如果最小值的下标改变了则交换
arr[minIndex] = arr[i];
arr[i] = min;
}
}
}
挿入ソート
挿入ソートは、ソートの目的を達成するために、要素を挿入することによってソートされる要素の適切な位置を見つけるという内部ソート方法に属します。
ソートアイデアを挿入
挿入ソート(挿入ソート)の基本的な考え方は、並べ替えられるn個の要素を順序付きリストと順序なしリストとして扱うことです。最初は、順序付きリストには1つの要素のみが含まれ、順序なしリストにはn-1個の要素が含まれます。 、ソートプロセス中に最初の要素が順序なしリストから取得されるたびに、そのソートコードが順序付きリスト要素のソートコードと順番に比較され、順序付きリストの適切な位置に挿入されて次のようになります。新しい順序付きリスト。
元の配列:(101)、34、119、1
オレンジ色の矢印は、挿入する要素の添え字を示します
緑の矢印は、挿入する要素を示します
最初の挿入ソート
2番目の挿入ソート
3番目の挿入ソート
コード
public static void insertSort(int[] arr){
int insertIndex = 0;
int insertValue = 0;
for (int i = 1; i < arr.length; i++) {
insertIndex = i - 1;
insertValue = arr[i];
while(insertIndex >= 0 && arr[insertIndex] > insertValue){
arr[insertIndex + 1] = arr[insertIndex];
insertIndex--;
}
//优化是否需要赋值
if (insertIndex + 1 != i){
arr[insertIndex + 1] = insertValue;
}
}
}
単純な挿入ソートの問題を分析する
単純な挿入ソートで起こりうる問題を見てみましょう。
Array arr = {2,3,4,5,6,1}この時点で挿入する必要のある数値1(最小)。プロセスは次のとおりです。
{2,3,4,5,6,6}
{2,3,4,5,5,6}
{2,3,4,4,5,6}
{2,3,3,4,5,6}
{2,2,3,4,5,6}
{1,2,3,4,5,6}
結論:挿入する数が少ない場合、後方シフトの数が大幅に増加し、効率に影響を与えます。
ヒルソート
ヒルソーティングは、1959年にドナルドシェルによって提案されたソーティングアルゴリズムです。ヒルソートも一種の挿入ソートであり、改良後の単純な挿入ソートのより効率的なバージョンであり、縮小増分ソートとしても知られています。
ヒルソーティングの基本的な考え方
ヒルソートとは、ターゲットの特定の増分でレコードをグループ化し、直接挿入ソートアルゴリズムを使用して各グループをソートすることです。増分が減少すると、各グループに含まれるキーワードが増えます。増分が1に減少すると、ファイル全体がが1つのグループに分割され、アルゴリズムが終了します
誰もが理解しやすいように
ヒルソートでは、順序付けられたシーケンスを挿入するときに、最初に交換方法(バブリング方法)が使用されます
public static void shellSort(int[] arr){
int temp = 0;
int count = 0;
for (int gap = arr.length / 2; gap > 0; gap /= 2) {
for (int i = gap; i < arr.length; i++) {
for (int j = i - gap; j >= 0; j -= gap) {
if (arr[j] > arr[j + gap]){//这里采用交换法
temp = arr[j];
arr[j] = arr[j + gap];
arr[j + gap] = temp;
}
}
}
}
}
ヒルソートの場合、順序付けられたシーケンスを挿入するときにシフト法(真のヒルソート)が使用されます(挿入法)
public static void shellSort(int[]arr){
int count = 0;
for (int gap = arr.length / 2; gap > 0; gap /= 2) {
for (int i = gap; i < arr.length; i++) {
int insertIndex = i - gap;
int insertValue = arr[insertIndex + gap];
while(insertIndex >= 0 && insertValue < arr[insertIndex]){
arr[insertIndex + gap] = arr[insertIndex];
insertIndex -= gap;
}
if (insertIndex != (i - gap)){
arr[insertIndex + gap] = insertValue;
}
}
}
}
クイックソート
クイックソート(Quicksort)は、バブルソートの改良版です。基本的な考え方は、並べ替えによって並べ替えるデータを2つの独立した部分に分割し、一方の部分のすべてのデータをもう一方の部分のすべてのデータよりも小さくしてから、この方法に従ってデータの2つの部分をすばやく並べ替えることです。並べ替えプロセス全体を再帰的に実行できるため、データ全体が順序付けられたシーケンスになります
コード
public static void quickSort(int[] arr,int left,int right){
int r = right;
int l = left;
int temp = 0;
int pivot = arr[(right + left) / 2];
while(l < r){
while(arr[l] < pivot){
l++;
}
while(arr[r] > pivot){
r--;
}
if(l == r)
break;
temp = arr[r];
arr[r] = arr[l];
arr[l] = temp;
if (arr[l] == pivot){
r--;
}
if (arr[r] == pivot){
l++;
}
}
if (l == r){
l += 1;
r -= 1;
}
//向左递归
if(left < r){
quickSort(arr,left,r);
}
//向右递归
if(right > l){
quickSort(arr,l,right);
}
}
マージソート
マージソート(MERGE-SORT)は、マージのアイデアを使用するソート方法です。アルゴリズムは、古典的な分割統治戦略(分割統治)を使用して問題を小さな問題に分割し、それらを再帰的に解決します。、そして征服段階は、異なる段階で得られた答えを一緒に「修正」します。つまり、分割統治法です。
注:この構造は完全な二分木に非常に似ていることがわかります。この記事のマージソートは再帰的に実装されます(反復的に実装することもできます)。ステージは、サブシーケンスを再帰的に分割するプロセスとして理解できます。
処理段階を見てみましょう。2つのすでに順序付けられたサブシーケンスを順序付けられたシーケンスにマージする必要があります。たとえば、上の図の最後のマージには[4,5,7,8]と[1,2、3、 6]すでに順序付けられた2つのサブシーケンスが、最終シーケンス[1,2,3,4,5,6,7,8]にマージされます。実装手順を見てみましょう。
コード
ルール
/**
*
* @param arr 排序的原始数组
* @param left 左边有序序列的初始索引
* @param mid 中间索引
* @param right 右边索引
* @param temp 中转数组
*/
public static void merge(int[] arr,int left,int mid,int right,int[]temp){
//System.out.println("*****");
int i = left;
int j = mid + 1;
int t = 0;
/*
(一)
先把两边有序的数据按照规则填充到temp数组
指导左右两边的有序序列,有一边处理完毕
*/
while(i <= mid && j <= right){
temp[t++] = arr[i] > arr[j] ? arr[j++] : arr[i++];
}
/*
(二)
把所有剩余数据的一边一次全部填充到temp
*/
while(i <= mid){
temp[t++] = arr[i++];
}
while (j <= right){
temp[t++] = arr[j++];
}
/*
(三)
将temp数组的元素拷贝到arr
*/
t = 0;
int tempLeft = left;
//System.out.println("tempLeft = " + tempLeft + "right = " + right);
while(tempLeft <= right){
arr[tempLeft++] = temp[t++];
}
}
ポイント(再帰的)
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);
}
}
カーディナリティソート(バケットソート)
1.基数ソート(基数ソート)は、「バケットソート」またはビンソートとも呼ばれる「分布ソート」に属します。名前が示すように、キー値の各ビットの値でソートされます。要素はに割り当てられます。ソート効果を達成するためのいくつかの「バケット」
2.カーディナリティソート法は安定したソート方法であり、基数ソート法は高効率の安定したソート方法です。
3.基数ソート(基数ソート)はバケットソートの拡張です
4.カーディナルソーティングは、1887年にHermannHolleriによって発明されました。これは次のように実装されます。整数は桁ごとに異なる数値にカットされ、各桁ごとに比較されます。
基数ソートの基本的な考え方
同じ桁の長さと比較するためにすべての値を統一し、短い桁の数字の前にゼロを埋め込みます。次に、最下位ビットから始めて、順番に1回ソートします。最下位から最上位にソートした後、シーケンスは順序付けられたシーケンスになります。
この説明は理解しにくいです。基本的な並べ替えの手順を理解するために、以下の図の説明を見てみましょう。
配列の初期状態arr = {53、3、542、748、14、214}
ラウンド1:
(1)各要素の1桁を取り出し、その番号をどのバケットに配置するかを確認します(1次元配列)
(2)バケットの順序に従って(1次元配列の添え字を1つずつ取り出して元の配列に入れます)
配列ソートの最初のラウンドarr = {542、53、3、14、214、748}
ラウンド2の並べ替え:
(1)各要素の10桁を取り出し、その番号をどのバケットに配置するかを確認します(1次元配列)
(2)バケットの順序に従って(1次元配列の添え字を1つずつ取り出して元の配列に入れます)
配列のソートの2回目のラウンドarr = {3、14、214、542、748、53}
ラウンド3の並べ替え:
(1)各要素の100桁を取り出し、その番号をどのバケットに配置するかを確認します(1次元配列)
(2)バケットの順序に従って(1次元配列の添え字を1つずつ取り出して元の配列に入れます)
配列のソートの第3ラウンドarr = {3、14、53、214、542、748}
上記は基数ソートの実装プロセスです
コード
コードの説明
配列の最大要素の桁数を取得します
2次元配列bucket [10] [arr.length]を使用して、バケットをシミュレートします
BucketElementCounts [10]を使用して、各バケットのポインターをシミュレートします
public static void redixSort(int[]arr){
//获取数组中最大元素的位数
int max = arr[0];
for (int i = 0; i < arr.length; i++) {
if(max < arr[i])
max = arr[i];
}
int maxLength = (max + "").length();
//定义一个二维数组模拟桶
int [][] bucket = new int[10][arr.length];
//为了记录每个桶中的元素个数定义一个一维数组
int [] bucketElementCounts = new int[10];
for (int i = 0, n = 1; i < maxLength; i++,n *= 10) {
//入桶
for (int j = 0; j < arr.length; j++) {
int digitOfElement = arr[j] / n %10;
bucket[digitOfElement][bucketElementCounts[digitOfElement]] = arr[j];
bucketElementCounts[digitOfElement]++;
}
int index = 0;
//出桶
for (int j = 0; j < bucketElementCounts.length; j++) {
if(bucketElementCounts[j] != 0){
for (int k = 0; k < bucketElementCounts[j]; k++) {
arr[index++] = bucket[j][k];
}
}
//取出元素后,需要将bucketElementCount中的元素清零
bucketElementCounts[j] = 0;
}
//System.out.println("第" + (i + 1) + "次排序后的数组" + Arrays.toString(arr));
}
}
ソートアルゴリズムのスピードテスト
以下では、テスト用に長さ80,000のランダム配列を作成しました
ハードウェア:CPU8世代i7
public static void main(String[] args) {
System.out.println("测试排序算法的时间");
int[] arr = new int[80000];
for (int i = 0; i < arr.length; i++) {
arr[i] = (int)(Math.random() * 8000000);
}
Long startTime = System.currentTimeMillis();
redixSort(arr);
Long endTime = System.currentTimeMillis();
System.out.println(endTime - startTime + "ms");
}
個別にテストする
バブルソート(最適化後)
多くのテストの後、80,000のデータバブリングソートには約10秒かかります
ソートを選択
多くのテストの後、80,000のデータの選択と並べ替えの時間は約1900ms〜2200msです
挿入ソート
多くのテストの後、80,000のデータ挿入とソート時間は約528ms-600msです
ヒルソート
多くのテストの後、80,000データのヒルソート時間は約17ms〜22msです
800,000データをテストする
8,000,000データをテストする
クイックソート
多くのテストの後、80,000個のデータがすばやくソートされます。およその時間は15ミリ秒から22ミリ秒です。
800,000データをテストする
8,000,000データをテストする
基数ソート
多くのテストの後、80,000のデータベースがソートされます。およその時間は18ms-33msです。
800,000データをテストする
8,000,000データをテストする
分析
関連用語の説明:
安定:aが元々bの前にあり、a = bの場合、ソート後もaはbの前にあります。
不安定:aが元々bの前にあり、a = bの場合、ソート後にaがbの後ろに表示されることがあります。
内部ソート:すべてのソート操作はメモリ内で完了します。
外部ソート:データが大きすぎるため、データはディスクに配置され、ソートはディスクとメモリ間のデータ転送によってのみ実行できます。
時間計算量:アルゴリズムの実行にかかる時間。
スペースの複雑さ:プログラムの実行に必要なメモリの量。
n:データサイズ
k:「バケット」の数
インプレース:追加のメモリを消費しません
アウトプレース:余分なメモリを消費する