詳細なヒープソート+TOP-K問題

最後のリンク: ヒープ+ヒープソートの実装



コンテンツ

前の記事の分析ヒープソート

アイデア

時間と空間の複雑さ

最適化されたヒープソート 

ヒープを構築する

分析する

ダイアグラム

時間計算量

コード

 ヒープビルドダウンを調整する

 分析する

ダイアグラム

時間計算量 

コード

要約する 

選別 

思考分析

アイデアの並べ替え

降順で小さなヒープを構築していることの証明

コード

小さなヒープは大きなヒープになります。

TOP-K問題

TOP-Kの問題とは何ですか?

アイデア

時間計算量

スペースの複雑さ

コード

ランダムデータテスト 

ソーターを追加する


前の記事の分析ヒープソート

前回の記事では、バイナリツリーノートを紹介し、最後に単純なヒープソートを実装しました。

アイデア

最初にヒープを作成し、ヒープの上部のプロパティを使用します。最大または最小を選択します

ヒープの一番上の要素を削除するプロパティを使用します。次に大きいまたは次に小さい要素を見つけます

配列を並べ替える

+

時間と空間の複雑さ

挿入と削除の時間計算量はO(logN)であり、最悪の場合は二分木の高さです。

順番に挿入および削除されるため、ノードの数に関連しているため、並べ替えアルゴリズムの時間計算量はO(N * logN)です。

ヒープを最初に作成する必要があり、配列データが挿入され、サイズが配列のサイズに関連しているため、スペースの複雑さはO(N)です。

void HeapSort(int* a,int size)
{
	HP hp;
	HeapInit(&hp);

    //时间复杂度O(N*logN)
	for (int i = 0;i<size;i++)
	{
		HeapPush(&hp,a[i]);
	}
	HeapPrint(&hp);
	int j = 0;

    //时间复杂度O(N*logN)
	while (!HeapEmpty(&hp))
	{
		a[j] = HeapTop(&hp);
		j++;
		HeapPop(&hp);
	}
	HeapDestroy(&hp);
}


 最適化されたヒープソート 

最適化の目標:時間計算量O(N * logN)

                  スペースの複雑さO(1)

以前は、最初にヒープを作成してから配列を挿入していましたが、今回は配列内に直接ヒープを構築して配列をヒープにしたため、ヒープソートアルゴリズムのスペースの複雑さはO(1)になりました。

アレイにヒープを構築するには、次の2つの方法があります。ヒープを調整する

                                           ヒープビルドダウンを調整する

ヒープを構築する

説明を簡単にするために、ここではヒープの上方調整を示します。例として小さなヒープを作成します。

分析する

配列サイズに直接含まれるのは、配列要素の数です。

上向きに調整するときは、開始ノードで終わるツリーがヒープでなければならないことを確認してください

最初の数字はヒープの一番上で、2番目の数字から始まり、上に向かって調整されます。

前から後ろへ、上向きに順番に調整します


ダイアグラム


時間計算量

コード

//向上调整
//建小堆为例
void Up(HPDataType* a,size_t child)
{
	size_t parent = (child - 1) / 2;
	while (child>0)
	{
		if (a[child] < a[parent])
		{
			swap(&a[child], &a[parent]);
			child = parent;
			parent = (child - 1) / 2;
		}
		else
		{
			break;
		}
	}
}

void HeapSort(int* a,int size)
{
	//向上调整建堆
	//分析后是件复杂度为O(N*logN)
	for (int i = 1;i<size;i++)
	{
		Up(a,i);
	}
}

 ヒープビルドダウンを調整する

 分析する

下向きに調整するときは、開始ノードがヒープの最上部であるツリーの左側のサブツリーと右側のサブツリーがヒープであることを確認してください。

ヒープの上部として、最初の非リーフノードから下向きに調整します

後ろから前に調整

ダイアグラム

時間計算量 

コード

//建小堆为例
void Down(HPDataType* a, size_t parent, size_t size)
{
	size_t child = parent * 2 + 1;
	while (child < size)
	{
		if (child + 1 <size && a[child+1] < a[child])
		{
			child++;
		}
		if (a[child] < a[parent])
		{
			swap(&a[child],&a[parent]);
			parent = child;
			child = parent * 2 + 1;
		}
		else
		{
			break;
		}
	}
}

