【アルゴリズム学習日記01】ランキングトップ10

目次

参考:トップ 10 の古典的な並べ替えアルゴリズム
PS: この記事のすべてのコードは 912 の質問を通じて検証されています (いくつかの並べ替えアルゴリズムはタイムアウトになります): 912. 配列の並べ替え

1 カテゴリー

カテゴリを並べ替える

2 アルゴリズムの複雑さ

選別方法 時間の複雑さ 空間の複雑さ 安定性
バブルソート O(n^2) ○(1) 安定させる
選択ソート O(n^2) ○(1) 不安定な
挿入ソート O(n^2) ○(1) 安定させる
ヒルソート O(n^1.3) ○(1) 不安定な
クイックソート O(nlogn) O(ログン~ン) 不安定な
マージソート O(nlogn) の上) 安定させる
ヒープソート O(nlogn) ○(1) 不安定な
カウントソート O(n+k) O(n+k) 安定させる
バケットソート O(n+k) O(n+k) 安定させる
基数ソート O(n*k) O(n+k) 安定させる

3 シンプルなアルゴリズム

3.1 バブルソート

3.1.1 アルゴリズムの説明

バブル ソートは、ソート対象の配列を繰り返し訪問し、一度に 2 つの要素を比較し、順序が要件を満たしていない場合は要素を交換する単純なソート アルゴリズムです。

具体的には、昇順を例に挙げると、バブル ソート アルゴリズムは次のように動作します。

1. 隣接する要素を比較し、最初の要素が 2 番目の要素より大きい場合は、それらの 2 つを交換します。
2. 隣接する要素の各ペアに対して、最初のペアから始めて最後のペアで終わるまで、同じことを行います。このステップが完了すると、最後の要素が最大数になります。
3. 比較する数値のペアがなくなるまで、最後の要素を除くすべての要素に対して上記の手順を繰り返します。

3.1.2 GIF のデモ

バブルソート

3.1.3 コードの実装

void BubbleSort(int[] nums){
    
    
	for(int i=0;i<nums.Length;i++){
    
    
		bool isOver = true; //剪枝:如果不存在一对数顺序不符合要求,则排序完成
        for(int j=0;j<nums.Length-i-1;j++){
    
    
			if(nums[j]>nums[j+1]){
    
    
            	Swap(nums,j,j+1);
	            isOver = false;
            }
        }
        if(isOver) break;
    }
}

3.1.4 パフォーマンス分析

時間の複雑さ 空間の複雑さ 安定性
O ( n 2 ) O(n^2)O ( n2 ) O ( 1 ) O (1) 安定させる

3.2 単純な選択の並べ替え

3.2.1 アルゴリズムの説明

単純選択ソートは、毎回ソート対象のデータ要素から最小/最大の要素を選択し、シーケンスの開始位置に配置する、シンプルで直感的なソート アルゴリズムです。次に、ソートされていない残りの要素に対して同じ手順を実行します。

具体的には、昇順を例に挙げると、単純な選択ソート アルゴリズムは次のように動作します。

1. 最初に、ソートされるシーケンスは nums[0…n-1] です。
2. ソートするシーケンスから最小の要素を選択し、それをソートするシーケンスの開始ビットと交換します。
3. 開始ビットを除いて、残りの要素はソートされる新しいシーケンスを形成します。ソートされるシーケンスが空になるまでステップ 2 を繰り返します (実際には、数字が 1 つだけ残ったときに停止できます)。

3.2.2 GIF のデモ

選択ソート

3.2.3 コードの実装

void SelectSort(int[] nums){
    
    
	for(int i=0;i<nums.Length-1;i++){
    
    
		int min = i;
		for(int j=i+1;j<nums.Length;j++){
    
    
			if(nums[j]<nums[min]) min = j;
        }
        if(min!=i) Swap(nums,min,i);
    }
}

3.2.4 パフォーマンス分析

