[C++ は再帰アルゴリズム、マージ ソート、ヒープ ソートを実装]

1. 再帰的アルゴリズム

アルゴリズムを繰り返し実行する必要がある場合は、再帰的アルゴリズムを使用するのが良い選択となります。このアルゴリズムでは、再帰を停止する特定の条件に達するまで、関数がそれ自体を呼び出しますC++ 言語では再帰がサポートされています。C++ を使用して再帰アルゴリズムを実装する方法を詳しく紹介します。

このチュートリアルでは、フィボナッチ数列を例として再帰アルゴリズムの実装を紹介します。

黄金分割数列としても知られるフィボナッチ数列は、数学者レオナルド フィボナッチがウサギの飼育を例にして紹介したものであるため、「ウサギ数列」とも呼ばれます。 、5、8、13、21、34、...数学では、フィボナッチ数列は次のように再帰的に定義されます: F (0)= 1、F (1)=1、F (n)= F (n - 1) ) + F (n - 2) ( n ≥ 2、n ∈ N*)

1. 関数を定義する

まず、フィボナッチ数列の n 番目の項を計算する関数を定義する必要があります。フィボナッチ数列の 0 番目と 1 番目の項目が既知であることを考慮すると、関数を 2 つの場合に分けることができます: n が 0 または 1 の場合は、n の値を直接返します; それ以外の場合は、フィボナッチ n-1 と n-1 の合計を返します。 Natch シーケンス内の n-2 項。コードは以下のように表示されます。

int fibonacci(int n) {
    
    
    if (n == 0 || n == 1) {
    
    
        return n;
    }
    else {
    
    
        return fibonacci(n - 1) + fibonacci(n - 2);
    }
}

2. テスト機能

この関数をテストに使用し、フィボナッチ数列の最初のいくつかの項を計算できます。たとえば、次のコードを使用して、フィボナッチ数列の最初の 10 項を計算できます。

int main() {
    
    
    for (int i = 0; i < 10; i++) {
    
    
        cout << fibonacci(i) << " ";
    }
    return 0;
}

3. 再帰的アルゴリズムを分析する

この例では、再帰アルゴリズムを使用して、次の再帰式を使用してフィボナッチ数列の n 番目の項を計算できます。

F(n) = F(n-1) + F(n-2)

このうち、F(n)はフィボナッチ数列のn番目の項目を表します。n=0 または 1 の場合は、n そのものを直接返します。

再帰的アルゴリズムでは、再帰を終了するための条件を設定する必要があることに注意してください。この例では、n=0 または 1 のときに再帰が終了するように設定します。再帰を終了する条件が設定されていない場合、または終了条件が正しく設定されていない場合、再帰は無限に実行され続けるため、スタック オーバーフローなどの問題が発生します。

4. まとめ

このチュートリアルでは、フィボナッチ数列を例として、C++ を使用して再帰アルゴリズムを実装する基本手順を紹介します。関数の定義、関数のテストから再帰アルゴリズムの分析まで、このチュートリアルは、再帰アルゴリズムの概念をより深く理解し、その実装方法を習得するのに役立つことを目的としています。

完全なコードは次のとおりです。

#include<iostream>
using namespace std;
int fibonacci(int n);
int main()
{
    
    
	for (int i = 0; i < 10; i++) {
    
    
        cout << fibonacci(i) << " ";
    }
    return 0;

}

int fibonacci(int n) {
    
    
    if (n == 0 || n == 1) {
    
    
        return n;
    }
    else {
    
    
        return fibonacci(n - 1) + fibonacci(n - 2);
    }
}

2. マージソート

マージ ソート (マージ ソート) は、分割統治アルゴリズムです。その基本的な考え方は、分割できなくなるまで大きな配列を 2 つの小さな配列に再帰的に分割し、その後、これらの小さな配列をペアでマージし、継続的にマージすることです。元の配列が完全にソートされるまでマージ ソートは安定しており、時間計算量は O(nlogn) で安定し、空間計算量は O(n) です。

