【データ構造学習記録27】-ソートとヒープソートを選択

1.選択ソート

1.原則

選択とソートは比較的簡単です。つまり、各トラバース後に、無秩序領域の最大(最小)値が無秩序領域の最初または最後の値と交換されます。このようにして、無秩序領域は徐々に順序付けられ、最後に並べ替えを完了します。その時間計算量もO(n 2)O(n ^ 2)です。O n2

2.プロセス

ここに画像の説明を挿入

3.コード

int selectsort(int arry[], int len)
{
    
    
    int min, i, j, temp;

    for(i = 0; i < len - 1; ++i)
    {
    
    
        min = i;
        for (j = i; j < len; ++j)
        {
    
    
            if (arry[j] < arry[min])
            {
    
    
                min = j;
            }
        }
        temp = arry[i];
        arry[i] = arry[min];
        arry[min] = temp;
    }
}

2.ヒープソート

1.ヒープの概念

シーケンスk1、k 2、⋅⋅⋅、kn(k_1、k_2、\ cdot \ cdot \ cdot、k_n)を想定します。k1k2kn個次の関係が満たされる場合にのみ、ヒープと呼ばれます。
{ki≤k2i、ki≤k2i + 1または{ki≥k2i、ki≥k2i + 1ここで、(i = 1、2、⋅⋅⋅、n 2)\ begin {cases}k_i≤k_{2i}、\\k_i≤k_{2i + 1} \ end {cases}または\ begin {cases}k_i≥k_{2i} 、\\k_i≥k_{2i + 1} \ end {cases} where(i = 1,2、\ cdot \ cdot \ cdot、\ frac {n} {2}){{ kk2kk2 I + 1または{{ kk2kk2 I + 1これはI=1 2 2n個)。
シーケンス番号は1から始まることに注意してください。
前者は関係以下であるため、これを呼びます。小根堆
後者は関係以上であるため、次のようにも呼びます。大根堆

2.ヒープと完全な二分木の関係

次に、このシーケンス(配列)を介して完全な二分木をシミュレートすると、ツリー構造の下で、ヒープには次のプロパティがあります。
各親ノードはその息子ノードよりも大きい(小さい)。図を参照してください。
この小さなルートがあるとします。ヒープ:
ここに画像の説明を挿入

それを完全な二分木に「変換」するには:
ここに画像の説明を挿入
ノードnの子ノードが2nと2n +1であることを確認するのは難しくありません。

3.ヒープソートのプロセス

このヒープバイナリツリーのルートノード(シーケンシャルシーケンスでシミュレート)はである必要が最大(小)あり、要素は順序付けられた状態である必要があることがわかりました。次に、ソートのアイデアは次のようになります。

  1. ヒープを構築する
  2. ルートノード要素を除外します(一番上の要素を除外するか、最後の要素と交換して除外します)
  3. 残りの要素が山を形成するように構造を再調整します
  4. すべての要素が整うまで2と3を繰り返します

もちろん、2番目のポイントでは、基本的なツリー構造が破壊されないようにするために、トップ要素とエンド要素交换パイルトップ要素の後に配置し、エンド要素を有序区、、つまり、除外、およびに分類することを選択します。ポイント3の構造の再調整には参加しないでください。
したがって、これに基づいて、通常は昇順大根堆と降順を使用します小根堆
複雑な導出の後、その時間計算量はO(nlog 2 n)O(nlog_2n)です。O n l o g2n

4.ヒープを構築します

ヒープを初期化するには、次の手順が必要です。
すべての親ノードで次の调整堆操作を実行します

  1. 子ノードがすべて親ノードよりも小さい場合、それは終了します。
  2. 非1の場合:親ノードと子ノードの最大値が交換され、交換後に子ノードで1と2の操作が実行されます。

葉でいっぱいの完全な二分木を仮定し、それがn個のノードを持っていると仮定すると、葉のノードの数は(n + 1)2 \ frac {(n + 1)} {2}であることがわかっています2N + 1 、次に、親ノードは(n − 1)2 \ frac((n-1))(2)を持ちます2N - 1 一つは
その後、我々は最後の親ノードから開始し、ループ内でヒープを構築するために(下からトップに、右から左へ)前進、我々はヒープを初期化するとします。
コードの観点から:

    // 从最后一个父结点开始,将所有结点给调整一次。
    for (i = len/2; i > 0; --i)
    {
    
    
        HeapAdjust(heapArry, i, len);
    }

5.ヒープを調整します

ヒープと考えを調整し、次のように述べました。

  1. 子ノードがすべて親ノードよりも小さい場合、それは終了します。
  2. 非1の場合:親ノードと子ノードの最大値が交換され、交換後に子ノードで1と2の操作が実行されます。

コードは次のように表示されます。

int HeapAdjust(int arry[], int index, int len)
{
    
    
    int i;
    arry[0] = arry[index]; // arry[0] 没被使用,刚好可以拿来当交换时的临时变量用
    for (i = 2*index; i <= len; i*=2)   // 从该结点的左孩子开始,且每次循环都直接到孩子的孩子,且i肯定不能超过树的大小
    {
    
    
        if (i < len && arry[i] < arry[i+1]) // 这里是判断i当前孩子是左孩子大还是右孩子大,将i指向最大的孩子
        {
    
    
            ++i;
        }
        // 把上者<改成>,下者>=改为<=,则该堆排序变成降序
        if (arry[0] >= arry[i]) // 如果我们的处理的结点大于了孩子结点,那么就必须交换。
        {
    
    
            break;
        }
        else
        {
    
    
            arry[index] = arry[i];  // 我们处理的结点被孩子顶替
            index = i;              // 我们新处理的结点变成了被顶替的孩子位置
        }
    }
    arry[index] = arry[0];  // 一直到了最后,孩子一直顶替,直到没法顶替了,就是我们最开始待处理结点的位置
}

凡例:
ルートノードのみが調整されていない場合、index = 1でノードを調整する必要があります:
ここに画像の説明を挿入
最初に入力して要素のステータスを決定します:5が調整され、7が息子への現在のポイントです:
ここに画像の説明を挿入
これで時間、[0] <[i]、つまり5 <7。調整が必要なため、[1]は[2]と等しくなり、調整する値は[1]から[1]に変更されます。 [2]で、先のとがった息子が[4]に変更されました]:
ここに画像の説明を挿入
このとき、調整が必要なため、[0] <[i]、つまり5 <6であるため、[2]は[4]と等しくなります。 ]、次に調整対象の調整が[2]から[4]に変更され、の息子が[8]に変更された(存在しない)を指しています。
ここに画像の説明を挿入

次に、ループを終了した後、調整する位置index = 4を決定したので、tempはarry [0]からarry [4]の値です。
ここに画像の説明を挿入

これでヒープ調整は完了です。

6.コード

特定のプロセスについては、ヒープソートを途中で出力して、ヒープの並べ替えと構築のプロセスを確認できます。

#include <stdio.h>
#include <stdlib.h>

int HeapAdjust(int arry[], int index, int len)
{
    
    
    int i;
    arry[0] = arry[index]; // arry[0] 没被使用,刚好可以拿来当交换时的临时变量用
    for (i = 2*index; i <= len; i*=2)   // 从该结点的左孩子开始,且每次循环都直接到孩子的孩子,且i肯定不能超过树的大小
    {
    
    
        if (i < len && arry[i] < arry[i+1]) // 这里是判断i当前孩子是左孩子大还是右孩子大,将i指向最大的孩子
        {
    
    
            ++i;
        }
        // 把上者<改成>,下者>=改为<=,则该堆排序变成降序
        if (arry[0] >= arry[i]) // 如果我们的处理的结点大于了孩子结点,那么就必须交换。
        {
    
    
            break;
        }
        else
        {
    
    
            arry[index] = arry[i];  // 我们处理的结点被孩子顶替
            index = i;              // 我们新处理的结点变成了被顶替的孩子位置
        }
    }
    arry[index] = arry[0];  // 一直到了最后,孩子一直顶替,直到没法顶替了,就是我们最开始待处理结点的位置
}

int HeapSort(int arry[], int len)
{
    
    
    int i = 1;

    int *heapArry = (int*)malloc(sizeof(int) * (len+1));    // 构造一个从下标1开始的序列。
    for (i = 1; i <= len; ++i)
    {
    
       
        heapArry[i] = arry[i - 1];
    }

    // 从最后一个父结点开始,将所有结点给调整一次。
    for (i = len/2; i > 0; --i)
    {
    
    
        HeapAdjust(heapArry, i, len);
    }

    // 堆排序。
    for (i = len; i > 0; --i)
    {
    
    
        arry[i-1] = heapArry[1];    // 堆顶是我们的最大元素,赋值给原数组
        heapArry[1] = heapArry[i];  // 因为是交换,所以要把最后一个元素给堆顶,堆顶给最后一个元素(有序),但我们可以舍弃这个保存,因为存到了老数组里
        HeapAdjust(heapArry, 1, i - 1); // i之后的结点是有序的(尽管没有赋值),所以不参与堆的构造
    }
}

int main()
{
    
    
    int a[7] = {
    
    5,6,3,7,2,1,4};
    int i;
    HeapSort(a, 7);
    for (i = 0; i < 7; ++i)
    {
    
    
        printf("%d ", a[i]);
    }
    return 0;
}

おすすめ

転載: blog.csdn.net/u011017694/article/details/111504356