時間の複雑さ 空間の複雑さ 安定性
O ( n 2 ) O(n^2)O ( n2 ) O ( 1 ) O (1) 不安定な

3.3 直接挿入ソート

3.3.1 アルゴリズムの説明

直接挿入ソートは、シンプルで直観的なソート アルゴリズムです。その動作原理は、順序付けされたシーケンスを構築することです。ソートされていないデータの場合は、ソートされたシーケンスの後ろから前にスキャンして、対応する位置を見つけて挿入します。トランプのようなものと想像できます。 . カード管理プロセス。

具体的には、昇順を例に挙げると、直接挿入ソート アルゴリズムは次のように動作します。

1. 最初の要素を最初の順序付けされたシーケンスとして扱います。
2. 順序付けされたシーケンスの最後の要素の次のビットから開始して、後ろから前にスキャンします。
3. 現在の要素 (並べ替えられたシーケンス内の要素) が新しい要素より大きい場合、要素を 1 つ前の位置に移動します。
4. 現在の要素が新しい要素以下になるまで手順 3 を繰り返します。
5. 要素の後に新しい要素を挿入します。
6. 手順2~5を繰り返します。

3.3.2 GIF のデモ

挿入ソート

3.3.3 コードの実装

void InsertSort(int[] nums){
    
    
	for(int i=1;i<nums.Length;i++){
    
    
    	int temp = nums[i];
        int j;
        for(j=i-1;j>=0;j--){
    
    
        	if(nums[j]>temp) nums[j+1] = nums[j];
            else break;
        }
        nums[j+1] = temp;
    }
}

3.3.4 パフォーマンス分析

時間の複雑さ 空間の複雑さ 安定性
O ( n 2 ) O(n^2)O ( n2 ) O ( 1 ) O (1) 安定させる

4 アルゴリズムの改善

4.1 ヒルソーティング

4.1.1 アルゴリズムの説明

ヒル ソートは、挿入ソートのより効率的かつ改良されたバージョンです。このメソッドは、1959 年に提案された DLShell にちなんで名付けられました。ヒル ソートでは、添字の特定の増分でレコードをグループ化し、直接挿入ソート アルゴリズムを使用して各グループをソートします。増分が徐々に減少するにつれて、各グループにはますます多くのキーワードが含まれます。増分が 1 まで減少すると、シーケンス全体が次のように分割されます。 1 つのグループに対して直接挿入ソートがすべてのレコードに対して実行されます。

ヒル ソートの時間計算量は増分シーケンスの選択に関連しており、平均時間計算量は O(n^1.3) です。

4.1.2 GIF のデモ

ヒルソート

4.1.3 コードの実装

void ShellSort(int[] nums){
    
    
	int gap = 1;
	//PS:关于为什么是gap*3+1而不是gap*3,据说是因为这样比较符合二进制计算机的特点,能够减少乘法运算的次数,从而提高效率;也有说法是说如果不这样做,算法复杂度可能会降到O(n^2)
    while(gap<nums.Length) gap = gap*3+1;
    while(gap>0){
    
    
    	//直接插入排序
        for(int i=gap;i<nums.Length;i++){
    
    
        	int temp = nums[i];
            int j;
            for(j=i-gap;j>=0;j-=gap){
    
    
            	if(nums[j]>temp) nums[j+gap] = nums[j];
                else break;
            }
            nums[j+gap] = temp;
        }
        gap/=3;
    }
}

4.1.4 パフォーマンス分析

時間の複雑さ 空間の複雑さ 安定性
O ( N 1.3 ) O(N^{1.3})O ( N1.3 ) O ( 1 ) O (1) 不安定な

4.2 クイックソート

4.2.1 アルゴリズムの説明

クイックソートは分割統治の考え方に基づいたソートアルゴリズムで、ソート対象の配列を連続的に2つの部分配列に分割し、それぞれを独立してソートします。

具体的には、昇順を例にすると、クイック ソートは次のように動作します。