void HeapSort(int* a,int size)
{
	//向下调整建堆
	//分析后是件复杂度为O(N+logN)=O(N)

	for (int i = (size-1-1)/2; i>=0; i--)
	{
		Down(a,i, size);
	}
}

要約する 

ヒープを調整する
時間計算量はO(N * logN)です。

ヒープを構築するための下方調整
、時間計算量はO(N)です

したがって、ヒープを構築するために下方調整を使用する方が効率的です。

選別 

昇順でヒープを構築する

降順で小さなヒープを構築します


思考分析

1.注:配列のサイズを上下に変更してヒープにします

           ヒープの最上位要素の削除、ヒープへの挿入などの機能インターフェイスは作成されません。

           したがって、HeapTopを配列に追加し、HeapPopを使用してヒープの先頭を削除することはできません。

2.一部の友人は、これら2つの関数インターフェースを構築して、それらを再び使用できると言うかもしれません。

いいえ、これを行うと、必然的に新しい配列を開いて、ヒープの最上位要素を配置します。

スペースの複雑さはO(N)になります

最初に構築されたヒープは、ヒープの先頭と末尾で交換および削除されます

最適化の目標を達成していません

3.次に、スペースの複雑さをO(1)にするには、元の配列で並べ替える必要があります


アイデアの並べ替え

1.ヒープのトップノードとエンドノードの要素を交換します

2.ヒープの最初のn-1ノードについて、ヒープの上部から下向きに調整します

3.この時点で、ヒープの一番上の要素は最大または最小の要素です

4.ヒープの一番上の要素をn-1番目の要素と交換します

5.上記のプロセスを繰り返して、昇順または降順を完了します

注:エンドノードの添え字が更新されます


降順で小さなヒープを構築していることの証明

 同じ分析は、昇順で大きなヒープを構築することによって実行できます。

結論:昇順で大きなヒープを構築し、降順で小さなヒープを構築します


コード

最初にヒープを調整します(高効率)

iは最初の非リーフノードの添え字です

最後のデータ添え字の終わりを記録します

end = 1の場合、スワップを終了して調整します

注:関数パラメーターを調整します

aは配列の開始アドレスです

parentは、親ノードの添え字です

サイズは調整する要素の数です

void Down(HPDataType* a, size_t parent, size_t size);

次のコードでは、endの意味に注意してください

before whileは、最後の要素の添え字を表します

whileは、調整する要素の数を表します

void HeapSort(int* a,int size)
{
	//升序建大堆
	//降序建小堆
	for (int i = (size-1-1)/2; i>=0; i--)
	{
		Down(a,i, size);
	}
	//最后一个数据的下标
	size_t end = size - 1;
	while (end>0)
	{
		swap(&a[0],&a[end]);
		Down(a,0,end);
		end--;
	}
}

これは、ヒープソートを実行する方法です。

昇順または降順を決定するには、大きなヒープまたは小さなヒープを作成します

小さなヒープは大きなヒープになります。

ヒープを構築するときに、大なり記号または小なり記号を変更できます。

子と子+1、子と親の比較記号


TOP-K問題

TOP-Kの問題とは何ですか?

つまり、データの組み合わせで上位K個の最大要素または最小要素を見つけるには、一般に、データ量が比較的多くなります。
例:プロのトップ10、世界のトップ500、豊富なリスト、ゲームのトップ100のアクティブなプレーヤーなど。


Top-Kの問題について、私が考えることができる最も単純で最も直接的な方法は、ソートすることです。

データ量が非常に多い場合(数十G)、ソートは望ましくなく、メモリは非常に大きくなり、効率は非常に低くなります。

最良の方法は、ヒープソートを使用して解決することです

アイデア

1.データセットの最初のK個の要素を使用して、ヒープの最初のk個の最大要素を作成し、次に
    小さなヒープの
    最初のk個の最小要素を

作成し、次に大きなヒープを作成します。2.残りのNK要素を使用して比較します。ヒープの上位要素が順番に

    ヒープが小さい場合、ヒープの上部よりも大きい要素がヒープの上部に置き換わります。

    ヒープの構造を確認するために調整します

    大きなヒープの場合は、ヒープの上部をヒープの上部よりも小さい要素に置き換えます

    ヒープの構造を確認するために調整します

