< structure de données > implémentation du tas

teneur

1. Introduction

        notion de tas

        structure de tas

2. Mise en œuvre du tas

      2.1 . Préparatifs

         Créer une structure de tas

         initialiser le tas

         tas d'impression

         Destruction du tas

      2.2, ajustement du tas

         échange de tas

         Algorithme d'ajustement du tas

         Algorithme d'ajustement vers le bas du tas

      2.3, fonctions de base

         Insertion de tas

         suppression de tas

         Vide de tas

         Obtenir le nombre d'éléments dans le tas

         Obtenir l'élément supérieur du tas

3. Code total

        Fichier tas.h

        Fichier tas.c

        Fichier test.c


1. Introduction

  • Utilisez une image pour passer en revue les points de connaissance expliqués à la fin du dernier article de blog :

Grâce à l'explication du billet de blog précédent, nous savons que les arbres binaires complets et les arbres binaires complets peuvent être stockés par des tableaux, et la relation parent-enfant entre eux peut être représentée par des indices. Il est souligné ici que la structure physique est en fait stockée dans la mémoire, qui est physiquement un tableau, mais logiquement, elle doit être vue comme un arbre binaire. C'est comme s'il n'y avait pas de vache dans le lait, pas de minéraux dans l'eau minérale, pas de femme dans le gâteau de femme ~

  • Puisque les arbres binaires complets et les arbres binaires complets conviennent au stockage dans des tableaux, qu'en est-il des arbres binaires ordinaires ? Compréhension du dessin :

