ヒープ関連の問題と第 2 レベルの結論について考える ヒープ ソートの時間計算量を調べる


  前書き: 今日はヒープについて学びましょう。当初、ヒープのステレオタイプは二分木であり、最下位層はそれが連結リストによって形成されていると考えていましたが、ヒープの知識を学習した後、それは配列によって実装されていることが判明しました。簡単に言うと、ヒープの論理構造は完全なバイナリ ツリーですが、物理構造は配列に連続的に格納されます。

パイル



1 ヒープ構造

  ヒープの構造定義は配列で構成されているため、ヒープ用のスペースと有効なデータとヒープ容量を動的に空ける必要があります。

typedef struct Heap
{
    
    
	HPDataType* a;
	int size; //有效数据个数
	int capacity; //容量
}HP; 

2 ヒープの初期化と拡張

  有効なデータ数が保存されたので、容量がいっぱいになって拡張の問題が発生することは避けられません
初期化: まず、4 つのプラスチックの容量が十分ではないため、容量を拡張します。

void HeapInit(HP* php)
{
    
    
	assert(php);
	php->a = (HPDataType*)malloc(sizeof(HPDataType) * 4);
	php->capacity = 4; 
	php->size = 0;
}

3 ヒープの占有率、サイズ、ヒープトップのデータ、およびヒープの破壊

  比較的シンプルでコードに直接記述できます

HPDataType HeapTop(HP* php) //堆顶元素
{
    
    
	assert(php);
	assert(!HeapEmpty(php));
	return php->a[0];
}
bool HeapEmpty(HP* php) //判空
{
    
    
	assert(php);
	return php->size == 0;
}
int HeapSize(HP* php) //大小
{
    
    
	assert(php);
	return php->size;
}
void HeapDestroy(HP* php) //销毁
{
    
    
	assert(php);
	free(php->a);
	php->a = NULL;
	php->size = 0;
	php->capacity = 0;
}




2 番目に、ヒープの挿入

  ヒープに挿入します。以前に挿入された要素がヒープを形成することを示します。ヒープの性質は挿入後も維持される必要があります。ここでは大きなヒープを構築します。山が大きいということは、両方の親が左右の子よりも大きいことを意味します。

1 つの挿入要素

  要素を挿入する前に、展開が必要かどうかを必ず確認してください

void HeapPush(HP* php, HPDataType x)
{
    
    
	assert(php);
	CheakCapacity(php);
	php->a[php->size] = x;
	AdjustUp(php->a, php->size);
	php->size++;
}

2 ビルドヒープを上方に調整します

(1)アニメーション理解

  上方に調整します。挿入された要素が親よりも大きい場合は、それらの位置を交換し、子が親より小さくなるまで繰り返し上方に調整する必要があります。


(2) コードの実装

void AdjustUp(HPDataType* a, int child)
{
    
    
	assert(a);
	int parent = (child - 1) / 2;
	while (child >= 0)
	{
    
    
		if (a[parent] < a[child])
		{
    
    
			Swap(&a[parent], &a[child]);
				//开始往上迭代
			child = parent;
			parent = (child - 1) / 2;
		}
		else
		{
    
    
				//前提是前面的数据构成大堆
			break;
		}
	}
}




3. ヒープの削除

  ヒープの先頭要素を削除する前に、まずヒープが空かどうかを確認してから、下方に調整します。ここで、ヒープの末尾の要素ではなく、ヒープの先頭の要素が削除される理由を考える必要があります。ヒープの最後にある要素は簡単に削除できますが、実用性は高くありません ヒープの先頭を削除 この要素は人生における TopK 問題を解決することができます。

1先頭データを直接削除してはどうでしょうか?

  ヒープの最上位要素を削除した後、左側の子の方が大きい場合、配列に穴が存在する可能性があり、完全なバイナリ ツリーではありません。



  ここでは、より良い方法を学びます。最初にヒープの先頭要素と末尾要素を交換し、次にヒープのサイズを交換します (最後の要素にはアクセスできません)。その後、復元ヒープを下方に調整します。