次の手順では、マージ ソートの詳細な実装を紹介します。

1、分解(Divide)

まず、ソート対象の配列が、各サブ配列の要素が 1 つだけになるまで、再帰的に 2 つのサブ配列に分割されます。これは、配列が長さ 1 の n 個のサブ配列に分解されたことを意味します。

2.マージ

サブ配列を 2 つずつマージして n/2 個のソートされたサブ配列を取得し、長さ n のソートされた配列が 1 つだけ残るまで、これらのソートされたサブ配列を再度マージします。2 つのソートされた部分配列をマージする手順は次のとおりです。

(1) マージされた要素を格納する空の配列を作成します。

(2) ソートされた 2 つの配列を同時に走査し、それらの最初の要素を比較し、小さい方の要素を新しい配列に追加します。

(3) いずれかの配列内のすべての要素が新しい配列に入れられるまで、上記の手順を繰り返します。

(4) 別の配列のすべての要素を新しい配列の末尾に直接追加します。

(5) 新しい配列を返します。

3. マージソートのコード実装

以下は、マージ・ソートのコード実装です。

void merge(int nums[], int left, int mid, int right) {
    
    
    /*将原数组分成两个部分,左半部分为[nums[left], nums[mid]],
    右半部分为[nums[mid + 1], nums[right]]*/
    int n1 = mid - left + 1;
    int n2 = right - mid;
    int L[n1], R[n2];
    for (int i = 0; i < n1; i++) {
    
    
        L[i] = nums[left + i];
    }
    for (int i = 0; i < n2; i++) {
    
    
        R[i] = nums[mid + 1 + i];
    }
    // 将两个数组合并为一个升序数组
    int i = 0, j = 0, k = left;
    while (i < n1 && j < n2) {
    
    
        if (L[i] <= R[j]) {
    
    
            nums[k++] = L[i++];
        }
        else {
    
    
            nums[k++] = R[j++];
        }
    }
    while (i < n1) {
    
    
        nums[k++] = L[i++];
    }
    while (j < n2) {
    
    
        nums[k++] = R[j++];
    }
}

void mergeSort(int nums[], int left, int right) {
    
    
    if (left < right) {
    
    
        int mid = left + (right - left) / 2;
        mergeSort(nums, left, mid);
        mergeSort(nums, mid + 1, right);
        merge(nums, left, mid, right);
    }
}

4. テストコード

テストコードは次のとおりです。

int main() {
    
    
    int nums[] = {
    
    2, 5, 1, 6, 3, 8, 4, 7};
    int len=sizeof(nums)/sizeof(nums[0]);
    cout<<"排序前:";
	for (int i=0;i<len;i++) {
    
    
        cout << nums[i] << " ";
    } 
	mergeSort(nums, 0, len - 1);
    cout<<"\n排序后:"; 
	for (int i=0;i<len;i++) {
    
    
        cout << nums[i] << " ";
    }
    return 0;
}

上記コードでは、テストデータ {2, 5, 1, 6, 3, 8, 4, 7} からマージソートにより得られるソート後のデータは {1, 2, 3, 4, 5, 6, 7 , 8}。

5. まとめ

マージ ソートは、配列を再帰的に半分に分割し、その 2 つの半分をソートされた配列にマージする効率的なソート アルゴリズムです。大規模なデータでも効率的にソートを実現できます。マージ ソートの実装プロセスには多くのポインターとループが含まれるため、境界を越えるなどの問題を避けるために実装中に細心の注意を払う必要があることに注意してください。

6. 完全なコード

#include<iostream>
using namespace std;