1. ピボット要素を選択します。配列から要素をピボット要素として選択します。
2. 分割操作: 基本要素より小さいすべての要素をその前に配置し、基本要素より大きいすべての要素をその後ろに配置します。
3. 左右の部分配列を再帰的にすばやく並べ替えます。

4.2.2 GIF のデモンストレーション

クイックソート

4.2.3 基準値の選択

クイックソートの基本値を選択するには、次の 3 つの方法があります。

1. エンドポイントはベース値として機能します。
2. 基本値としてランダムな値。
3. 3 つの数字の中央を見つけます。

その中でも、ベンチマーク値の選択方法として最もよく使われるのが 3 ナンバー法です。このメソッドは、まず、並べ替える配列の左、中、右の位置にあるデータから中央の桁を基準値として選択します。これにより、配列がすでにソートされているか、ソートされそうになっているときに、クイック ソートがバブル ソートに変質するのを防ぐことができます。

4.2.4 コードの実装

基本値として 1 つのエンドポイント
void QuickSort(int[] nums,int left,int right){
    
    
    if(left>=right) return;
    int mid = PartSort(nums,left,right);
    QuickSort(nums,left,mid-1);
    QuickSort(nums,mid+1,right);
}
int PartSort(int[] nums,int left,int right){
    
    
	//最简单的基准值选择:选择待排序数组的第一位元素。
    int pivot = nums[left];
    while(left<right){
    
    
        while(left<right&&nums[right]>=pivot) right--;
        nums[left] = nums[right];
        while(left<right&&nums[left]<=pivot) left++;
        nums[right] = nums[left];
    }
    nums[left] = pivot;
    return left;
}
基本値として2つのランダムな値
//C# Random实例.Next左闭右开
Random random = new Random();
int index = random.Next(left,right+1);
Swap(nums,index,left);
//Unity Random.Range左闭右开
int index = Random.Range(left,right+1);
Swap(nums,index,left);
3 3つの数字の中点を求める方法
//在int pivot = nums[left]前加上以下代码,使数值居中的值位于left位。
int mid = (left+right)/2;
if(nums[left]>nums[right]) Swap(nums,left,right);
if(nums[mid]>nums[right]) Swap(nums,mid,right);
if(nums[left]<nums[mid]) Swap(nums,left,mid);

4.2.5 最適化

最適化 1: シーケンス長が一定サイズに達した場合、挿入ソートを使用します。

クイックソートが一定の深さに達すると、分割間隔が非常に小さくなるため、この時点でクイックソートを使用するのは効率的ではありませんが、この時点で挿入ソートを使用すると、いくつかの有害な劣化状況を回避できます。

void QuickSort(int left,int right){
    
    
	if(left>=right) return;
	int len = right-left+1;
	if(len<10) InsertSort(left,right);
	else{
    
    
		int mid = PartSort(left,right);
		QuickSort(left,mid-1);
		QuickSort(mid+1,right);
	}
}

最適化 2: 末尾再帰の最適化

クイックソート アルゴリズムには、ほとんどの分割統治型ソート アルゴリズムと同様に、2 つの再帰呼び出しがあります。ただし、クイック ソートはマージ ソートとは異なります。マージの再帰は関数の先頭にありますが、クイック ソートの再帰は関数の最後にあります。これにより、クイック ソート コードで末尾再帰最適化を実装できます。末尾再帰最適化を使用した後、スタックの深さを元の O(n) から O(logn) に減らすことができます。

末尾再帰
関数内のすべての再帰呼び出しが関数の最後で発生し、その再帰呼び出しが関数本体全体で実行される最後のステートメントであり、その戻り値が式の一部ではない場合、再帰呼び出しは末尾再帰となります。

