L'algorithme d'ajustement vers le bas du tas, l'algorithme d'ajustement vers le haut du tas, l'implémentation du tas, le problème de Topk

teneur

pile

1. Le concept de tas

2. La nature du tas

3. Classification des tas

Deuxièmement, l'algorithme d'ajustement à la baisse du tas

Troisièmement, la création du tas 

Quatrièmement, l'algorithme d'ajustement à la hausse du tas

Cinq, la réalisation du tas

1. Initialisation du tas

2. Destruction du tas

3. Insertion de tas

4. Suppression du tas

5. Obtenez les données en haut du tas

6. Le nombre de données dans le tas

7. Jugement vide du tas

8. Impression du tas

9. Testez un petit exemple

 6. Topk problème

top de test


 

pile

1. Le concept de tas

Heap : S'il existe un ensemble de clés K={k0,k1,k2,…,kn-1}, stocker tous ses éléments dans un tableau unidimensionnel dans l'ordre d'un arbre binaire complet, et satisfaire ki<= k2i +1 et ki<=k2i+2 (ou satisfont ki>=k2i+1 et ki>=k2i+2), où i=0,1,2,…, alors l'ensemble est appelé un tas.

2. La nature du tas

①La valeur d'un nœud dans le tas n'est toujours ni supérieure ni inférieure à la valeur de son nœud parent ;

②Le tas est toujours un arbre binaire complet.

3. Classification des tas

Gros tas : le tas avec le plus grand nœud racine est appelé tas max ou grand tas racine, et la valeur du nœud parent est toujours supérieure à celle de l'enfant.

Petit tas : le tas avec le plus petit nœud racine est appelé le tas min ou petit tas racine, et la valeur du nœud parent est toujours inférieure à celle de l'enfant.

Deuxièmement, l'algorithme d'ajustement à la baisse du tas

Donnons maintenant un tableau, logiquement considéré comme un arbre binaire complet. Nous pouvons l'ajuster en un petit tas en ajustant l'algorithme vers le bas à partir du nœud racine. L' algorithme d'ajustement vers le bas a une prémisse : les sous-arbres gauche et droit doivent être un tas afin de s'ajuster.

L' idée de base de l' algorithme d'ajustement à la baisse : ( prenez l'exemple de la construction d'un petit tas )

①En partant du nœud racine, sélectionnez celui avec la plus petite valeur dans les nœuds enfants gauche et droit

② Laissez le père comparer avec les enfants plus jeunes

Si le père est plus grand que l'enfant, alors échangez

Si le père est inférieur à l'enfant, pas d'échange

③Condition de fin

1. Père <= petit enfant puis arrêt

2. Ajustez au nœud feuille (le nœud feuille est caractérisé par aucun enfant gauche, c'est-à-dire que l'indice du tableau est hors plage et qu'il n'existe pas)

code afficher comme ci-dessous:

void Swap(int* x,int* y)
{
int*tmp=*x;
*x=*y;
*y=tmp;
}
void AdjustDown(int* a, int n, int parent) //注意右孩子可能不存在
{
	//child记录左右孩子中值较小的孩子的下标
	int child = 2 * parent + 1; //先默认其左孩子的值较小
	while(child<n)
	{ 
		if (child + 1 < n && a[child + 1] < a[child])//右孩子存在并且右孩子比左孩子还小
		{
			child++;//较小的孩子改为右孩子
		}
		if (a[child < a[parent]])
		{
			Swap(&a[child], & a[parent]);
			parent = child;
			child = parent * 2 + 1;
		}
		else
		{
			break;
		}
	}

}

Le processus détaillé de l'algorithme d'ajustement à la baisse : 

Troisièmement, la création du tas 

Comment redimensionner un arbre arbitraire dans un tas?

Ici, nous pouvons utiliser l'algorithme d'ajustement vers le bas du tas que nous venons d'écrire. Nous savons que pour satisfaire le tas, les sous-arbres gauche et droit doivent être de grands tas ou de petits tas. Nous pouvons utiliser cette idée pour remonter du bas vers le haut, à partir du premier À partir d'un nœud non-feuille, via le contrôle de l'indice du tableau, utilisez-le comme racine pour ajuster vers le bas, puis montez à tour de rôle, jusqu'à ce que le chemin actuel soit ajusté à un tas grand ou petit qui rencontre les conditions.