Les arbres binaires ordinaires ne conviennent pas au stockage dans des tableaux, car il peut y avoir beaucoup d'espace perdu. Un arbre binaire complet est plus adapté au stockage dans une structure séquentielle. Parce que le taux d'utilisation de l'espace est élevé, il n'y aura pas de gaspillage. En réalité, nous stockons généralement le tas (une sorte d'arbre binaire) à l'aide d'un tableau de structures séquentielles. Il convient de noter que le tas ici et le tas dans l'espace d'adressage du processus virtuel du système d'exploitation sont deux choses différentes. L'un est la structure des données et l'autre est le système de gestion dans le système d'exploitation.Une région de la mémoire est segmentée. S'il ne s'agit pas d'un arbre binaire complet ou d'un arbre binaire complet, il est recommandé d'utiliser le stockage en chaîne, ce qui a été expliqué dans le billet de blog précédent et ne sera pas répété.

Le tas est un arbre binaire complet, qui peut être stocké dans un tableau. Ensuite, je vais l'expliquer en détail :

notion de tas

Comme on peut le voir ci-dessus, le tas est un arbre binaire complet et tous ses éléments sont stockés dans un tableau unidimensionnel selon l'ordre de l'arbre binaire complet. Il existe deux types de tas : le petit tas racine et le gros tas racine

  1. Petit tas : la valeur de chaque nœud parent est inférieure ou égale à la valeur de son nœud enfant correspondant, et la valeur du nœud racine est la plus petite.
  2. Grand tas : la valeur de chaque nœud parent est supérieure ou égale à la valeur de son nœud enfant correspondant, et la valeur du nœud racine est la plus grande.
  • Propriétés du tas :
  1. La valeur d'un nœud dans le tas n'est toujours ni supérieure ni inférieure à la valeur de son parent.
  2. Un tas est toujours un arbre binaire complet.

structure de tas

De la structure physique, nous pouvons connaître les deux points suivants :

  1. commandé doit être un tas
  2. non ordonné peut être un tas

2. Mise en œuvre du tas

2.1. Préparatifs

Créer une structure de tas

  • Idées :

Il ressort de ce qui précède que la structure de base du tas est un tableau.Lors de la création d'une structure de tas, elle peut être ouverte dynamiquement comme auparavant.Le processus de fonctionnement est également similaire et le code est directement ajouté. Mais prenons d'abord le petit tas de racines comme exemple.

  • Fichier tas.h :
//创建堆结构
typedef int HPDataType; //堆中存储数据的类型
typedef struct Heap
{
	HPDataType* a; //用于存储数据
	size_t size; //记录堆中有效元素个数
	size_t capacity; //记录堆的容量
}HP;

initialiser le tas

  • Idées :

Initialisez le tas, puis le pointeur de structure passé ne peut pas être vide, tout d'abord, il doit être affirmé. Le reste des opérations est identique à la table de séquence précédente et à l'initialisation de la pile.

  • Fichier tas.h :
//初始化堆
void HeapInit(HP* php);
  • Fichier tas.c :
//初始化堆
void HeapInit(HP* php)
{
	assert(php);
	php->a = NULL;
	php->size = php->capacity = 0;
}

tas d'impression

  • Idées :

En fait, l'impression du tas est très simple. La structure physique du tas est un tableau. L'essence de l'impression du tas est toujours similaire à l'impression de la table de séquence précédente. Vous pouvez accéder aux indices et imprimer à tour de rôle.

  • Fichier tas.h :
//堆的打印
void HeapPrint(HP* php);
  • Fichier tas.c :
//堆的打印
void HeapPrint(HP* php)
{
	assert(php);
	for (size_t i = 0; i < php->size; i++)
	{
		printf("%d ", php->a[i]);
	}
	printf("\n");
}

Destruction du tas

  • Idées :

Pour une mémoire ouverte dynamiquement, elle doit être détruite même après utilisation.

  • Fichier tas.h :
//堆的销毁
void HeapDestroy(HP* php);
  • Fichier tas.c :
//堆的销毁
void HeapDestroy(HP* php)
{
	assert(php);
	free(php->a);//释放动态开辟的空间
	php->a = NULL; //置空
	php->size = php->capacity = 0; //置0
}

2.2, ajustement du tas

échange de tas

  • Idées :

L'échange du tas est relativement simple, ce n'est pas différent de ce qui était écrit avant, pensez à passer l'adresse.

  • Fichier tas.h :
//交换
void Swap(HPDataType* pa, HPDataType* pb);
  • Fichier tas.c :
//交换
void Swap(HPDataType* pa, HPDataType* pb)
{
	HPDataType tmp = *pa;
	*pa = *pb;
	*pb = tmp;
}

Algorithme d'ajustement du tas

  • Idées :

Cet algorithme est une fonction qui est encapsulée séparément pour s'assurer que le tas après l'insertion des données est toujours conforme à la nature du tas, tout comme le nombre 10 que nous voulons insérer plus tard, dessinons d'abord une image.

Afin de s'assurer qu'il s'agit toujours d'un petit tas racine après l'insertion du nombre 10, nous devons donc échanger 10 et 28, comparer la taille du nœud parent parent et du nœud enfant enfant tour à tour , lorsque le parent est plus petit que le nœud enfant, il reviendra, sinon il sera toujours échangé, jusqu'à la racine.

À partir de la règle précédente, parent = (child - 1) / 2 , nous manipulons un tableau, mais considérons-le comme un arbre binaire. Dessin pour illustrer le processus d'ajustement :

  • Fichier tas.c :
//向上调整算法
void AdjustUp(HPDataType* a, size_t child)
{
	size_t parent = (child - 1) / 2;
	while (child > 0)
	{
		//if (a[child] > a[parent]) //大根堆
		if (a[child] < a[parent]) //小根堆
		{
			Swap(&a[child], &a[parent]);
			child = parent;
			parent = (child - 1) / 2;
		}
		else
		{
			break;
		}
	}
}

Algorithme d'ajustement vers le bas du tas

  • Idées :

Utilisez d'abord une image pour démontrer :

À ce stade, nous pouvons voir que cet arbre binaire n'est pas conforme aux propriétés du tas dans son ensemble, mais les sous-arbres gauche et droit de sa racine satisfont tous les deux aux propriétés du tas. Ensuite, ajustez vers le bas pour vous assurer qu'il finit par être un tas. Juste une trilogie.

  1. Trouvez le plus jeune des enfants de gauche et de droite
  2. Comparer avec le père, si plus jeune que le père, échanger
  3. Continuez à ajuster vers le bas à partir de la position de l'enfant échangée

Le schéma de changement est le suivant :

  • Fichier tas.c :
//向下调整算法
void AdjustDown(HPDataType* a, size_t size, size_t root)
{
	int parent = root;
	int child = 2 * parent + 1;
	while (child < size)
	{
		//1、确保child的下标对应的值最小,即取左右孩子较小那个
		if (child + 1 < size && a[child + 1] < a[child]) //得确保右孩子存在
		{
			child++; //此时右孩子小
		}
		//2、如果孩子小于父亲则交换,并继续往下调整
		if (a[child] < a[parent])
		{
			Swap(&a[child], &a[parent]);
			parent = child;
			child = 2 * parent + 1;
		}
		else
		{
			break; //如果中途满足堆的性质,直接返回
		}
	}
}

2.3, fonctions de base

Insertion de tas

  • Avis:

L'insertion du tas n'est pas comme dans le tableau de séquence précédent, il peut être inséré en tête, inséré à n'importe quelle position, etc. , et la structure d'origine du tas ne peut pas être modifiée, l'insertion de la queue est donc la plus appropriée. , et vérifiez si elle est conforme à la nature du tas après l'insertion de la queue.

Par exemple, nous avons une chaîne de tableaux, qui sont stockés en fonction de la nature du petit tas racine. Maintenant, je veux insérer un nombre 10 à la fin du tableau, comme indiqué sur la figure :

  • Idées :

Cet arbre est un petit tas avant que le nombre 10 ne soit inséré, et ce n'est pas après l'insertion, ce qui change la nature du petit tas racine. Comme le nœud enfant 10 est plus petit que son nœud parent 28, que devons-nous faire ?

Tout d'abord, avant l'insertion, vous devez d'abord déterminer si la capacité du tas est encore suffisante pour insérer des données, vérifier d'abord s'il faut étendre la capacité, puis une fois l'expansion terminée. Nous pouvons constater que le 10 inséré n'affectera que la racine de lui-même vers la racine, c'est-à-dire l'ancêtre. Tant que ce chemin est conforme à la nature du tas, l'insertion est réussie.

Idée centrale : ajuster l'algorithme vers le haut. Quand on voit que le 10 inséré a 28 heures de plus que le père, on échange les nombres à ce moment, mais à ce moment 10 est plus petit que 18, on l'échange à nouveau, et finalement on trouve que 10 est plus petit que 15, et on l'échanger à nouveau. Bien sûr, c'est le pire des cas. Si les propriétés du tas sont satisfaites lors de la modification intermédiaire, il n'est pas nécessaire de modifier à nouveau, il suffit de revenir directement. C'est ce qu'on appelle l'algorithme d'ajustement vers le haut, et la fonction ci-dessus peut être directement appliquée.

  • Fichier tas.h :
//堆的插入
void HeapPush(HP* php, HPDataType x);
  • Fichier tas.c :
//堆的插入
void HeapPush(HP* php, HPDataType x)
{
	assert(php);
	//检测是否需要扩容
	if (php->size == php->capacity)
	{
		//扩容
		size_t newcapacity = php->capacity == 0 ? 4 : php->capacity * 2;
		HPDataType* tmp = (HPDataType*)realloc(php->a, sizeof(HPDataType) * newcapacity);
		if (tmp == NULL)
		{
			printf("realloc fail\n");
			exit(-1);
		}
		php->a = tmp;
		php->capacity = newcapacity;
	}
	php->a[php->size] = x;
	php->size++;
	//保持继续是堆,向上调整算法
	AdjustUp(php->a, php->size - 1);
}
  • Fichier test.c :
void TestHeap()
{
	HP hp;
	HeapInit(&hp);
	//插入数据
	HeapPush(&hp, 1);
	HeapPush(&hp, 5);
	HeapPush(&hp, 3);
	HeapPush(&hp, 0);
	HeapPush(&hp, 8);
	HeapPush(&hp, 9);
	//打印
	HeapPrint(&hp);
	//销毁
	HeapDestroy(&hp);
}
  • L'effet est le suivant :

conforme à la nature du tas.

suppression de tas

  • Comme le montre la figure :

  • Idées :

Dans l'insertion de tas ci-dessus, nous avons précisé qu'il s'agit toujours d'un tas après l'insertion, et la suppression du tas ici garantit également qu'il s'agit toujours d'un tas après la suppression. Remarque : la suppression du tas ici consiste à supprimer les données au sommet du tas. En prenant le petit tas racine comme exemple, supprimez les données en haut du tas, c'est-à-dire supprimez les données les plus petites, puis assurez-vous qu'il s'agit toujours d'un tas. L'idée que j'ai donnée est la suivante :

  • Tout d'abord , échangez les premières données avec les dernières données

Après l'échange, le tas à ce moment n'est pas conforme à sa nature, car les dernières données doivent être plus grandes que les premières données, et maintenant les dernières données atteignent le sommet du tas, ce n'est pas un tas, mais le sous-arbre gauche du nœud racine et du sous-arbre droit ne sont pas affectés, ils sont toujours des tas lorsqu'ils sont vus seuls

  • Ensuite , --size garantit que les données du haut du tas sont supprimées

Parce que les données en haut du tas ont atteint la fin du tas à ce moment, tout comme la table de séquence --size, pour s'assurer que les données effectives sont réduites de 1, c'est-à-dire pour assurer la suppression du haut du tas

  • Enfin , utilisez l'algorithme d'ajustement vers le bas pour vous assurer qu'il s'agit d'une structure de tas

Le schéma de changement est le suivant :

analyse de la complexité temporelle

L'échange des premières données et des dernières données est O(1), et la complexité temporelle de l'algorithme d'ajustement à la baisse est O(logN), car l'ajustement à la baisse consiste à ajuster les temps de hauteur, en fonction du nombre de nœuds N, on peut en déduire que la hauteur est d'environ logN

  • Fichier tas.h :
//堆的删除  删除堆顶的数据
void HeapPop(HP* php);
  • Fichier tas.c :
//堆的删除  删除堆顶的数据
void HeapPop(HP* php)
{
	assert(php);
	assert(php->size > 0);//确保size>0
	Swap(&php->a[0], &php->a[php->size - 1]); //交换堆头和堆尾
	php->size--;
	//向下调整,确保仍然是堆结构
	AdjustDown(php->a, php->size, 0);
}
  • Fichier test.c :
void TestHeap2()
{
	HP hp;
	HeapInit(&hp);
	//插入数据
	HeapPush(&hp, 1);
	HeapPush(&hp, 5);
	HeapPush(&hp, 3);
	HeapPush(&hp, 0);
	HeapPush(&hp, 8);
	HeapPush(&hp, 9);
	HeapPrint(&hp);//打印
	//删除堆顶数据
	HeapPop(&hp);
	HeapPrint(&hp);//打印
	//销毁
	HeapDestroy(&hp);
}
  • L'effet est le suivant :

Vide de tas

  • Idées :

Le jugement vide du tas est très simple, et il n'est pas différent de la table de séquence de pile précédente.Si la taille est 0, elle peut être renvoyée directement.

  • Fichier tas.h :
//堆的判空
bool HeapEmpty(HP* php);
  • Fichier tas.c :
//堆的判空
bool HeapEmpty(HP* php)
{
	assert(php);
	return php->size == 0; //size为0即为空
}

Obtenir le nombre d'éléments dans le tas

  • Idées :

Renvoyez simplement la taille directement.

  • Fichier tas.h :
//堆的元素个数
size_t HeapSize(HP* php);
  • Fichier tas.c :
//堆的元素个数
size_t HeapSize(HP* php)
{
	assert(php);
	return php->size;
}

Obtenir l'élément supérieur du tas

  • Idées :

Retournez simplement au sommet du tas. La prémisse est d'affirmer que size>0

  • Fichier tas.h :
//获取堆顶元素
HPDataType HeapTop(HP* php);
  • Fichier tas.c :
//获取堆顶元素
HPDataType HeapTop(HP* php)
{
	assert(php);
	assert(php->size > 0);
	return php->a[0];
}

3. Code total

Fichier tas.h

#pragma once
#include<stdio.h>
#include<stdlib.h>
#include<assert.h>
#include<stdbool.h>

//创建堆结构
typedef int HPDataType; //堆中存储数据的类型
typedef struct Heap
{
	HPDataType* a; //用于存储数据
	size_t size; //记录堆中有效元素个数
	size_t capacity; //记录堆的容量
}HP;

//初始化堆
void HeapInit(HP* php);
//堆的销毁
void HeapDestroy(HP* php);
//堆的打印
void HeapPrint(HP* php);

//交换
void Swap(HPDataType* pa, HPDataType* pb);

//堆的插入
void HeapPush(HP* php, HPDataType x);
//堆的删除  删除堆顶的数据
void HeapPop(HP* php);

//堆的判空
bool HeapEmpty(HP* php);
//堆的元素个数
size_t HeapSize(HP* php);
//获取堆顶元素
HPDataType HeapTop(HP* php);

Fichier tas.c

#define _CRT_SECURE_NO_WARNINGS 1
#include"Heap.h"
//初始化堆
void HeapInit(HP* php)
{
	assert(php);
	php->a = NULL;
	php->size = php->capacity = 0;
}

//堆的销毁
void HeapDestroy(HP* php)
{
	assert(php);
	free(php->a);
	php->a = NULL; //置空
	php->size = php->capacity = 0; //置0
}
//堆的打印
void HeapPrint(HP* php)
{
	assert(php);
	for (size_t i = 0; i < php->size; i++)
	{
		printf("%d ", php->a[i]);
	}
	printf("\n");
}

//交换
void Swap(HPDataType* pa, HPDataType* pb)
{
	HPDataType tmp = *pa;
	*pa = *pb;
	*pb = tmp;
}

//向上调整算法
void AdjustUp(HPDataType* a, size_t child)
{
	size_t parent = (child - 1) / 2;
	while (child > 0)
	{
		//if (a[child] > a[parent]) //大根堆
		if (a[child] < a[parent]) //小根堆
		{
			Swap(&a[child], &a[parent]);
			child = parent;
			parent = (child - 1) / 2;
		}
		else
		{
			break;
		}
	}
}

//向下调整算法
void AdjustDown(HPDataType* a, size_t size, size_t root)
{
	int parent = root;
	int child = 2 * parent + 1;
	while (child < size)
	{
		//1、确保child的下标对应的值最小,即取左右孩子较小那个
		if (child + 1 < size && a[child + 1] < a[child]) //得确保右孩子存在
		{
			child++; //此时右孩子小
		}
		//2、如果孩子小于父亲则交换,并继续往下调整
		if (a[child] < a[parent])
		{
			Swap(&a[child], &a[parent]);
			parent = child;
			child = 2 * parent + 1;
		}
		else
		{
			break;
		}
	}
}

//堆的插入
void HeapPush(HP* php, HPDataType x)
{
	assert(php);
	//检测是否需要扩容
	if (php->size == php->capacity)
	{
		//扩容
		size_t newcapacity = php->capacity == 0 ? 4 : php->capacity * 2;
		HPDataType* tmp = (HPDataType*)realloc(php->a, sizeof(HPDataType) * newcapacity);
		if (tmp == NULL)
		{
			printf("realloc fail\n");
			exit(-1);
		}
		php->a = tmp;
		php->capacity = newcapacity;
	}
	php->a[php->size] = x;
	php->size++;
	//保持继续是堆,向上调整算法
	AdjustUp(php->a, php->size - 1);
}


//堆的删除  删除堆顶的数据
void HeapPop(HP* php)
{
	assert(php);
	assert(php->size > 0);//确保size>0
	Swap(&php->a[0], &php->a[php->size - 1]); //交换堆头和堆尾
	php->size--;
	//向下调整,确保仍然是堆结构
	AdjustDown(php->a, php->size, 0);
}


//堆的判空
bool HeapEmpty(HP* php)
{
	assert(php);
	return php->size == 0; //size为0即为空
}

//堆的元素个数
size_t HeapSize(HP* php)
{
	assert(php);
	return php->size;
}

//获取堆顶元素
HPDataType HeapTop(HP* php)
{
	assert(php);
	assert(php->size > 0);
	return php->a[0];
}

Fichier test.c

#define _CRT_SECURE_NO_WARNINGS 1
#include"Heap.h"
void TestHeap1()
{
	HP hp;
	HeapInit(&hp);
	//插入数据
	HeapPush(&hp, 1);
	HeapPush(&hp, 5);
	HeapPush(&hp, 3);
	HeapPush(&hp, 0);
	HeapPush(&hp, 8);
	HeapPush(&hp, 9);
	//打印
	HeapPrint(&hp);
	//销毁
	HeapDestroy(&hp);
}
void TestHeap2()
{
	HP hp;
	HeapInit(&hp);
	//插入数据
	HeapPush(&hp, 1);
	HeapPush(&hp, 5);
	HeapPush(&hp, 3);
	HeapPush(&hp, 0);
	HeapPush(&hp, 8);
	HeapPush(&hp, 9);
	HeapPrint(&hp);//打印
	//删除堆顶数据
	HeapPop(&hp);
	HeapPrint(&hp);//打印
	//销毁
	HeapDestroy(&hp);
}
int main()
{
	//TestHeap1();
	TestHeap2();
	return 0;
}

Je suppose que tu aimes

Origine blog.csdn.net/bit_zyx/article/details/123969874
conseillé
Classement