コンパイラは、関数呼び出しが末尾再帰的であることを検出すると、スタック上に新しいレコードを作成するのではなく、現在のアクティブなレコードを上書きします。コンパイラがこれを実行できるのは、再帰呼び出しが現在のアクティブ期間で実行される最後のステートメントであるためです。そのため、呼び出しが戻ったときにスタック フレーム内で他に行うことは何もないため、スタック フレームを保存する必要がありません。現在のスタック フレームに新しいスタック フレームを追加するのではなく上書きすることにより、使用されるスタック スペースが大幅に削減され、実際の操作効率が向上します。

一例

//线性递归
int fact(int n){
    
    
	if(n<0) return 0;
	else if(n==0||n==1) return 1;
	else n*fact(n-1);
}

n=5 の場合、線形再帰の再帰プロセスは次のようになります。

fact(5)
{
    
    5*fact(4)}
{
    
    5*{
    
    4*fact(3)}}
{
    
    5*{
    
    4*{
    
    3*fact(2)}}}
{
    
    5*{
    
    4*{
    
    3*{
    
    2*fact(1)}}}}
{
    
    5*{
    
    4*{
    
    3*{
    
    2*1}}}}
{
    
    5*{
    
    4*{
    
    3*2}}}
{
    
    5*{
    
    4*6}}
{
    
    5*24}
120
//尾递归
int fact(int n,int a){
    
    
	if(n<0) return 0;
	else if(n==0) return 1;
	else if(n==1) return a;
	else return fact(n-1,a*n);
}

n=5 の場合、末尾再帰の再帰プロセスは次のようになります。

facttail(5,1)
facttail(4,5)
facttail(3,20)
facttail(2,60)
facttail(1,120)
120

末尾再帰によってスタックの深さを大幅に削減できることがわかります。

クイック ソートの最適化
最初の再帰の後、変数 left は役に立ちません。これは、2 番目の再帰を反復制御構造に置き換えることができることを意味します。

void QuickSort(int left,int right){
    
    
	if(left>=right) return;
	int len = right-left+1;
	if(len<10) InsertSort(left,right);
	else{
    
    
		while(left<right){
    
    
			int mid = PartSort(left,right);
			QuickSort(left,mid-1);
			left = mid+1;
		}
	}
}

4.2.6 パフォーマンス分析

時間の複雑さ 空間の複雑さ 安定性
O ( nlogn ) O(nlogn)O ( nログn ) _ _ O ( logn − n ) O(logn-n)O (ログオン_ _ _n ) 不安定な

4.3 マージソート

4.3.1 アルゴリズムの説明

マージソートは分割統治の考え方に基づいたソートアルゴリズムで、大きな配列をいくつかの小さな配列に分割し、少しずつマージしていきます。マージ ソートの基本的な考え方は、ソート対象のシーケンスをいくつかのサブシーケンスに分割し、各サブシーケンスを順序付けし、それらのサブシーケンスを全体として順序付けられたシーケンスにマージすることです。

具体的には、昇順を例に挙げると、マージ ソート アルゴリズムは次のように動作します。

1. ソートするシーケンスをいくつかのサブシーケンスに分割します。
2. 隣接するサブシーケンスをマージして、長さ 2 の順序付けされたシーケンスをいくつか取得します。
3. ステップ 2 を繰り返します。

4.3.2 クイックソートとマージソートの違い

クイックソートとマージソートはどちらも分割統治の考え方を採用しており、アルゴリズムの時間計算量はO(nlogn)ですが、具体的な実装方法が異なります。

クイック ソートは不安定なソート アルゴリズムです。その基本的な考え方は、1 回のソートでソート対象の列を 2 つの独立した部分に分割することです。一方の部分に記録されているキーワードが他方の部分のキーワードよりも小さいので、これを押します。メソッドは続行されます。レコードのこれら 2 つの部分を並べ替えて、シーケンス全体を順序付けるという目的を達成します。また、クイック ソートは、配列内の要素を交換することでソートを実装するインプレース ソート アルゴリズムです。クイック ソートの空間複雑さは、再帰スタックの深さに依存します。一般的に、空間複雑さは O(logn)~O( n)。