code afficher comme ci-dessous:

	//建堆   n为数组个数  n-1为最后一个元素下标 (n-1-1)/2找到第一个非叶子节点的父亲
	for (int i = (n - 1 - 1) / 2; i >= 0; i--)
	{
		AdjustDown(hp->a, hp->size, i);
	}

La complexité temporelle de la construction d'un tas est O(N)

Parce que le tas est un arbre binaire complet, et que l'arbre binaire complet est également un arbre binaire complet, afin de simplifier l'utilisation de l'arbre binaire complet à prouver (la complexité temporelle est à l'origine une approximation, et quelques nœuds supplémentaires ne le font pas affecter le résultat final):

 

Quatrièmement, l'algorithme d'ajustement à la hausse du tas

Lorsque nous avons déjà un tas, nous devons insérer des données à la fin du tas, puis l'ajuster pour qu'il conserve toujours la structure du tas, ici nous devons utiliser l'algorithme d'ajustement vers le haut du tas

L' idée de base de l'algorithme d'ajustement vers le haut du tas : ( prenez l'exemple de la construction d'un petit tas )

①Comparer les données à insérer avec ses données de nœud parent

② Si les données du nœud enfant sont inférieures aux données du nœud parent, échangez

Si les données du nœud enfant sont supérieures aux données du nœud parent, elles ne seront pas échangées, aucun ajustement n'est nécessaire et la structure du tas est satisfaite.

code afficher comme ci-dessous:

void Swap(int* x, int* y)
{
	int* tmp = *x;
	*x = *y;
	*y = tmp;
}
void AdjustUp(int * a, int child)
{
	int parent = (child - 1) / 2;
	while (child>0)
	{
		if (a[parent] > a[child])
		{
			Swap(&a[child],&a[parent]);
			child = parent;
			parent = (child - 1) / 2;
		}
		else 
		{
			break;
		}
	}
}

Algorithme d'ajustement détaillé à la hausse :

 Maintenant que nous connaissons l'algorithme d'ajustement du tas vers le haut et vers le bas, nous pouvons commencer à implémenter la structure du tas

Cinq, la réalisation du tas

Nous avons besoin d'une structure pour représenter le tas

typedef int HPDataType;//方便后续修改数据类型
typedef struct Heap
{
	HPDataType* a;//用于动态开辟的指针
	int size;
	int capacity;
}Heap;

1. Initialisation du tas

Nous définissons toujours la taille et la capacité sur 0, et le tableau a est NULL.

void HeapInit(Heap* hp)
{
	assert(hp);
	hp->capacity = hp->size = 0;
	hp->a = NULL;
}

2. Destruction du tas

Tableaux utilisés pour le développement dynamique, afin d'éviter les fuites de mémoire, il faut les libérer et les retourner au système d'exploitation

void HeapDestroy(Heap* hp)
{
	assert(hp);

	free(hp->a);//释放动态开辟的数组
	hp->a = NULL;//及时置空
	hp->capacity =hp->size = 0;//元素个数置0
}

3. Insertion de tas

Une fois que nous avons demandé au tas d'insérer des données, sa structure est toujours un tas, nous devons donc l'ajuster vers le haut. Au début, la taille et la capacité sont toutes deux de 0, nous devons donc d'abord étendre la capacité, puis mettre les données dans

void HeapPush(Heap* hp,HPDataType x)
{
	assert(hp);
	if (hp->capacity == hp->size)
	{
		size_t newcapacity = hp->capacity == 0 ? 4 : hp->capacity * 2;
		HPDataType* tmp = (HPDataType*)realloc(hp->a, sizeof(HPDataType) * newcapacity);
		if (tmp == NULL)
		{
			printf("realloc fail\n");
				exit(-1);
		}
		hp->a = tmp;
		hp->capacity = newcapacity;
	}
	hp->a[hp->size] = x;
	hp->size++;
	AdjustUp(hp->a, hp->size - 1);
}

4. Suppression du tas