void HeapPop(HP* php)
{
    
    
	assert(php);
	assert(!HeapEmpty(php));
	Swap(&php->a[0], &php->a[php->size-1]);
	php->size--;
	AdjustDown(php->a, php->size, 0);
}

2 アンドゥヒープを下に調整します

(1)アニメーション理解

(2) コードの実装

  ここで大きな山を復元するには、まず左と右の子のうち大きい方を見つけて、それから下に反復処理します。同時に、詳細に注意してください。最初にデフォルトで左の子が大きいことを設定する必要があります。右の子が大きいためです。存在しない可能性があり、子を直接設定するとオーバーフローする可能性があります。

void AdjustDown(HPDataType* a, int size, int parent)
{
    
    
		//默认左孩子大,因为右孩子可能不存在就越界了
	int child = parent * 2 + 1;

	while (child < size)
	{
    
    
			//找到左右孩子中大的那一个
		if (child + 1 < size && a[child] < a[child + 1])
			child++;	//注意防止child+1>size

		if (a[parent] < a[child])
		{
    
    
			Swap(&a[parent], &a[child]);
			parent = child;
			child = child * 2 + 1;
		}
		else
		{
    
    
			//除了调整元素外其他元素构成堆
			break;
		}
	}
}




4. ヒープソート

1 なぜ大きなヒープを昇順で構築し、小さなヒープを降順で構築するのか考えてみてください。

  小さなヒープを構築して配列を昇順に並べたい場合、小さなヒープの先頭の要素が最小値となり、ヒープの先頭の位置がソート後の最小値の位置にもなるため、これは、ヒープの最上位要素を移動する必要がないことを意味します。その場合、ヒープは最後のビットから再構築し、小さなヒープを構築し、最小値を見つけ、配列全体が配置されるまで次のビットから最小値を見つける必要があります。一見すると何の問題もないように見えますが、大きなヒープを構築する場合と異なる点は、小さなヒープを構築する場合、最も多くの値を選択するために毎回次のビットからヒープを再構築する必要があることです。演算はO ( nlogn ) O(nlogn)ですO ( n l o g n )大規模なヒープを構築する場合と比較して、時間計算量はO ( logn ) O(logn)O ( log n )は非常にいっぱいであるため、小さなヒープを昇順で構築できますが、大きなヒープの構築ほど高速ではないため、大きなヒープを構築することを選択します
  小さなヒープを降順で構築する場合も同様です。

2 ヒープビルド

(1) 上方修正方法O ( nlogn ) O(nlogn)O ( nログn ) _ _

  ヒープの挿入プロセスを模倣する上方調整方法の時間計算量は nlogn です。ここでは、ヒープにhhがあると仮定します。h 、 k番目k はk − 1 k-1k1回目、その後kk 回目k層に必要な調整数( k − 1 ) ∗ 2 k − 1 (k-1)*2^{k-1}( k1 )2k1 a n = ( n − 1 ) ∗ 2 n − 1 a_n=(n-1)*2^{n-1} ある=( n1 )2n 1


$S_n=2^0*0+2^1*1+……+2^{n-1}*(n-1)$ $2S_n=2^1*0+2^2*1+…+ 2 ^n*(n-1)$ は転位によって減算されます: $S_n=2^n*(n-1)-(2^1+2^2+……+2^{n-1})=2 ^ n*(n-2)+2))$ および $N=2^n-1$ のすべての計算量は $O(nlogn)$ です
void HeapSort(int* a, int size)
{
    
    
	for (int i = 1; i < size; i++)
	{
    
    
		AdjustUp(a, i);
	}
	int end = size - 1;
	while(end>0)
	{
    
    
		Swap(&a[end], &a[0]);
		AdjustDown(a, end, 0);
		--end;
	}
}