マージ ソートは安定した並べ替えアルゴリズムですが、一時配列を保存するために追加のスペースが必要です。マージ ソートの基本的な考え方は、ソート対象のシーケンスをいくつかのサブシーケンスに分割し、各サブシーケンスを順序付けし、それらのサブシーケンスを全体として順序付けられたシーケンスにマージすることです。マージ ソートの空間計算量は O(n) です。

4.3.3 GIF のデモンストレーション

マージソート

4.3.4 コードの実装

void MergeSort(int[] nums,int left,int right){
    
    
    if(left>=right) return;
    int mid = (left+right)/2;
    MergeSort(nums,left,mid);
    MergeSort(nums,mid+1,right);
    Merge(nums,left,right);
}
void Merge(int[] nums,int left,int right){
    
    
    int mid = (left+right)/2;
    int i = left;
    int j = mid+1;
    int[] temp = new int[right-left+1];
    int k = 0;
    while(i<=mid&&j<=right){
    
    
        if(nums[i]<=nums[j]) temp[k++] = nums[i++];
        else temp[k++] = nums[j++];
    }
    while(i<=mid) temp[k++] = nums[i++];
    while(j<=right) temp[k++] = nums[j++];
    for(int p=0;p<temp.Length;p++) nums[left+p] = temp[p];
}

4.3.5 パフォーマンス分析

時間の複雑さ 空間の複雑さ 安定性
O ( nlogn ) O(nlogn)O ( nログn ) _ _ O ( n ) O( n )O ( n ) 安定させる

4.4 ヒープソート

4.4.1 アルゴリズムの説明

ヒープ ソートは、ヒープを維持することによってソートを実装するインプレース ソート アルゴリズムです。ヒープは、次の 2 つの条件を満たす特別なツリー データ構造です。

1. ヒープ内のノードの値は、常にその親ノードの値以上でも以下でもありません。
2. ヒープは常に完全なバイナリ ツリーです。

ヒープ ソートの基本的な考え方は、ソート対象のシーケンスを大きなルート ヒープまたは小さなルート ヒープに構築し、シーケンス全体がソートされるまでヒープの先頭要素を順番に削除することです。

具体的には、昇順を例に挙げると、ヒープ ソート アルゴリズムは次のように動作します。

1. ヒープの構築: 並べ替えるキーワードの最初のシーケンス (R1、R2….Rn) を大きな上部ヒープに構築します。このとき (R1、R2….Rn) は、初期の順序付けされていない領域です。
2. 交換: 最上位要素 R[1] を最後の要素 R[n] と交換し、新しい非順序領域 (R1、R2、...Rn-1) と新しい順序領域 (Rn) を取得します。満足 R[1,2…n-1]<=R[n];
3. ヒープの調整: 交換後の新しいヒープ先頭 R[1] はヒープのプロパティに違反する可能性があるため、ヒープを調整する必要があります。現在の順序なし領域 (R1、R2、...Rn-1) が新しいヒープに合わせて調整され、次に R[1] が順序なし領域の最後の要素と再度交換されて、新しい順序なし領域 (R1、R2. ..Rn-2) と新しい Ordered 領域 (Rn-1、Rn)。この処理を順序付き領域の要素数が n-1 個になるまで繰り返し、ソート処理全体が完了します。

4.4.2 GIF のデモ

ここに画像の説明を挿入します

4.4.3 コードの実装

知識ポイント:

父:i -> 子:2i+1/2i+2
子:i ->:(i-1)/2
void HeapSort(int[] nums){
    
    
    CreateHeap(nums);
    for(int i=nums.Length-1;i>0;i--){
    
    
        Swap(nums,0,i);
        AdjustHeap(nums,0,i-1);
    }
}
void CreateHeap(int[] nums){
    
    
    int last = nums.Length-1;
    for(int i=(last-1)/2;i>=0;i--){
    
    
        AdjustHeap(nums,i,last);
    }
}
void AdjustHeap(int[] nums,int left,int right){
    
    
    int root = left;
    int child = root*2+1;
    while(child<=right){
    
    
        if(child+1<=right&&nums[child]<nums[child+1]) child++;
        if(nums[root]>=nums[child]) return;
        else{
    
    
            Swap(nums,root,child);
            root = child;
            child = root*2+1;
        } 
    }
}