La suppression du tas consiste à supprimer les données en haut du tas, à échanger les données en haut du tas avec les dernières données, puis à supprimer les dernières données, et les données restantes peuvent être ajustées à la baisse. le tableau, nous n'avons besoin que de la taille - C'est comme supprimer les données

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

5. Obtenez les données en haut du tas

Nous pouvons directement utiliser l'indice du tableau pour renvoyer les données

//取堆顶的数据
HPDataType HeapTop(Heap* hp)
{
	assert(hp);
	assert(!HeapEmpty(hp));
	return hp->a[0];
}

6. Le nombre de données dans le tas

Il suffit de renvoyer la taille

//堆的总数据个数
int HeapSize(Heap* hp)
{
	assert(hp);

	return hp->size;
}

7. Jugement vide du tas

Déterminer si le nombre de données dans le tas est 0

//堆的判空
bool HeapEmpty(Heap* hp)
{
	assert(hp);

	return hp->size == 0;//0为假   非0为真
}

8. Impression du tas

void HeapPrint(Heap* hp)
{
	assert(hp);
	for(int i = 0; i < hp->size; i++)
	{
		printf("%d ",hp->a[i]);
	}
	printf("\n");
}

9. Testez un petit exemple

#include"Heap.h"

int main()
{
	int arr[] = { 27,15,19,18,28,34,65,49,25,37 };
	Heap hp;
	HeapInit(&hp);
	int i = 0;
	for (i = 0; i < sizeof(arr) / sizeof(arr[0]); i++)
	{
		HeapPush(&hp, arr[i]);
	}
	HeapPrint1(&hp);
	HeapPop(&hp);
	HeapPop(&hp);
	HeapPop(&hp);
	HeapPop(&hp);
    HeapPrint1(&hp);
	printf("top=%d\n", HeapTop(&hp));
	printf("size=%d\n",HeapSize(&hp));
	HeapDestroy(&hp);
	return 0;

}

Résultats de test

 6. Topk problème

Autrement dit, pour trouver les K éléments les plus grands ou les plus petits éléments de la combinaison de données, en général, la quantité de données est relativement importante.

Par exemple : top 10 professionnel, top 500 mondial, liste riche, 100 meilleurs joueurs actifs dans le jeu, etc.

Pour le problème Top-K, le moyen le plus simple et le plus direct auquel je puisse penser est le tri, mais : si la quantité de données est très importante, le tri n'est pas souhaitable (peut-être que les données ne peuvent pas être chargées en mémoire toutes en même temps). Le meilleur moyen est d'utiliser le tas pour résoudre le problème, l'idée de base est la suivante :

1. Construire un tas avec les premiers éléments K de l'ensemble de données

Les k premiers éléments les plus grands, puis construisent un petit tas

Les k premiers éléments les plus petits, puis forment un grand tas

2. Utilisez les éléments NK restants pour comparer avec les éléments supérieurs du tas à tour de rôle et remplacez les éléments supérieurs du tas s'ils ne sont pas satisfaits

Après avoir comparé les éléments NK restants avec les éléments supérieurs du tas à leur tour

3. Les K éléments restants dans le tas sont les K premiers éléments les plus petits ou les plus grands requis

code afficher comme ci-dessous: 

//topk问题
void PrintTopK(int* a, int n, int k) 
{
	// 1. 建堆--用a中前k个元素建堆(依次一个一个插入)
	Heap hp;
	HeapInit(&hp);
	for(int i=0;i<k;i++)
	{
		HeapPush(&hp,a[i]);
	}

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

	
}

top de test


void TestTopk()
{
	int n = 10000;
	int* a = (int*)malloc(sizeof(int) * n);
	srand((unsigned int)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;
}

Ici, nous utilisons la méthode de génération de nombres aléatoires, générons d'abord aléatoirement 10 000 nombres inférieurs à 100 000, puis construisons aléatoirement 10 nombres supérieurs à 100 000, nous pouvons utiliser la fonction Topk pour donner ces 10 nombres supérieurs à 100 000 pour le savoir !

Résultats de test:

 Merci à tous d'avoir regardé !

 

 

Je suppose que tu aimes

Origine blog.csdn.net/weixin_57675461/article/details/121387313
conseillé
Classement