[Datenstruktur - C-Sprache] Heap, Sie sind ein Heap, sehen Sie, wie ich gründlich mit Ihnen umgehen kann

Inhaltsverzeichnis

1. Das Konzept und die Struktur des Heaps

1.1 Konzepte (Konzepte sind immer wichtig)

1.2 Struktur, unterteilt in zwei Typen

1.2.1 Beispiel für einen kleinen Heap/kleinen Root-Heap

1.2.2 Beispiel für einen großen Heap/großen Root-Heap

2. Heap-Schnittstelle

3. Schnittstellenimplementierung

3.1 Heap-Initialisierung

3.2 Zerstörung des Heaps

3.3 Heap-Einfügung

Funktionsanalyse:

Funktionsrealisierung:

3.4 Heap-Löschung

Funktionsanalyse:

Funktionsrealisierung:

3.5 Holen Sie sich die Daten oben auf dem Heap

3.6 Die Anzahl der Daten im Heap

3.7 Urteil über den leeren Haufen

4. Vollständiger Code


1. Das Konzept und die Struktur des Heaps

1.1 Konzepte (Konzepte sind immer wichtig)

Der obige Absatz ist das Konzept von Stapeln, aber das ist zu langweilig. Lassen Sie uns auf einfache Weise darüber sprechen und an die Tafel klopfen:

Die Essenz des Heaps ist ein vollständiger Binärbaum.

Großer Heap (auch Big Root Heap genannt): Der übergeordnete Knoten ist größer/gleich dem untergeordneten Knoten.

Kleines Paar (auch kleiner Root-Heap genannt): Der übergeordnete Knoten ist kleiner/gleich dem untergeordneten Knoten.

Wenn die oben genannten Bedingungen nicht erfüllt sind, handelt es sich nicht um einen Heap.

Eigenschaften des Heaps :

1. Der Wert eines Knotens im Heap ist immer nicht größer oder nicht kleiner als der Wert seines übergeordneten Knotens.
2. Der Heap ist immer ein vollständiger Binärbaum.

1.2 Struktur, unterteilt in zwei Typen

1.2.1 Beispiel für einen kleinen Heap/kleinen Root-Heap

1.2.2 Beispiel für einen großen Heap/großen Root-Heap

Schauen wir uns ein Thema an:

Die folgende Folge von Schlüsselwörtern ist ein Heap: (A)

A 100,60,70,50,32,65

B 60,70,65,50,32,100

C 65.100.70.32.50.60

D 70,65,100,32,50,60

E 32,50,100,70,65,60

F 50.100.70.65.60.32

Analyse : Wir zeichnen ein Diagramm zur Analyse

2. Heap-Schnittstelle

Dieser Artikel wird am Beispiel des kleinen Heaps implementiert. Der Datenspeicher des Heaps wird in einem Array gespeichert, und die Speicherstruktur der Daten im Speicher wird sequentiell gespeichert. Wir verstehen sie zum leichteren Verständnis in einer logischen Struktur.

Zu den Schnittstellen des Heaps gehören: Initialisierung, Zerstörung, Einfügen, Löschen, Übernahme des oberen Rands des Heaps, Anzahl der Daten im Heap und Leerbeurteilung.

#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
#include <stdbool.h>

typedef int HPDataType;
typedef struct Heap
{
	HPDataType* a;
	int size;
	int capacity;
}Heap;

// 堆的初始化
void HeapInit(Heap* hp);
// 堆的销毁
void HeapDestory(Heap* hp);
//交换
void Swap(HPDataType* p1, HPDataType* p2);
//向上调整
void AdjustUp(HPDataType* a, int child);
//向下调整
void AdjustDown(HPDataType* a, int size, int parent);
// 堆的插入
void HeapPush(Heap* hp, HPDataType x);
// 堆的删除
void HeapPop(Heap* hp);
// 取堆顶的数据
HPDataType HeapTop(Heap* hp);
// 堆的数据个数
int HeapSize(Heap* hp);
// 堆的判空
bool HeapEmpty(Heap* hp);

3. Schnittstellenimplementierung

Viele unserer Schnittstellen ähneln den vorherigen Artikeln zur Datenstruktur. Sie wurden schon oft erklärt, daher werde ich sie hier nicht erklären. Wenn Sie etwas nicht verstehen, können Sie auf die vorherigen Artikel zur Datenstruktur zurückgreifen.

3.1 Heap-Initialisierung

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

	hp->a = NULL;
	hp->size = 0;
	hp->capacity = 0;
}

3.2 Zerstörung des Heaps

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

	free(hp->a);
	hp->a = NULL;
	hp->size = hp->capacity = 0;
}

3.3 Heap-Einfügung

Das Einfügen von Heaps ist komplizierter und schwieriger. Lassen Sie es uns zuerst analysieren und dann die Funktion implementieren.

Funktionsanalyse:

1. Beim Einfügen müssen wir zunächst prüfen, ob das Array erweitert werden muss, zunächst feststellen, ob es voll ist. Wenn der Speicherplatz voll ist, erweitern wir zuerst die Kapazität und fügen dann das neue Element am Ende des Arrays ein.

2. Wenn wir ein neues Element einfügen, müssen wir analysieren, ob der Heap die Struktur des kleinen Heaps erfüllt. Wenn nicht, müssen wir das neue Element nach oben anpassen.

3. Analyse des Aufwärtsanpassungsprozesses:

Nehmen wir zur Analyse ein Beispiel: Wenn die Struktur des Heaps nach dem Einfügen eines Elements in einen kleinen Heap zerstört wird, wie können Anpassungen vorgenommen werden, um die Struktur des kleinen Heaps wiederherzustellen?

