記事ディレクトリ
序文
ソートアルゴリズムは、「データ構造とアルゴリズム」の最も基本的なアルゴリズムの 1 つです。
並べ替えアルゴリズムは、内部並べ替えと外部並べ替えに分けることができます。内部並べ替えはメモリ内のデータ レコードを並べ替えるのに対し、外部並べ替えは、並べ替えられたデータが非常に大きく、並べ替えられたすべてのレコードを一度に収容できないためです。並べ替えプロセス中に、外部メモリにアクセスする必要があります。一般的なソート アルゴリズムには、挿入ソート、ヒル ソート、選択ソート、バブル ソート、マージ ソート、クイック ソート、ヒープ ソート、テクニカル ソートなどがあります。
1. 上位 10 個の一般的な並べ替えアルゴリズムの概要
一般的なソート アルゴリズムの平均時間計算量と最良の場合の時間計算量は次のとおりです: バブル ソート、選択ソート、挿入ソート、ヒル ソート、マージ ソート、クイック ソート、ヒープ ソート、カウンティング ソート、バケット ソート、および基数ソート。最悪の場合の時間計算量、空間計算量、ソート方法、安定性の概要。
1. 用語の説明
- n: データサイズ
- k: 「バケット」の数
- インプレース: 定数メモリを占有し、追加のメモリを占有しません。
- アウトプレイス: 余分なメモリを消費します
- 安定性: ソート後の 2 つの等しいキー値の順序は、ソート前の順序と同じです。
安定したソート アルゴリズムには、バブル ソート、挿入ソート、マージ ソート、基数ソートが含まれます。
不安定なソート アルゴリズムには、選択ソート、クイック ソート、ヒル ソート、およびヒープ ソートが含まれます。
下の画像をクリックすると拡大表示されます:
2. 時間計算量
- 平方オーダー O(n2): 各種単純ソート、直接挿入、直接選択、バブルソート。
- 線形対数順序 O(nlog2n): クイック ソート、マージ ソート、ヒープ ソート。
- O(n1+ζ): ζ は 0 と 1 の間の定数、ヒル ソート。
- 線形順序 O(n): 基数ソート、バケット ソート、ビン ソート。
2. ソートアルゴリズムとC言語実装
1. バブルソート
バブル ソートは、シンプルで直感的なソート アルゴリズムです。相互に比較し、大きい方が常に最後にランク付けされ、最大の番号が最後にランク付けされるまで次の番号との比較を続け、その後、すべての番号がランク付けされるまで、最後の番号の前のシーケンスでプロセスを繰り返し続けます。良い。
-
アルゴリズムのステップ
: 隣接する要素を比較し、最初の要素が 2 番目の要素よりも大きい場合は、交換して大きい方の要素を後ろにランク付けします。
隣接する要素の各ペアに対して同じ比較が行われ、最後の要素が最大の数値になります。
最初の数値が最小の数値になるまで、すべての要素に対して同じことを行います。 -
アニメーションデモンストレーション
-
最も速いのはいつですか?
入力データがすでに整っているときです。 -
最も遅いのはいつですか?
入力データが逆順の場合、すべてのステップを移動して交換する必要があります。 -
バブルソートを実装するC言語
#include <stdio.h>
void bubble_sort(int arr[], int len) {
int i, j, temp;
for (i = 0; i < len - 1; i++)
for (j = 0; j < len - 1 - i; j++)
if (arr[j] > arr[j + 1]) {
temp = arr[j];
arr[j] = arr[j + 1];
arr[j + 1] = temp;
}
}
int main() {
int arr[] = {
22, 34, 3, 32, 82, 55, 89, 50, 37, 5, 64, 35, 9, 70 };
int len = sizeof(arr) / sizeof(arr[0]);
bubble_sort(arr, len);
int i;
for (i = 0; i < len; i++)
printf("%d ", arr[i]);
return 0;
}
2. 選択ソート
選択ソートはシンプルで直観的なソート アルゴリズムであり、どのようなデータが入力されても、時間計算量は O(n2) です。したがって、使用する場合はデータサイズが小さいほど良いです。
- アルゴリズムのステップ
まず、ソートされていないシーケンス内で最小 (最大) の要素を見つけ、それをソートされたシーケンスの開始位置に格納します。
次に、ソートされていない残りの要素から最小 (最大) の要素を検索し、それをソートされたシーケンスの最後に置きます。
すべての要素が並べ替えられるまで、手順 2 を繰り返します。
-
アニメーションデモンストレーション
-
選択ソートの C 言語実装
void swap(int *a,int *b) //交換兩個變數
{
int temp = *a;
*a = *b;
*b = temp;
}
void selection_sort(int arr[], int len)
{
int i,j;
for (i = 0 ; i < len - 1 ; i++)
{
int min = i;
for (j = i + 1; j < len; j++) //走訪未排序的元素
if (arr[j] < arr[min]) //找到目前最小值
min = j; //紀錄最小值
swap(&arr[min], &arr[i]); //做交換
}
}
3. 挿入ソート
挿入ソートのコード実装はバブル ソートや選択ソートほど単純で粗雑ではありませんが、ポーカーをプレイしたことがある人なら誰でも数秒で理解できるはずなので、その原理は最も理解しやすいはずです。挿入ソートは、最も単純かつ直感的なソート アルゴリズムであり、その動作原理は、順序付けられたシーケンスを構築することによって、ソートされていないデータに対してソートされたシーケンスの後ろから前に向かってスキャンし、対応する位置を見つけて挿入することです。
- アルゴリズムのステップ
並べ替えるシーケンスの最初の要素を順序付きシーケンスとして扱い、2 番目の要素から最後の要素までを並べ替えられていないシーケンスとして扱います。
並べ替えられていないシーケンスを最初から最後までスキャンし、スキャンした各要素を順序付けされたシーケンス内の適切な位置に挿入します。(挿入される要素が順序付けされたシーケンス内の要素と等しい場合、挿入される要素は等しい要素の後に挿入されます)
- アニメーションデモンストレーション
- C言語の実装
void insertion_sort(int arr[], int len){
int i,j,key;
for (i=1;i<len;i++){
key = arr[i];
j=i-1;
while((j>=0) && (arr[j]>key)) {
arr[j+1] = arr[j];
j--;
}
arr[j+1] = key;
}
}
4. ヒルソート
ヒル ソートは、降順増分ソート アルゴリズムとも呼ばれ、挿入ソートのより効率的かつ改良されたバージョンです。ただし、ヒル ソートは不安定なソート アルゴリズムです。
ヒル ソートは、挿入ソートの次の 2 つの特性に基づいて改良された方法です。
- 挿入ソートは、ほぼすでに配置されているデータを操作する場合に非常に効率的です。つまり、線形ソートの効率を達成できます。
- ただし、挿入ソートは一度に 1 ビットしかデータを移動できないため、一般に非効率的です。
ヒル ソートの基本的な考え方は次のとおりです。まず、ソート対象のレコード シーケンス全体をいくつかのシーケンスに分割し、(ステップ サイズ交換に従って) 直接挿入ソートを実行します。シーケンス全体のレコードが「基本的に順序付けされている」場合、その後、すべてのレコードが直接挿入ソートを実行します。
- アルゴリズムのステップ
例として、{8, 9, 1, 7, 2, 3, 5, 6, 4, 0} というシーケンスを考えてみましょう。
1. 初期ステップ サイズ ギャップ = 長さ/2 = 5。これは、配列全体が 5 つのグループ、つまり [8, 3]、[9, 5]、[1, 6]、[7, 4]) に分割されることを意味します。 , [2 , 0]、各グループに対して挿入ソートを実行し、シーケンス {3, 5, 1, 4, 0, 8, 9, 6, 7, 2} を取得します。次のことがわかります: 3, 5, 4 , 0 これらの小さな要素はすべて前述しました。
2. インクリメント ギャップ = 5/2 = 2 を減らします。配列は 2 つのグループ、つまり [3, 1, 0, 9, 7]、[5, 4, 8, 6, 2] に分割されます。これら 2 つのグループについてそれぞれ直接挿入ソートを実行すると、配列全体がさらに順序付けされていることがわかります。
3. 増分を再度減らします (gap = 2/2 = 1)。このとき、配列全体は [0, 2, 1, 4, 3, 5, 7, 6, 9, 8] になります。挿入ソートを実行して、配列のソート (大規模な移動操作ではなく、単純な微調整のみが必要です)。
- Java言語の実装
import java.util.Arrays;
/**
* @author 兴趣使然黄小黄
* @version 1.0
* 希尔排序
*/
public class ShellSort {
public static void main(String[] args) {
int[] arr = {
8, 9, 1, 7, 2, 3, 5, 6, 4, 0};
System.out.println("排序前: " + Arrays.toString(arr));
shellSort(arr);
System.out.println("排序后: " + Arrays.toString(arr));
}
//希尔排序
public static void shellSort(int[] arr){
//设定步长
for (int gap = arr.length / 2; gap > 0; gap /= 2){
//将数据分为arr.length/gap组,逐个对其所在的组进行插入排序
for (int i = gap; i < arr.length; i++) {
//遍历各组中的所有元素,步长为gap
int j = i;
int temp = arr[j]; //记录待插入的值
while (j - gap >= 0 && temp < arr[j-gap]){
//移动
arr[j] = arr[j-gap];
j -= gap;
}
//找到位置,进行插入
arr[j] = temp;
}
System.out.println(Arrays.toString(arr));
}
}
}
5. マージソート
マージ ソート (マージ ソート) は、マージ操作に基づく効果的で安定した並べ替えアルゴリズムであり、分割統治の非常に典型的なアプリケーションです。すでに順序付けられているサブシーケンスをマージして、完全に順序付けられたシーケンスを取得します。つまり、最初に各サブシーケンスを順序どおりにしてから、サブシーケンス セグメントを順序どおりにします。
-
アルゴリズム ステップ
ステップ 1—カットオフ ポイント mid を決定します (一般的に使用される 3 つの方法): q[l]、q[(l+r)/2]、q[r]、またはランダムに決定します; ステップ 2—列を次のように調整します。
左と右の 2 つの部分列にソート、再帰的に左と右にソート;
ステップ 3 - マージ (2 つを 1 つに結合)、完了! -
デモ
-
Cの実装
int min(int x, int y) {
return x < y ? x : y;
}
void merge_sort(int arr[], int len) {
int *a = arr;
int *b = (int *) malloc(len * sizeof(int));
int seg, start;
for (seg = 1; seg < len; seg += seg) {
for (start = 0; start < len; start += seg * 2) {
int low = start, mid = min(start + seg, len), high = min(start + seg * 2, len);
int k = low;
int start1 = low, end1 = mid;
int start2 = mid, end2 = high;
while (start1 < end1 && start2 < end2)
b[k++] = a[start1] < a[start2] ? a[start1++] : a[start2++];
while (start1 < end1)
b[k++] = a[start1++];
while (start2 < end2)
b[k++] = a[start2++];
}
int *temp = a;
a = b;
b = temp;
}
if (a != arr) {
int i;
for (i = 0; i < len; i++)
b[i] = a[i];
b = a;
}
free(b);
}
6. クイックソート
クイック ソートはバブル ソートを改良したものです。1962 年に CAR Hoare によって提案された基本的なアイデアは、レコードをピボットとして選択し、ソート プロセスの後にシーケンス全体を 2 つの部分に分割し、その 1 つの部分の値はピボットよりも小さく、もう 1 つの部分はピボットよりも大きいというものです。ピボット。次に、シーケンス全体が順序どおりになるように、これら 2 つの部分の並べ替えを続けます。
- アルゴリズムのステップ
- 1. 基本的な考え方:
たとえば、ソートされるソース配列の場合、arr = {4, 1, 3, 2, 7, 6, 8}。
要素は自由に選択できます。配列の最初の要素を選択する場合、この要素を「ピボット」と呼びます。
次に、ピボット以上の要素を右側に配置し、ピボット以下の要素を左側に配置します。
このルールを調整すると、左側の要素はすべてピボット以下になり、右側の要素はピボット以上になります。明らかに、このときのピボットの位置は順序付けされた位置です。つまり、ピボットはすでにソートされた位置にあります。
ピボットは配列を 2 つの半分に分割します。ピボットを介して大きな配列を 2 つの小さな部分に分割する操作は、パーティション操作とも呼ばれます。
次に、再帰によって左側と右側の部分に同じ方法を使用し、毎回メイン要素を 1 つ選択して順序付けられた位置に配置します。もちろん、再帰は部分配列の要素が 1 つだけ、または要素が 0 の場合に終了します。
コード: Quick_sort はクイック ソート アルゴリズムであり、partition 関数は配列の分割操作です。分割操作には多くの方法があります。
素早いソート分割操作には多くの方法がありますが、最も基本的な方法がここにリストされています。
-
アニメーションデモンストレーション
-
選択ソートの C 言語実装
void QuickSort(int array[], int low, int high) {
int i = low;
int j = high;
if(i >= j) {
return;
}
int temp = array[low];
while(i != j) {
while(array[j] >= temp && i < j) {
j--;
}
while(array[i] <= temp && i < j) {
i++;
}
if(i < j) {
swap(array[i], array[j]);
}
}
//将基准temp放于自己的位置,(第i个位置)
swap(array[low], array[i]);
QuickSort(array, low, i - 1);
QuickSort(array, i + 1, high);
}
7. ヒープソート
ヒープソートは、ヒープなどのデータ構造を使用して設計されたソート アルゴリズムを指します。これは、ほぼ完全なバイナリ ツリーである構造をスタックし、子ノードのキー値またはインデックスが常に小さい (または大きい) というスタッキングの特性を満たします。より) それの親ノード。
これは 2 つの方法に分けられます:
大きなトップ ヒープ: 親ノードが子ノード以上であり、昇順アルゴリズムに属します;
スモール トップ ヒープ: 親ノードが子ノード以下であり、これは昇順アルゴリズムに属します。降順アルゴリズムに属し、
ヒープ ソートの平均時間計算量は O (nlogn) です。
ヒープは通常、大量のデータの中から上位 N 個の最大データまたは上位 N 個の最小データを見つけるために使用されます。
大きいトップヒープ: 大きいものから小さいものへの降順 (Desc) 12...
小さいトップヒープ: 小さいものから大きいものへの昇順 (Asc) 2...
大きな上部ヒープを構築するためのアルゴリズム手順:
小さな上部ヒープを構築します。
- アニメーションデモンストレーション
- C 言語は選択ソートを実装します
。大きなコード:
#include <stdio.h>
void swap(int arr[], int i, int j){
int temp = arr[i];
arr[i] = arr[j];
arr[j] = temp;
}
void heapify(int tree[], int n, int i){
if (i >= n){
return;
}
int c1 = 2 * i + 1;
int c2 = 2 * i + 2;
int max = i;
if (c1 < n && tree[c1] > tree[max]){
max = c1;
}
if (c2 < n && tree[c2] > tree[max]){
max = c2;
}
if (max != i){
swap(tree, max ,i);
heapify(tree, n, max);
}
}
void build_heap(int tree[], int n){
int last_node = n - 1;
int parent = (last_node - 1) / 2;
for (int i = parent; i >= 0; i--){
heapify(tree, n, i);
}
}
void heap_sort(int tree[], int n){
build_heap(tree, n);
for (int i = n - 1; i >= 0; i--){
swap(tree, i, 0);
heapify(tree, i, 0);
}
}
int main(){
int tree[] = {
6, 10, 3, 9, 5, 12, 7, 2, 8};
int n = 9;
build_heap(tree, 9);
// heap_sort(tree, 9);
for(int i = 0; i < n; i++){
printf("%d\n",tree[i]);
}
return 0;
}
大きなトップ ヒープの結果:大きいものから小さいものへ、結果は降順 (説明)
. 小さなトップ ヒープ コード:
#include <stdio.h>
void swap(int arr[], int i, int j){
int temp = arr[i];
arr[i] = arr[j];
arr[j] = temp;
}
void heapify(int tree[], int n, int i){
if (i >= n){
return;
}
int c1 = 2 * i + 1;
int c2 = 2 * i + 2;
int max = i;
if (c1 < n && tree[c1] > tree[max]){
max = c1;
}
if (c2 < n && tree[c2] > tree[max]){
max = c2;
}
if (max != i){
swap(tree, max ,i);
heapify(tree, n, max);
}
}
void build_heap(int tree[], int n){
int last_node = n - 1;
int parent = (last_node - 1) / 2;
for (int i = parent; i >= 0; i--){
heapify(tree, n, i);
}
}
void heap_sort(int tree[], int n){
build_heap(tree, n);
for (int i = n - 1; i >= 0; i--){
swap(tree, i, 0);
heapify(tree, i, 0);
}
}
int main(){
int tree[] = {
6, 10, 3, 9, 5, 12, 7, 2, 8};
int n = 9;
// build_heap(tree, 9);
heap_sort(tree, 9);
for(int i = 0; i < n; i++){
printf("%d\n",tree[i]);
}
return 0;
}
小さなトップヒープの結果:小さいものから大きいものへの昇順 (Asc)
8. 数えて仕分けする
カウンティングソートの核心は、入力データ値をキーに変換し、追加で開かれた配列空間に格納することです。線形時間計算量ソートであるカウンティング ソートでは、入力データが特定の範囲内の整数である必要があります。
- カウンティングソートの特徴
入力要素が0からkまでの整数n個の場合、実行時間はΘ(n + k)となります。カウントソートは比較ソートではなく、ソートはどの比較ソートアルゴリズムよりも高速です。
カウントに使用される配列 C の長さは、ソートされる配列内のデータの範囲 (ソートされる配列の最大値と最小値の差に 1 を加えたものに等しい) に依存するため、カウントのソートが必要になります。広いデータ範囲を持つ多数の配列、時間とメモリ。たとえば、カウント ソートは 0 から 100 までの数値を並べ替えるには最適なアルゴリズムですが、名前をアルファベット順に並べ替えるには適していません。ただし、カウントソートは、基数ソートで使用されるアルゴリズムを使用して、大きなデータ範囲を持つ配列をソートするために使用できます。
わかりやすく言うと、例えば、年齢の異なる人が10人いて、Aさんより年下が8人いると計算すると、Aさんの年齢は9位となり、この方法で他の全員の位置を取得することができます。ランク付けされます。もちろん、年齢が繰り返される場合には特別な処理が必要です (安定性を確保するため)。そのため、最後にターゲット配列が埋められ、各数値の統計が 1 減算されます。
-
アルゴリズムの手順
(1) ソート対象の配列内の最大要素と最小要素を検索します
(2) 配列内で値 i を持つ各要素の出現数をカウントし、それを配列 C の i 番目の項目に格納します
(3)すべてのカウントを累積します (C の最初の要素から開始して、各項目が前の項目に追加されます)
(4) ターゲット配列を逆に埋める: 各要素 i を新しい配列の C(i) 番目の項目に配置し、各要素 i 要素、C(i) から 1 を減算します。 -
アニメーションデモンストレーション
-
選択ソートの C 言語実装
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
void print_arr(int *arr, int n) {
int i;
printf("%d", arr[0]);
for (i = 1; i < n; i++)
printf(" %d", arr[i]);
printf("\n");
}
void counting_sort(int *ini_arr, int *sorted_arr, int n) {
int *count_arr = (int *) malloc(sizeof(int) * 100);
int i, j, k;
for (k = 0; k < 100; k++)
count_arr[k] = 0;
for (i = 0; i < n; i++)
count_arr[ini_arr[i]]++;
for (k = 1; k < 100; k++)
count_arr[k] += count_arr[k - 1];
for (j = n; j > 0; j--)
sorted_arr[--count_arr[ini_arr[j - 1]]] = ini_arr[j - 1];
free(count_arr);
}
int main(int argc, char **argv) {
int n = 10;
int i;
int *arr = (int *) malloc(sizeof(int) * n);
int *sorted_arr = (int *) malloc(sizeof(int) * n);
srand(time(0));
for (i = 0; i < n; i++)
arr[i] = rand() % 100;
printf("ini_array: ");
print_arr(arr, n);
counting_sort(arr, sorted_arr, n);
printf("sorted_array: ");
print_arr(sorted_arr, n);
free(arr);
free(sorted_arr);
return 0;
}
9. バケットソート
- 選択ソートを実装するための C++ 言語
#include<iterator>
#include<iostream>
#include<vector>
using namespace std;
const int BUCKET_NUM = 10;
struct ListNode{
explicit ListNode(int i=0):mData(i),mNext(NULL){
}
ListNode* mNext;
int mData;
};
ListNode* insert(ListNode* head,int val){
ListNode dummyNode;
ListNode *newNode = new ListNode(val);
ListNode *pre,*curr;
dummyNode.mNext = head;
pre = &dummyNode;
curr = head;
while(NULL!=curr && curr->mData<=val){
pre = curr;
curr = curr->mNext;
}
newNode->mNext = curr;
pre->mNext = newNode;
return dummyNode.mNext;
}
ListNode* Merge(ListNode *head1,ListNode *head2){
ListNode dummyNode;
ListNode *dummy = &dummyNode;
while(NULL!=head1 && NULL!=head2){
if(head1->mData <= head2->mData){
dummy->mNext = head1;
head1 = head1->mNext;
}else{
dummy->mNext = head2;
head2 = head2->mNext;
}
dummy = dummy->mNext;
}
if(NULL!=head1) dummy->mNext = head1;
if(NULL!=head2) dummy->mNext = head2;
return dummyNode.mNext;
}
void BucketSort(int n,int arr[]){
vector<ListNode*> buckets(BUCKET_NUM,(ListNode*)(0));
for(int i=0;i<n;++i){
int index = arr[i]/BUCKET_NUM;
ListNode *head = buckets.at(index);
buckets.at(index) = insert(head,arr[i]);
}
ListNode *head = buckets.at(0);
for(int i=1;i<BUCKET_NUM;++i){
head = Merge(head,buckets.at(i));
}
for(int i=0;i<n;++i){
arr[i] = head->mData;
head = head->mNext;
}
}
10. 基数ソート
- LSD 基数ソート アニメーションのデモンストレーション
- 選択ソートの C 言語実装
#include<stdio.h>
#define MAX 20
//#define SHOWPASS
#define BASE 10
void print(int *a, int n) {
int i;
for (i = 0; i < n; i++) {
printf("%d\t", a[i]);
}
}
void radixsort(int *a, int n) {
int i, b[MAX], m = a[0], exp = 1;
for (i = 1; i < n; i++) {
if (a[i] > m) {
m = a[i];
}
}
while (m / exp > 0) {
int bucket[BASE] = {
0 };
for (i = 0; i < n; i++) {
bucket[(a[i] / exp) % BASE]++;
}
for (i = 1; i < BASE; i++) {
bucket[i] += bucket[i - 1];
}
for (i = n - 1; i >= 0; i--) {
b[--bucket[(a[i] / exp) % BASE]] = a[i];
}
for (i = 0; i < n; i++) {
a[i] = b[i];
}
exp *= BASE;
#ifdef SHOWPASS
printf("\nPASS : ");
print(a, n);
#endif
}
}
int main() {
int arr[MAX];
int i, n;
printf("Enter total elements (n <= %d) : ", MAX);
scanf("%d", &n);
n = n < MAX ? n : MAX;
printf("Enter %d Elements : ", n);
for (i = 0; i < n; i++) {
scanf("%d", &arr[i]);
}
printf("\nARRAY : ");
print(&arr[0], n);
radixsort(&arr[0], n);
printf("\nSORTED : ");
print(&arr[0], n);
printf("\n");
return 0;
}