順番に比較した後、ヒープはすべてのデータの中で最大または最小の上位K要素です

一度トラバースするだけ


時間計算量

ヒープはKとして設定され、最悪の場合に残っているNKの数を調整する必要があります

調整回数はlogK*(NK)回です

O(K + logK *(NK))

Kのサイズは不確実であり、省略できません

スペースの複雑さ

ヒープを構築するためにKスペースを開く必要があるだけです

わかった)

コード

//TOP-K问题
void PrintTopK(int* a, int n, int k)
{
	// 1. 建堆--用a中前k个元素建堆
	int* kHeap = (int*)malloc(sizeof(int)*k);
	if (kHeap == NULL)
	{
		printf("malloc fail\n");
		exit(-1);
	}

	//将前k个数插入数组kHeap中
	for (int i = 0;i<k;i++)
	{
		kHeap[i] = a[i];
	}

	//在数组里面建小堆
	for (int i = (k - 1 - 1) / 2; i >= 0; i--)
	{
		Down(a, i, k);
	}

	// 2. 将剩余n-k个元素依次与堆顶元素交换,不满则则替换
	for (int i = k;i<n;i++)
	{
		if (a[i]>kHeap[0])
		{
			kHeap[0] = a[i];
			Down(kHeap,0,k);
		}
	}

	// 3. 打印最大或最小的前k个
	for (int j = 0;j<k;j++)
	{
		printf("%d ",kHeap[j]);
	}
	printf("\n");


	free(kHeap);
}

ランダムデータテスト 

100000以内の乱数を生成し、100000内の10個の乱数位置を100000より大きい数値に変換します

10,000個の数字から最大の10個の数字を見つける

コードを実行する

void TestTopk()
{
	int n = 10000;
	int* a = (int*)malloc(sizeof(int)*n);
	srand(time(0));
	for (size_t i = 0; i < n; ++i)
	{
		a[i] = rand() % 1000000;
	}
	a[5] = 1000000 + 1;
	a[1231] = 1000000 + 2;
	a[531] = 1000000 + 3;
	a[5121] = 1000000 + 4;
	a[115] = 1000000 + 5;
	a[2335] = 1000000 + 6;
	a[9999] = 1000000 + 7;
	a[76] = 1000000 + 8;
	a[423] = 1000000 + 9;
	a[3144] = 1000000 + 10;
	PrintTopK(a, n, 10);
}


int main()
{
	TestTopk();
	return 0;
}

演算結果:

最大10個の番号を取得できますが、順序付けされていません

ソーターを追加する 

//TOP-K问题
void PrintTopK(int* a, int n, int k)
{
	// 1. 建堆--用a中前k个元素建堆
	int* kHeap = (int*)malloc(sizeof(int)*k);
	if (kHeap == NULL)
	{
		printf("malloc fail\n");
		exit(-1);
	}

	//将前k个数插入数组kHeap中
	for (int i = 0;i<k;i++)
	{
		kHeap[i] = a[i];
	}

	//在数组里面建小堆
	for (int i = (k - 1 - 1) / 2; i >= 0; i--)
	{
		Down(a, i, k);
	}

	// 2. 将剩余n-k个元素依次与堆顶元素交换,不满则则替换
	for (int i = k;i<n;i++)
	{
		if (a[i]>kHeap[0])
		{
			kHeap[0] = a[i];
			Down(kHeap,0,k);
		}
	}

	// 3. 排序
	//最后一个数据的下标
	size_t end = k - 1;
	while (end>0)
	{
		swap(&kHeap[0], &kHeap[end]);
		Down(kHeap, 0, end);
		end--;
	}


	// 4. 打印排序后的前k个
	for (int j = 0;j<k;j++)
	{
		printf("%d ",kHeap[j]);
	}
	printf("\n");


	free(kHeap);
}

運転結果


ヒープソートとTOP-Kの問題に関するメモはここにあります。すべてのパートナーは、コメント領域でコメントを交換できます。! 

おすすめ

転載: blog.csdn.net/weixin_53316121/article/details/124065512