a. Wenn wir nach dem Einfügen einer 10 in den kleinen Heap feststellen, dass 10 kleiner als der übergeordnete Knoten 28 ist, wodurch die Struktur des kleinen Heaps zerstört wird, müssen wir den Heap anpassen.

b. Die physische Struktur des Heaps ist ein Array, sodass wir den übergeordneten Knoten über den Index finden können. Hier ist die Formel zum Finden des übergeordneten Knotens: parent = (child-1)/2. Wenn wir den übergeordneten Knoten finden, vergleichen wir den untergeordneten Knoten mit dem übergeordneten Knoten. Wenn er kleiner als der übergeordnete Knoten ist, tauschen wir die Elemente der beiden Knoten aus. Der ausgetauschte übergeordnete Knoten und sein übergeordneter Knoten erfüllen möglicherweise nicht den kleinen Heap Wir müssen die Anpassung nach oben fortsetzen

c. Schleife zum Vergleichen und Anpassen. Wenn Kind = 0, ist unsere Anpassung abgeschlossen, sodass unsere Schleifenbeurteilungsbedingung Kind > 0 ist.

Hinweis: Nach einer Anpassung ist unser Heap zu einem kleinen Heap geworden und wir springen aus der Schleife.

Lassen Sie uns ein Bild gemäß den oben genannten Ideen zeichnen:

Unsere Analyse der Funktion ist abgeschlossen und wir beginnen, die Funktion zu erkennen.

Funktionsrealisierung:

void Swap(HPDataType* p1, HPDataType* p2)
{
	HPDataType tmp = *p1;
	*p1 = *p2;
	*p2 = tmp;
}

void AdjustUp(HPDataType* a, int child)
{
	int 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;
		}
	}
}

//log N 
void HeapPush(Heap* hp, HPDataType x)
{
	assert(hp);

	if (hp->size == hp->capacity)
	{
		int newcapacity = hp->capacity == 0 ? 4 : 2 * hp->capacity;
		HPDataType* tmp = (HPDataType*)realloc(hp->a, sizeof(HPDataType) * newcapacity);
		if (NULL == tmp)
		{
			perror("realloc fail:");
		}

		hp->a = tmp;
		hp->capacity = newcapacity;
	}
	hp->a[hp->size] = x;
	hp->size++;

	AdjustUp(hp->a, hp->size - 1);
}

Wir werden den Austausch und die Aufwärtsanpassung später wiederverwenden, also kapseln wir die beiden Funktionen in einer Funktion.

3.4 Heap-Löschung

Beim Löschen des Heaps wird das Element oben im Heap gelöscht.

Idee: Tauschen Sie die obersten Daten mit den letzten Daten aus, löschen Sie die letzten Daten und passen Sie sie dann vom oberen Rand des Heaps aus an.

Funktionsanalyse:

Wenn wir die Daten oben im Heap löschen, können wir sie nicht direkt löschen. Durch direktes Löschen der Daten oben im Heap wird die Heap-Struktur zerstört, und der Zeitaufwand für den Aufbau des Heaps ist zu hoch, also nicht empfohlen. Hier stellen wir eine Methode mit geringerer Komplexität vor:

1. Wir tauschen zuerst die obersten Daten mit den letzten Daten aus, löschen dann die letzten Daten und passen sie schließlich vom oberen Rand des Heaps nach unten an.

2. Die Abwärtsanpassung ist komplizierter. Lassen Sie uns analysieren und ein Bild zeichnen, um es zu erklären:

a. Zu diesem Zeitpunkt ist unser übergeordneter Knoten der oberste Knoten des Heaps. Als nächstes müssen wir den kleineren der beiden untergeordneten Knoten als untergeordneten Knoten finden. Die Formel zum Finden des untergeordneten Knotens lautet hier: Kind = Eltern * 2 + 1;

b. Wenn das Kind kleiner als der Vater ist, tauschen Sie es aus und passen Sie es weiter nach unten an. Lassen Sie den Elternteil zur Kinderposition gehen und berechnen Sie dann die Kinderposition.

c. Wenn der Index des untergeordneten Elements größer als die Größe des Arrays ist, endet die Schleife und die gesamte Anpassung ist abgeschlossen.

Hinweis: Nach einer Anpassung ist unser Heap zu einem kleinen Heap geworden und wir springen aus der Schleife.

Funktionsrealisierung:

void AdjustDown(HPDataType* a, int size, int parent)
{
	int child = parent * 2 + 1;
	while (child < size)//当child大于了数组大小就跳出循环
	{
		//找出左右孩子中小/大的那个(假设法)
		if (child + 1 < size && a[child + 1] < a[child])
		{
			child++;
		}

		if (a[child] < a[parent])
		{
			Swap(&a[parent], &a[child]);

			parent = child;
			child = parent * 2 + 1;
		}
		else
		{
			break;
		}
	}
}

//log N
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);
}

3.5 Holen Sie sich die Daten oben auf dem Heap

HPDataType HeapTop(Heap* hp)
{
	assert(hp);
	assert(!HeapEmpty(hp));

	return hp->a[0];
}

3.6 Die Anzahl der Daten im Heap

int HeapSize(Heap* hp)
{
	assert(hp);
	assert(!HeapEmpty(hp));

	return hp->size;
}

3.7 Urteil über den leeren Haufen

bool HeapEmpty(Heap* hp)
{
	assert(hp);

	return hp->size == 0;
}

4. Vollständiger Code

Der vollständige Code befindet sich im Code Warehouse: Heap Xiaobai arbeitet hart an jy/DataStructure - Code Cloud - Open Source China (gitee.com)

Ich denke du magst

Origin blog.csdn.net/Ljy_cx_21_4_3/article/details/130903751
Empfohlen
Rangfolge