4.4.4 パフォーマンス分析

時間の複雑さ 空間の複雑さ 安定性
O ( nlogn ) O(nlogn)O ( nログn ) _ _ O ( 1 ) O (1) 不安定な

その他の 5 つのアルゴリズム

5.1 カウントソート

5.1.1 アルゴリズムの説明

カウント ソートの中心は、入力データを配列添字に変換し、追加の開いた配列空間に格納することであり、入力データが特定の範囲の正の整数である必要があります。

具体的には、昇順を例に挙げると、カウントソートアルゴリズムは次のように動作します。

1. 順序なしシーケンスから最大値 max と最小値 min を見つけ、追加の配列スペースのサイズを決定します: int[] temp = new int[max-min+1]。
2. 元の配列を走査し、配列内の各値の出現回数を数え、それを配列 newArr に記録します。
3. 統計配列を改善します。統計配列の各項目は、newArr から現在の項目までのカウントの合計です。
4. パディングを反転します。

5.1.2 GIF のデモンストレーション

カウントソート

5.1.3 コードの実装

void CountSort(int[] nums){
    
    
	//1.找最大最小值以确定额外开辟的数组空间的大小
	int min = nums[0];
	int max = nums[0];
	for(int i=1;i<nums.Length;i++){
    
    
		if(nums[i]<min) min = nums[i];
		if(nums[i]>max) max = nums[i];
	}
	int[] newArr = new int[max-min+1];
	for(int i=0;i<nums.Length;i++){
    
    
		newArr[nums[i]-min]++;
	}
	//2.统计数组-为保证排序稳定
	int[] countArr = new int[newArr.Length];
	for(int i=0;i<newArr.Length;i++){
    
    
		if(i==0) countArr[i] = newArr[i];
		else countArr[i] = newArr[i]+countArr[i-1];
	}
	//3.最终结果
	int[] result = new int[nums.Length];
	for(int i=nums.Length-1;i>=0;i--){
    
    
		result[countArr[nums[i]-min]-1] = nums[i];
		countArr[nums[i]-min]--;
	}
}

5.1.4 パフォーマンス分析

時間の複雑さ 空間の複雑さ 安定性
O ( n + k ) O(n+k)O ( n+k ) O ( n + k ) O(n+k)O ( n+k ) 安定させる

5.2 バケットのソート

5.2.1 アルゴリズムの説明

バケット ソートは、カウンティング ソートのアップグレード バージョンです。関数の写像関係を利用するもので、この写像関数の決定が効率の鍵となります。

バケット ソートの動作原理: 入力データが一様分布に従っていることを前提とし、データを限られた数のバケットに分割し、各バケットを個別にソートします (他のソート アルゴリズムを使用したり、再帰的に使用し続けることも可能です)。並べ替え)。

具体的には、昇順を例に挙げると、バケット ソート アルゴリズムは次のように動作します。

1. まず全データの最大値maxと最小値minを求めます。
2. バケットのサイズを決定します。最大値と最小値に基づいて各バケットに含まれるデータの範囲を決定します。サイズ = (最大値-最小値)/n+1、n はデータの数です。少なくとも 1 つのバケットがあるため、Add 1 が必要です。
3. バケットの数を決定します: サイズを見つけたら、各バケットに含まれるデータの範囲がわかります。また、必要なバケット数 cnt (cnt = (最大-最小)/サイズ + 1) を計算する必要もあります。各バケットが少なくとも 1 つの数値を保持できることを確認する必要があるため、1 を追加する必要があります。
4. サイズと cnt を取得した後、最初のバケットのデータ範囲は [min, min+size)、2 番目のバケットは [min+size, min+2*size),...] であることがわかります。 。
5. 各バケット内のデータを並べ替えるには、バケットの再帰的並べ替えを使用するか、他の並べ替え方法を使用できます。
6. 各バケット内の順序付けされたシーケンスを順番に出力します。