(2)上方調整法の複雑さは、第 2 レベルの結論S n = ( A n + B ) ∗ qn − B ) S_n=(An+B)*q^nB) を証明します。S=(あん_+B qnB

  上記では、n 番目の層an = ( n − 1 ) ∗ 2 n − 1 a_n=(n-1)*2^{n-1} であることがすでにわかっています。ある=( n1 )2n 1であり、高校では二次的な結論があります。この数列の最初の n 項目の合計はS n = ( A n + B ) ∗ qn − B ) S_n=(An+B)*q^ でなければ注B)S=(あん_+B qnB )、今回はS 1 = a 1 = 0 、 S 2 = 1 ∗ 2 1 = 2 S_1=a_1=0、 S_2=1*2^1=2 を見つけることができます。S1=ある1=0 S2=121=2
S 1 = ( A + B ) ∗ 2 1 − B = 2 A + B = 0 S_1=(A+B)*2^1-B=2A+B=0S1=( A+B 21B=2A_ _+B=0
S 2 = ( 2 A + B ) ∗ 2 2 − B = 8 A + 3 B = 2 S_2=(2A+B)*2^2-B=8A+3B=2S2=( 2A _+B 22B=8A _+3B_ _=22
つの式を同時に行うと、 A = 1 、 B = − 2 となるため、 S n = ( n − 2 ) ∗ 2 n + 2 A=1、 B=-2 となるため、 S_n=(n-2)*2^n +2=1 B=2 したがってS=( n2 )2n+2
ここでの時間計算量は明らかに O(nlogn) です。 ここでの時間計算量は明らかに O(nlogn) ですここで、時間計算量は明らかO ( n log n )です。


(3) 下方修正方法O ( N ) O(N)O ( N )

  下方調整方法はヒープの逆数の非リーフ ノードから開始され、時間計算量はO ( nlogn ) O(nlogn)未満である必要があります。O ( n log n ) . _ _ ここではヒープにhhh

S n = 2 0 ∗ ( h − 1 ) + 2 1 ∗ ( h − 2 ) + … … + 2 h − 2 ∗ 1 S_n=2^0*(h-1)+2^1*(h -2)+...+2^{h-2}*1S=20( h1 )+21( h2 )+……+2h 21
2 S n = 2 1 ∗ ( h − 1 ) + 2 2 ∗ ( h − 2 ) + … … + 2 h − 1 ∗ 1 2S_n=2^1*(h-1) + 2^2*(h -2)+……+2^{h-1}*12S _=21( h1 )+22( h2 )+……+2h 11
転位減算:
S n = 2 1 + 2 2 + 2 ( h − 2 ) + 2 h − 1 − 1 + h = 2 h − 1 + 1 − h = 2 h − h S_n=2^1+ 2^ 2+2^{(h-2)}+2^{h-1}-1+h=2^h-1+1-h=2^hhS=21+22+2( h 2 )+2h 11+h=2h1+1h=2hh
および 2 h − 1 = N の場合、時間計算量は O ( N ) および 2^h-1=N の場合、時間計算量は O(N)もう2つh1=Nの場合、時間計算量はO ( N )

void HeapSort(int* a, int size)
{
    
    
		//从倒数第一个叶子结点开始向下调整堆
	for (int i = (size - 1 - 1) / 2; i >= 0; i--)
	{
    
    
		AdjustDown(a, size, i);
	}
		//每次把堆首尾交换,大的数字在最后面,在调回大堆
	int end = size - 1;
	while(end>0)
	{
    
    
		Swap(&a[end], &a[0]);
		AdjustDown(a, end, 0);
		--end;
	}
}

3 ソートのためにヒープの削除を模倣する

  ここではヒープの削除を真似て、ヒープの最初と最後の要素を変更し、番号を順番に並べてヒープを調整するだけで、ヒープ調整にコードが付けられています。

要約する

  ヒープの内容はこれで終わりです。ヒープの上方調整の時間計算量を求める第 2 レベルの結論が役立つことを願っています。次のブログでお会いできることを楽しみにしています。

おすすめ

転載: blog.csdn.net/Front123456/article/details/129889633