void merge(int nums[], int left, int mid, int right) {
    
    
    /*将原数组分成两个部分,左半部分为[nums[left], nums[mid]],
    右半部分为[nums[mid + 1], nums[right]]*/
    int n1 = mid - left + 1;
    int n2 = right - mid;
    int L[n1], R[n2];
    for (int i = 0; i < n1; i++) {
    
    
        L[i] = nums[left + i];
    }
    for (int i = 0; i < n2; i++) {
    
    
        R[i] = nums[mid + 1 + i];
    }
    // 将两个数组合并为一个升序数组
    int i = 0, j = 0, k = left;
    while (i < n1 && j < n2) {
    
    
        if (L[i] <= R[j]) {
    
    
            nums[k++] = L[i++];
        }
        else {
    
    
            nums[k++] = R[j++];
        }
    }
    while (i < n1) {
    
    
        nums[k++] = L[i++];
    }
    while (j < n2) {
    
    
        nums[k++] = R[j++];
    }
}

void mergeSort(int nums[], int left, int right) {
    
    
    if (left < right) {
    
    
        int mid = left + (right - left) / 2;
        mergeSort(nums, left, mid);
        mergeSort(nums, mid + 1, right);
        merge(nums, left, mid, right);
    }
}
int main() {
    
    
    int nums[] = {
    
    2, 5, 1, 6, 3, 8, 4, 7};
    int len=sizeof(nums)/sizeof(nums[0]);
    cout<<"排序前:";
	for (int i=0;i<len;i++) {
    
    
        cout << nums[i] << " ";
    } 
	mergeSort(nums, 0, len - 1);
    cout<<"\n排序后:"; 
	for (int i=0;i<len;i++) {
    
    
        cout << nums[i] << " ";
    }
    return 0;
}

3. ヒープソート

ヒープソートはバイナリヒープに基づくソートアルゴリズムであり、その時間計算量はO(nlogn)であり、実用的なアプリケーションで広く使用されています。ヒープ ソートの基本的な考え方は、ソート対象のシーケンスをバイナリ ヒープに構築し、ヒープが空になるまでヒープの先頭要素を順番に取り出すことです

1. ヒープの定義

ヒープは完全なバイナリ ツリーであり、max-heap と min-heap の 2 つのタイプに分けられます。

最大ヒープ: 各ノードの値は、その左右の子ノードの値以上です。

最小ヒープ: 各ノードの値は、その左右の子ノードの値以下です。

2. ヒープソートの基本的な考え方

ヒープ ソートの基本的な考え方は、ソート対象のシーケンスをバイナリ ヒープに構築し、ヒープが空になるまでヒープの先頭要素を順番に取り出すことです。具体的なプロセスは次のとおりです。

(1) ソート対象のシーケンスを最大ヒープに構築します。

(2) ヒープの最上部の要素とヒープの最下部の要素を交換し、ヒープの最下部にある要素を削除し、残りの要素を最大ヒープに再構築します。

(3) ヒープが空になるまで手順 (2) を繰り返します。

3. ヒープソートの実装

(1) 最大のヒープを構築する

最大ヒープを構築するプロセスは、最後の非リーフ ノードから開始して、最大ヒープになるように各ノードをそのサブツリーに順次調整するボトムアップ方法を採用できます。具体的な実装は以下の通りです。

void AdjustHeap(int arr[], int i, int n)
{
    
    
    int temp = arr[i];
    int j = 2 * i + 1;
    while (j < n)
    {
    
    
        if (j + 1 < n && arr[j + 1] > arr[j])
        {
    
    
            j++;
        }
        if (arr[j] > temp)
        {
    
    
            arr[i] = arr[j];
            i = j;
            j = 2 * i + 1;
        }
        else
        {
    
    
            break;
        }
    }
    arr[i] = temp;
}

void buildMaxHeap(int arr[], int n)
{
    
    
    for (int i = n / 2 - 1; i >= 0; i--)
    {
    
    
        AdjustHeap(arr, i, n);
    }
}

(2) ヒープソート

ヒープソートの実装プロセスは次のとおりです。