5.2.2 コードの実装

void BucketSort(int[] nums){
    
    
    //1.找最小值和最大值以计算size和cnt
    int min = nums[0];
    int max = nums[0];
    for(int i=1;i<nums.Length;i++){
    
    
        if(nums[i]<min) min = nums[i];
        if(nums[i]>max) max = nums[i];
    }
    int n = nums.Length;
    int size = (max-min)/n+1;
    int cnt = (max-min)/size+1;
    List<int>[] bucket = new List<int>[cnt];
    for(int i=0;i<cnt;i++){
    
    
        bucket[i] = new List<int>();
    }
    //2.扫描数组,将元素放进对应的桶里
    for(int i=0;i<nums.Length;i++){
    
    
        int index = (nums[i]-min)/size;
        bucket[index].Add(nums[i]);
    }
    //3.对各个桶进行排序
    for(int i=0;i<cnt;i++){
    
    
        bucket[i].Sort();
    }
    //4.反向填充
    int m = 0;
    for(int i=0;i<cnt;i++){
    
    
        for(int j=0;j<bucket[i].Count;j++){
    
    
            nums[m++] = bucket[i][j];
        }
    }
}

5.2.3 パフォーマンス分析

時間の複雑さ 空間の複雑さ 安定性
O ( n + k ) O(n+k)O ( n+k ) O ( n + k ) O(n+k)O ( n+k ) 安定させる

5.3 基数ソート

5.3.1 アルゴリズムの説明

基数ソートは非比較整数ソート アルゴリズムであり、その原理は、整数を桁数に応じて異なる数値に分割し、各桁を個別に比較することです。基数ソート方法には、LSD (最下位デジタル) または MSD (最上位デジタル) を使用できます。LSD のソート方法はキー値の右端から開始され、MSD のソート方法はキー値の左端から開始されます。

具体的には、昇順を例として挙げると、基数ソート アルゴリズムは次のように動作します。

1. 配列内の最大数を見つけて、桁数を決定します。
2. 最下位ビットから順に、各ビットが基数配列を形成します。
3. 基数配列をカウントでソートします。

5.3.2 GIF のデモンストレーション

基数ソート

5.3.3 コードの実装

void RadixSort(int[] nums) {
    
    
	//1.准备桶
    int[,] bucket = new int[10, nums.Length];
    int[] bucketCount = new int[10];
    //2.获取最大数的位数
    int max = nums[0];
    for(int i = 1; i < nums.Length; i++) {
    
    
    	if (nums[i] > max) max = nums[i]; 
    }
    int len = (max + "").Length;
    //3.桶排序
    int arrCount;  //原始数组索引
    for(int k = 0, n = 1; k < len; k++, n *= 10) {
    
    
    	//3.1 第k轮桶排序
        for(int i = 0; i < nums.Length; i++) {
    
    
        	int digit = nums[i] / n % 10;  //获取个十百千万数
            bucket[digit,bucketCount[digit]] = nums[i];
            bucketCount[digit]++;
        }
        //3.2 将桶中数据取出来给原数组
        arrCount = 0;
        for(int i = 0; i < 10; i++) {
    
    
        	if (bucketCount[i] != 0) {
    
    
            	for(int j = 0; j < bucketCount[i]; j++) {
    
    
                	nums[arrCount++] = bucket[i, j];
                }
                }
            }
        //3.3 清空桶
        bucketCount = new int[10];
   }
}

5.3.4 パフォーマンス分析

時間の複雑さ 空間の複雑さ 安定性
O ( n ∗ k ) O(n*k)O ( nk ) O ( n + k ) O(n+k)O ( n+k ) 安定させる

