データ構造:一般的な並べ替えアルゴリズム(5):ヒープ並べ替え
ヒープソートは一種のツリー選択ソートであり、直接選択ソートの効果的な改善です。ヒープは、特別なツリーデータ構造、つまり完全なバイナリツリーです。ヒープは、大きなルートヒープと小さなルートヒープに分けられます。大きなルートヒープは、ルートノードの値が2つの子ノードの値よりも大きいことを意味します。小さなルートヒープは、ルートノードの値が2つの子ノードの値よりも小さいことを意味し、ルートノードの2つのサブツリーも1つです。ヒープ。
ヒープの定義:(hi> = h2i、hi> = 2i + 1)または(hi <= h2i、hi <= 2i)の場合に限り、n個の要素(h1、h2、..。、hn)を持つシーケンス+1)(i = 1,2、...、n / 2)はヒープと呼ばれます。ここでは、前者の条件を満たすヒープについてのみ説明します。ヒープの定義から、最上位の要素(つまり、最初の要素)が最大のアイテム(大きな最上位のヒープ)でなければならないことがわかります。完全なバイナリツリーは、ヒープの構造を直感的に表すことができます。ヒープの最上部はルートであり、その他は左右のサブツリーです。(プレオーダートラバーサル、ミドルオーダートラバーサル、ポストオーダートラバーサルに拡張できます)
1.基本的な考え方:
当初は、並べ替える番号の順番を順番に格納されたバイナリツリーと見なし、格納順序を調整してヒープにします。このとき、ヒープのルートノードの数が最大になります。次に、ルートノードをヒープの最後のノードと交換します。次に、前の(n-1)番号を再調整してパイルにします。以下同様に、ヒープ内にノードが2つだけになるまで、それらを交換し、最後にnノードの順序付けられたシーケンスを取得します。アルゴリズムの説明の観点から、ヒープの並べ替えには2つのプロセスが必要です。1つはヒープを構築するプロセスで、もう1つはヒープの上部とヒープの最後の要素の間で位置を交換するプロセスです。したがって、ヒープソートには2つの機能があります。1つは原子炉を構築するための貫通機能であり、もう1つは貫通関数を繰り返し呼び出して仕分けを行う機能です。
難しいのは(1)シーケンスを大きなヒープに生成する方法です
(2)ヒープの一番上の要素を出力した後、残りの要素に大きなルートヒープを生成させる方法
アイデア:
-
ステップ1:大きなルートパイルを構築する-n個の要素の順序付けられていないシーケンスから大きなルートパイルを構築します。
-
ステップ2:ヒープ要素を交換する-終了要素とヘッド要素を交換して、終了要素が最大の要素になるようにします。
-
ステップ3:ビッグルートパイルを再構築する-最初のn-1要素で構成される無秩序なシーケンスをビッグルートパイルに調整する
シーケンス全体が整うまで、手順2と3を繰り返します。
2.例
例1:arr []
画像ソース:https://www.cnblogs.com/zwtgyh/p/10631760.html
#include<iostream>
#include<vector>
using namespace std;
// 递归方式构建大根堆(len是arr的长度,index是第一个非叶子节点的下标)
void adjust(vector<int> &arr, int len, int index)
{
int left = 2 * index + 1; // index的左子节点
int right = 2 * index + 2;// index的右子节点
int maxIdx = index;
if (left<len && arr[left] > arr[maxIdx]) maxIdx = left;
if (right<len && arr[right] > arr[maxIdx]) maxIdx = right;
if (maxIdx != index)
{
swap(arr[maxIdx], arr[index]);
adjust(arr, len, maxIdx);
}
}
// 堆排序
void heapSort(vector<int> &arr, int size)
{
// 构建大根堆(从最后一个非叶子节点向上)
for (int i = size / 2 - 1; i >= 0; i--)
{
adjust(arr, size, i);
}
// 调整大根堆
for (int i = size - 1; i >= 1; i--)
{
swap(arr[0], arr[i]); // 将当前最大的放置到数组末尾
adjust(arr, i, 0); // 将未完成排序的部分继续进行堆排序
}
}
int main()
{
vector<int> arr = { 8,6,7,4,5,3,2,1 };
heapSort(arr, arr.size());
for (int i = 0; i<arr.size(); i++)
{
cout << arr[i] <<" ";
}
cout << endl;
return 0;
}
例2:arr [4,6,8,5,9]ヒープによる並べ替え
- ステップ1:大きなルートヒープを構築する
①完全なバイナリツリーを構築するための順序付けられていないシーケンス
②最後のリーフノードから、左から右、下から上に調整し、完全なバイナリツリーを大きなルートパイルに調整します
a。6の右側の子ノードが6より大きいため、最初の非リーフノード6を見つけます。したがって、6と9を交換します。交換後は、ビッグルートパイルの構造に適合します。
c。4の左側の子ノードが4より大きいため、2番目の非リーフノード4を見つけます。したがって、4と9を交換します。交換が大きなルートパイルの構造に適合しなくなった後、右から左へ、そして下から上へと調整を続けます。
- ステップ2:ヒープ要素を交換します(ヘッド要素とテール要素を交換します-最大の要素を取得します)
- ステップ3:大きなルートパイル(最初のn-1要素)を再構築します
- シーケンス全体が整うまで、手順2と3を繰り返します。
画像ソース:https://www.cnblogs.com/chengxiao/p/6129630.html
コード例:
#include<iostream>
#include<vector>
using namespace std;
// 递归方式构建大根堆(len是arr的长度,index是第一个非叶子节点的下标)
void adjust(vector<int> &arr, int len, int index)
{
int left = 2 * index + 1; // index的左子节点
int right = 2 * index + 2;// index的右子节点
int maxIdx = index;
if (left<len && arr[left] > arr[maxIdx]) maxIdx = left;
if (right<len && arr[right] > arr[maxIdx]) maxIdx = right;
if (maxIdx != index)
{
swap(arr[maxIdx], arr[index]);
adjust(arr, len, maxIdx);
}
}
// 堆排序
void heapSort(vector<int> &arr, int size)
{
// 构建大根堆(从最后一个非叶子节点向上)
for (int i = size / 2 - 1; i >= 0; i--)
{
adjust(arr, size, i);
}
// 调整大根堆
for (int i = size - 1; i >= 1; i--)
{
swap(arr[0], arr[i]); // 将当前最大的放置到数组末尾
adjust(arr, i, 0); // 将未完成排序的部分继续进行堆排序
}
}
int main()
{
vector<int> arr = { 4, 6, 8, 5, 9 };
heapSort(arr, arr.size());
for (int i = 0; i<arr.size(); i++)
{
cout << arr[i] <<" ";
}
cout << endl;
return 0;
}
3.まとめ
- ヒープソートの時間の複雑さは、主に2つの部分で構成されます。ヒープを初期化するプロセスと、毎回最上位の要素をポップした後にヒープを再構築するプロセスです。
- ヒープを構築する初期化プロセスの時間の複雑さはO(n)です。ヒープの高さがkであると仮定すると、最後から2番目のレイヤーの右側のノードから開始して、このレイヤーのノードは子ノードを比較し、最後から3番目のノードを交換するかどうかを選択する必要があります。レイヤーは最初のレイヤーまで同じです(つまり、レイヤーの数はk-1から1です)。合計時間は(2 (i-1))*(ki)です。ここで、iはi番目のレイヤーを表します(範囲はk-1)。 1)に対して、2(i-1)はレイヤー上の要素の数を示し、(ki)はサブツリー上の比較の数を示します。つまり、S = 2 ^(k-2)* 1 + 2 ^(k-3)2 + 2 ^(k-4)3 +…+ 2 ^ 1(k-2)+ 2 ^ 0(k-1)、千鳥減算を使用(変換を支援するために定数2を使用し、両側に2を掛けて減算元の方程式を削除して)S = 2 ^(K-1)+ 2 ^(K-2)+ 2 ^(K-3)+…+ 2-(K-1)を取得し、最後の定数項を無視して待機しているだけです比率シーケンス、つまりS = 2 k-2-(k-1)= 2 kk-1であり、kは完全なバイナリツリーの深さであるため、2 ^ k <= n <2 ^ k-1であるため、k = logn、要約すると、S = n-logn -1であるため、時間の複雑さはO(n)です。
- ヒープの最上位要素をポップした後にヒープを再構築するプロセスの時間の複雑さはO(nlogn)です。フォローノードからループ検索まで毎回n-1回ループするため、毎回lognになると、合計時間は(n-1)*になります。 logn = nlogn-logn
- したがって、ヒープソートの時間の複雑さはO(n)+ O(nlogn)= O(nlogn)です。
- ヒープソートは接地ソートであるため、スペースの複雑さは一定ですO(1)