データ構造:一般的な並べ替えアルゴリズム(5):ヒープ並べ替え(C ++実装)

データ構造:一般的な並べ替えアルゴリズム(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 []

img

画像ソース: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)

おすすめ

転載: blog.csdn.net/qq_43801020/article/details/108136124