6 リストのソート方法

6.1 ソート方法

list.Sort();      //升序方法
list.Reverse();   //降序方法
list.OrderBy(x=>x.MyProperty); //OrderBy接受一个Lambda表达式指定要按其排序的键

6.2 ソートメソッドを書き換える

6.2.1 x.CompareTo(y)

int result = a.CompareTo(b);

a=b の場合、結果 = 0、a>b の場合、結果 = 1、a<b の場合、結果 =-1。

6.2.2 書き換え

1 カスタムコンパレータ
class CustomComparer : IComparer<MyClass>{
    
    
	public int Compare(MyClass x,MyClass y){
    
    
		return x.MyProperty.CompareTo(y.MyProperty);
	}
}

list.Sort(new CustomComparer());
2 ラムダ
list.Sort((x,y)=>x.MyProperty.CompareTo(y.MyProperty));
3 匿名の代表団
list.Sort(delegate(MyClass x,MyClass y){
    
    
	if(x.MyProperty>y.MyProperty) return 1;
	else return -1;
});

7つの演習

912 ソートされた配列

少しずつ手で仕分けする練習に使えます。

56 マージ間隔

public class Solution {
    
    
    public int[][] Merge(int[][] intervals) {
    
    
        //1.依据左端点进行排序
        Array.Sort(intervals,(x,y)=>x[0].CompareTo(y[0]));
        //2.合并重叠区间
        List<int[]> list = new List<int[]>();
        list.Add(intervals[0]);
        for(int i=1;i<intervals.Length;i++){
    
    
            int[] temp = list[list.Count-1];
            if(temp[1]>=intervals[i][0]){
    
    
                temp[1] = Math.Max(temp[1],intervals[i][1]);
            }else{
    
    
                list.Add(intervals[i]);
            }
        }
        return list.ToArray();
    }
}

148 ソートされたリンクリスト

1 リンクリストの中点を見つける - 高速ポインタと低速ポインタ

ListNode FindMidNode(ListNode head){
    
    
	if(head==null||head.next==null) return head;
    ListNode fast = head;
    ListNode slow = head;
    while(fast.next!=null){
    
    
    	if(fast.next.next==null) return slow;
        else fast = fast.next.next;
        slow = slow.next;
    }
    return slow;
}

2 2 つの順序付きリンク リストを結合する

ListNode Merge(ListNode list1,ListNode list2){
    
    
	if(list1==null) return list2;
    if(list2==null) return list1;
    if(list1.val>list2.val) return MergeTwoLists(list2,list1);
    list1.next = MergeTwoLists(list1.next,list2);
    return list1;
}

3 まとめ

public class Solution {
    
    
    public ListNode SortList(ListNode head) {
    
    
        if(head==null||head.next==null) return head;
        ListNode mid = MiddleNode(head);
        ListNode temp = mid.next;
        mid.next = null;
        ListNode list1 = SortList(head);
        ListNode list2 = SortList(temp);
        return MergeTwoLists(list1,list2);
    }
    //辅助方法1:找到链表的中间结点(当中间结点有两个时,返回前面的那个)
    private ListNode MiddleNode(ListNode head){
    
    
        if(head==null||head.next==null) return head;
        ListNode fast = head;
        ListNode slow = head;
        while(fast.next!=null){
    
    
            if(fast.next.next==null) return slow;
            else fast = fast.next.next;
            slow = slow.next;
        }
        return slow;
    }
    //辅助方法2:合并两个有序链表
    private ListNode MergeTwoLists(ListNode list1,ListNode list2){
    
    
        if(list1==null) return list2;
        if(list2==null) return list1;
        if(list1.val>list2.val) return MergeTwoLists(list2,list1);
        list1.next = MergeTwoLists(list1.next,list2);
        return list1;
    }
}

おすすめ

転載: blog.csdn.net/manpi/article/details/129856032