配列ソートの詳細説明
1. ソートアルゴリズムのまとめ
(1 )
非線形時間ソート アルゴリズム: 要素間の相対的な順序は比較によって決定されます. その時間計算量は O(nlogn) を超えることができないため、非線形時間比較ソートと呼ばれます. このようなアルゴリズムには、バブル ソート、挿入ソート、選択ソート、ヒル ソート、マージ ソート、クイック ソート、ヒープ ソートなどがあります。
(2) 線形時間非比較ソートアルゴリズム: 比較によって要素の相対的な順序を決定するのではなく、比較ソートに基づいて時間の下限を突破でき、線形時間で実行されるため、線形時間非比較と呼ばれます。比較ソート。このようなアルゴリズムには、バケット ソート、カウンティング ソート、基数ソートなどがあります。ただし、これらのアルゴリズムの時間の複雑さは比較的低いですが、並べ替えるデータには厳しい要件もあります。
さまざまなアルゴリズムの複雑さの分析は次のとおりです。
2. ソートアルゴリズムの原理と実装
2.1 マージソート
- マージソートの原理
は分割統治の考え方を採用しており、配列をソートする場合は、まず配列を真ん中から2つに分割し、次に2つの部分を別々にソートし、次に2つの部分をマージします一緒にソートされているため、配列全体がソートされます。コアは merge() マージ関数です。 - 達成
/**
* 归并排序:
* 采用分治思想,如果要排序一个数组,我们先把数组从中间分成前后两个部分,
* 然后对前后两个部分分别进行排序,再将排好序的两部分合并在一起,这样整个数组就都有序了。
* @author mling
*/
public class mergeSortTest {
public static void main(String[] args) {
int[] arr = new int[]{1,3,2,5,3};
mergeSort(arr,0,arr.length-1);
print(arr);
}
/**归并算法:
* @param arr 数组
* @param l 待排序的起始下标(左边)
* @param r 待排序的终止下标(右边)
* */
public static void mergeSort(int[] arr,int l,int r){
if(l >= r){//递归终止条件
return;
}
int mid = (l+r)/2;//取数组的中间点
mergeSort(arr,l,mid);//对数组前半部分进行排序
mergeSort(arr,mid+1,r);//对数组后半部分进行排序
merge(arr,l,r,mid);//合并数组前半部分和后半部分
}
/**
* 合并数组前半部分和后半部分: 从小到大
* //l为起点,r为终点,mid为第1区间与第2区间的分界线
*/
private static void merge(int[] arr, int l, int r, int mid) {
//初始化变量:i表示前半部分的下标,j表示后半部分的下标,k表示临时数组的下标
int i=l,j=mid+1,k=0;
int[] tmp = new int[r-l+1];
while(i<=mid && j<=r){//1,2区间均未结束
if(arr[i] <= arr[j]){//1区间的数<2区间
tmp[k++] = arr[i++];
}else{
tmp[k++] = arr[j++];
}
}
//将剩余数据拷贝到临时数组中
for(;i<=mid;i++) tmp[k++]=arr[i];
for(;j<=r;j++) tmp[k++]=arr[j];
//将tmp临时数组拷贝回原数组
for(int x=0;x<r-l+1;x++){
arr[l+x]=tmp[x];
}
}
private static void print(int[] arr) {
for(int i=0; i<arr.length; i++){
System.out.print(arr[i]);
}
System.out.println();
}
}
- アルゴリズム分析
(1) マージソートは安定したソートアルゴリズムです;
(2) 最良、最悪、および平均時間の複雑度は O(nlogn) です。
(3) スペースの複雑さは O(n) であるため、インプレース ソートではありません。これはマージソートのアキレス腱でもあります。
2.2 クイックソート
- クイックソートの原則は、
分割統治の考え方を使用することでもあります。データ セットを並べ替える場合は、最初にこのデータ セット内のいずれかのデータをパーティション ポイント ピボットとして選択し、次にこのデータ セットをトラバースして、パーティション ポイント ピボットよりも小さいデータを左側に配置します。分割点ピボットよりも大きいデータを右側に配置し、ピボットを中央に配置します。次に、左右のパーツを別々に並べ替えます。コアは partition() 関数です。 - 達成
/**
* 快速排序
* @author rmling
*/
public class QuickSortTest {
public static void main(String[] args) {
int[] arr = new int[]{1,3,2,5,3};
quickSort(arr,0,arr.length-1);
print(arr);
}
/**快速排序算法:从小到大
* @param arr:数组 l:待排序的起始下标(左边) r:待排序的终止下标(右边)
* */
public static void quickSort(int[] arr,int l,int r){
if(l >= r){//递归终止条件
return;
}
int part = partition(arr,l,r);//获取分区点
quickSort(arr,l,part-1);//对左分区进行排序
quickSort(arr,part+1,r);//对右分区进行排序
}
/**返回分区点下标*/
private static int partition(int[] arr, int l, int r) {
int i=l;int prv=arr[r];
for(int j=l;j<=r;j++){
if(arr[j] < prv){
swap(arr,i,j);
i++;
}
}
swap(arr,i,r);
return i;
}
private static void swap(int[] a,int x,int y){
int temp=a[x];
a[x]=a[y];
a[y]=temp;
}
private static void print(int[] arr) {
for(int i=0; i<arr.length; i++){
System.out.print(arr[i]);
}
System.out.println();
}
}
- アルゴリズム分析
(1) クイック ソートは、スペースの複雑さが O(1) のインプレース ソートです。
(2) クイックソートは不安定なアルゴリズムです。
(3) クイックソートの最適で平均的な時間計算量は O(nlogn) です。しかし、極端な場合、配列内のデータが最初から 1、3、5、6、8 のように順序付けられている場合、毎回最後の要素をピボットとして選択すると、各分割によって得られる 2 つの間隔は異なります。同様に, クイックソートのプロセス全体を完了するには, 約 n 個のパーティション操作を実行する必要があります. 各パーティションに対して平均で約 n/2 個の要素をスキャンする必要があります. このとき, 時間計算量は O(n^2) です.は、最も悪い時間の複雑さです。
マージとクイックソートの違い:
(1) マージソートもクイックソートも分割統治の考え方を用いており、コードは再帰で実装されていますが、マージソートの核となるのはmerge()関数です。クイックソートの核となるのは partition() パーティション関数です。マージソートの処理は、下から上へ、最初にサブ問題を処理してからマージします。クイックソートの処理プロセスは正反対で、その処理プロセスは上から下へ、最初に分割し、次にサブ問題を処理します。
(2) マージソートアルゴリズムは、いずれの場合も時間計算量が比較的安定したソートアルゴリズムであり、時間計算量は O(nlogn) であり、クイックソートアルゴリズムの時間計算量は最悪の場合 O(n^2) であり、しかし、平均時間計算量は O(nlogn) であるだけでなく、クイック ソートの時間計算量が O(n^2) に縮退する確率は非常に小さく、合理的にピボットを選択することでこの状況を回避できます。
(3) マージソートはインプレースソートアルゴリズムではなく、スペースの複雑度は比較的高く、O(n) です; クイックソートはインプレースソートアルゴリズムで、スペースの複雑度は O(1) です。
(4) マージソートは安定したアルゴリズムですが、クイックソートは不安定です。
2.3 バブルソート
- 原則
バブル ソートは、隣接する 2 つのデータに対してのみ機能します。各バブリング操作では、隣接する 2 つの要素を比較して、サイズの関係の要件を満たしているかどうかを確認します。そうでない場合は、場所を交換させます。1 回のバブリングにより、少なくとも 1 つの要素が本来あるべき場所に移動され、n 回繰り返されて n 個のデータの並べ替えが完了します。 - アルゴリズムの説明
(1) 隣接する要素を比較します。最初の要素が 2 番目の要素よりも大きい場合は、両方を交換します。
(2) 最初の最初のペアから最後の最後のペアまで、隣接する要素の各ペアに対して同じことを行い、最後の要素が次のようになるようにします。は最大の数;
(3) 最後の要素を除くすべての要素について上記の手順を繰り返します;
並べ替えが完了するまで手順 1 ~ 3 を繰り返します。 - 達成
/**
* 冒泡排序
* @author mling
*/
public class BubbleSortTest {
public static void main(String[] args) {
int[] arr = new int[]{1,3,2,5,3};
bubbleSort(arr);
print(arr);
}
/**从小到大排序 */
public static void bubbleSort(int[] arr){
int len = arr.length;
for(int i=0; i<len; i++){
boolean flag = false;//提前退出冒泡循环的标志
for(int j=0;j<len-i-1;++j){
if(arr[j] > arr[j+1]){
swap(arr,j,j+1);
flag=true;//true表示有数据交换
}
}
if(!flag) break;//没有数据交换,提前退出
}
}
private static void swap(int[] a,int x,int y){
int temp=a[x];a[x]=a[y];a[y]=temp;
}
private static void print(int[] arr) {
for(int i=0; i<arr.length; i++){ System.out.print(arr[i]);}
System.out.println();
}
}
- アルゴリズム分析
(1) バブリングのプロセスは、隣接するデータの交換操作のみを含み、一定レベルの一時スペースのみを必要とするため、そのスペースの複雑さは O(1) であり、インプレース ソート アルゴリズムです。
(2) バブル ソートは、安定したソート アルゴリズムです。
(3) 最良の時間計算量は O(n)、最悪の時間計算量は O(n*n)、時間計算量は O(n*n) です。
2.4 挿入ソート
- この原則は、
配列のデータを、ソートされたインターバルとソートされていないインターバルの 2 つのインターバルに分割します。最初の並べ替え範囲には、配列の最初の要素である 1 つの要素しかありません。アルゴリズムの核となる考え方は、ソートされていない範囲内の要素を取得し、ソートされた範囲内の適切な位置を見つけて挿入し、ソートされた範囲内のデータが常に適切であるようにすることです。ソートされていない区間の要素が空になるまでこのプロセスを繰り返し、アルゴリズムは終了します。 - アルゴリズムの説明
(1) 最初の要素からソートされていると見なすことができる
(2) 次の要素を取り出し、ソートされた要素シーケンスの後ろから前にスキャンする
(3) 要素 (ソートされている場合) ) が新しい要素より大きい場合、要素を次の位置に移動します;
(4) 並べ替えられた要素が新しい要素以下になる位置が見つかるまで、手順 3 を繰り返します; (5) 要素の後に
新しい要素を挿入します位置;
(6) 手順 2 ~ 5 を繰り返します。 - 達成
/**
* 插入排序
* @author mling
*/
public class InsertSortTest {
public static void main(String[] args) {
int[] arr = new int[]{1,3,2,5,3};
insertSort(arr);
print(arr);
}
/**从小到大排序 */
public static void insertSort(int[] arr){
int len = arr.length;
for(int i=1; i<len; i++){
int current = arr[i];
//查找需要插入的位置
int j=i-1;
for(;j>=0;j--){
if(arr[j] > current){
arr[j+1]=arr[j];
}else{
break;
}
}
arr[j+1] = current;
}
}
private static void print(int[] arr) {
for(int i=0; i<arr.length; i++){
System.out.print(arr[i]);
}
System.out.println();
}
}
- アルゴリズム分析
(1) 挿入アルゴリズムの操作は、追加の記憶域スペースのみを必要とするため、スペースの複雑さは O(1) であり、これはインプレース ソート アルゴリズムです。
(2) 挿入アルゴリズムは、安定したソート アルゴリズムです。
(3) 最良の時間計算量は O(n)、最悪の時間計算量は O(n*n)、平均時間計算量は O(n^2) です。
2.5 選択ソート
- アルゴリズムの原理:
選択ソートの実装の考え方は、挿入ソートと似ており、ソートされた区間とソートされていない区間にも分けられます。ただし、選択ソートは、ソートされていない間隔から最小 (最大) の要素を毎回選択し、ソートされた間隔の最後を格納します。すべての要素がソートされるまでこれを繰り返します。 - アルゴリズムの説明:
(1) 初期状態: 順序付けされていない領域は R[1...n]、順序付けられた領域は空 (
2) i 番目の並べ替え (i=1,2,3...n -1) 開始、現在の順序領域と不規則領域は、それぞれ R[1...i-1] と R(i...n) です。このソート プロセスでは、現在の順序付けされていない領域から最小のキーを持つレコード R[k] が選択され、それが順序付けされていない領域の最初のレコード R と交換されるため、R[1...i] と R[i+1. .. n) それぞれ、レコード数の増加に伴う新しい順序付け領域と、レコード数の減少に伴う新しい不規則化領域になる; ( 3)
n-1 パスが終了し、配列が順序付けられます。 - アルゴリズムの実装:
/**
* 插入排序
* @author mling
*/
public class SelectSortTest {
public static void main(String[] args) {
int[] arr = new int[]{1,3,2,5,3};
selectionSort(arr);
print(arr);
}
/**选择排序:从小到大*/
public static void selectionSort(int[] arr){
int len = arr.length;
int minIndex;
for(int i=0;i<len;i++){
minIndex=i;
//寻找未排序部分的最小值的索引
for(int j=i+1;j<len;j++){
if(arr[j]<arr[minIndex]){
minIndex=j;
}
}
swap(arr,i,minIndex);
}
}
private static void swap(int[] a,int x,int y){
int temp=a[x];a[x]=a[y];a[y]=temp;
}
private static void print(int[] arr) {
for(int i=0; i<arr.length; i++){
System.out.print(arr[i]);
}
System.out.println();
}
}
- アルゴリズム分析:
(1) 選択ソートの空間複雑度は O(1) であり、これはインプレース ソート アルゴリズムです。
(2) 選択ソートは、安定したソートアルゴリズムではありません。選択ソートは、毎回未ソート区間から最小値を選択し、前の要素と位置を交換する必要があるため、安定性が失われます。
(3) 選択ソートの最良、最悪、および平均時間の複雑さは O(n^2) です。
2.6 ヒルソート:
- アルゴリズムの原理:
ヒル ソートは単純挿入ソートの改良版です。彼と挿入ソートの違いは、より遠い要素を優先することです。ヒル ソートは、縮小インクリメンタル ソートとも呼ばれます。ヒルソーティングの核心は、間隔シーケンスの設定 (つまり、インクリメント) にあります。間隔シーケンスは、事前に設定することも、動的に定義することもできます。 - アルゴリズムの説明:
最初に、ソート対象のレコード シーケンス全体を直接挿入ソート用のいくつかのサブシーケンスに分割します。具体的なアルゴリズムの説明: (1)
増分シーケンス t1、t2、...、tk を選択します。ここで、ti>tj、i <j , tk=1;
(2) 増分シーケンス番号 k に従って、シーケンスを k 回ソートします;
(3) ソートごとに、対応する増分 ti に従って、ソートされるシーケンスをいくつかの長さ m に分割します(m= 長さ/ti)、各サブテーブルで直接挿入ソートを実行します。増分係数が 1 の場合のみ、シーケンス全体がテーブルとして扱われ、テーブルの長さはシーケンス全体の長さになります。 - アルゴリズムの実装:
/**
* 希尔排序
* @author mling
*/
public class ShellSortTest {
public static void main(String[] args) {
int[] arr = new int[]{1,3,2,5,3};
shellSort(arr);
print(arr);
}
/**希尔排序:从小到大*/
public static void shellSort(int[] arr){
int len = arr.length;
int gap=1;//希尔排序的关键在于设置增量,这里我们动态设置一个增量
while(gap<len/3){
gap=gap*3+1;
}
int temp;
for(;gap>0;gap=(int) Math.floor(gap/3)){
for(int i=gap;i<len;i++){
temp = arr[i];
int j=i-gap;
for(;j>=0 && arr[j]>temp;j-=gap){
arr[j+gap]=arr[j];
}
arr[j+gap]=temp;
}
}
}
private static void print(int[] arr) {
for(int i=0; i<arr.length; i++){
System.out.print(arr[i]);
}
System.out.println();
}
}
- アルゴリズム分析:
(1) ヒル ソートの空間複雑度は O(1) であり、これはインプレース ソート アルゴリズムです。
(2) ヒル ソートは、不安定なソート アルゴリズムではありません。複数の挿入ソート、1 つの挿入ソートは安定しており、同じ要素の相対的な順序を変更しないことがわかっていますが、異なる挿入ソート プロセスでは、同じ要素がそれぞれの挿入ソートで移動する可能性があり、最終的にその安定性が破壊されますシャッフルであるため、ヒルソートは不安定です。
(3) 選択ソートの最良の時間複雑度は O(n)、最悪の時間複雑度は O(n*n)、平均時間複雑度は O(n^1.3) です。
参考リンク:ソートアルゴリズム