void heapSort(int arr[], int n)
{
    
    
    buildMaxHeap(arr, n);
    for (int i = n - 1; i > 0; i--)
    {
    
    
        swap(arr[0], arr[i]);
        AdjustHeap(arr, 0, i);
    }
}

4. ヒープソートの最適化

(1) ヒープ構築プロセスの最適化

ヒープを構築する過程で、ノード i の左と右の子ノードはそれぞれ 2i+1 と 2i+2 になります。左と右の子ノードの大きい値がノード i の値より大きい場合、より大きな値を i ノードのスワップと組み合わせる必要があり、その後下方調整が続けられます。このプロセスはループで実装できるため、再帰呼び出しが回避され、効率が向上します。

void AdjustHeap(int arr[], int i, int n)
{
    
    
    int temp = arr[i];
    for (int j = 2 * i + 1; j < n; j = 2 * j + 1)
    {
    
    
        if (j + 1 < n && arr[j + 1] > arr[j])
        {
    
    
            j++;
        }
        if (arr[j] > temp)
        {
    
    
            arr[i] = arr[j];
            i = j;
        }
        else
        {
    
    
            break;
        }
    }
    arr[i] = temp;
}

(2) 仕分けプロセスの最適化

ソート処理中に、ヒープの最上位要素がヒープの最下位要素と交換されるたびに、ヒープのサイズが 1 ずつ減り、その後ヒープを再調整する必要があります。ただし、ヒープの最下位要素はヒープの最上位に交換されており、ヒープの最下位要素より上の部分は依然として最大ヒープであるため、ヒープ全体を再構築せずにヒープの最上位要素を直接調整できます。これにより、不必要な操作を減らすことができます。

void heapSort(int arr[], int n)
{
    
    
    buildMaxHeap(arr, n);
    for (int i = n - 1; i > 0; i--)
    {
    
    
        swap(arr[0], arr[i]);
        AdjustHeap(arr, 0, i);
    }
}

5. ヒープソートの安定性

ヒープ ソートは、ヒープを調整する過程で同じ要素の位置が交換される可能性があるため、不安定なソート アルゴリズムです。たとえば、シーケンス {5, 2, 5, 1, 6, 2} の場合、ヒープ ソート後は {1, 2, 2, 5, 5, 6} になる可能性があり、2 つの 2 の相対位置が変化します。 。

6. 完全なコード

#include<iostream>
using namespace std;
//构建最大堆 
void AdjustHeap(int arr[], int i, int n)
{
    
    
    int temp = arr[i];
    for (int j = 2 * i + 1; j < n; j = 2 * j + 1)
    {
    
    
        if (j + 1 < n && arr[j + 1] > arr[j])
        {
    
    
            j++;
        }
        if (arr[j] > temp)
        {
    
    
            arr[i] = arr[j];
            i = j;
        }
        else
        {
    
    
            break;
        }
    }
    arr[i] = temp;
}

void buildMaxHeap(int arr[], int n)
{
    
    
    for (int i = n / 2 - 1; i >= 0; i--)
    {
    
    
        AdjustHeap(arr, i, n);
    }
}
//堆排序 
void heapSort(int arr[], int n)
{
    
    
    buildMaxHeap(arr, n);
    for (int i = n - 1; i > 0; i--)
    {
    
    
        swap(arr[0], arr[i]);
        AdjustHeap(arr, 0, i);
    }
}

int main()
{
    
    
	int arr[]={
    
    4,6,3,7,2,9};
	int len=sizeof(arr)/sizeof(arr[0]);
	cout<<"原数组:"; 
	for(int i=0;i<len;i++){
    
    
		cout<<arr[i]<<" ";
	}	
	heapSort(arr,len);
	cout<<endl;
	cout<<"排序后:";
	for(int i=0;i<len;i++){
    
    
		cout<<arr[i]<<" ";
	}
 } 

おすすめ

転載: blog.csdn.net/qq_43884946/article/details/131186691