< Datenstruktur > Heap-Implementierung

Inhalt

1. Einleitung

        Haufen-Konzept

        Haufenstruktur

2. Implementierung des Haufens

      2.1 Vorbereitungen

         Heap-Struktur erstellen

         Haufen initialisieren

         Haufen drucken

         Zerstörung des Haufens

      2.2, Heap-Anpassung

         Haufen tauschen

         Heap-Up-Anpassungsalgorithmus

         Heap-Abwärtsanpassungsalgorithmus

      2.3, Kernfunktionen

         Heap-Einfügung

         Heap-Löschung

         Haufenlos

         Holen Sie sich die Anzahl der Elemente im Haufen

         Holen Sie sich das oberste Element des Haufens

3. Gesamtcode

        Heap.h-Datei

        Heap.c-Datei

        Test.c-Datei


1. Einleitung

  • Verwenden Sie ein Bild, um die am Ende des letzten Blogbeitrags erläuterten Wissenspunkte zu überprüfen:

Durch die Erklärung des vorherigen Blogbeitrags wissen wir, dass vollständige Binärbäume und vollständige Binärbäume durch Arrays gespeichert werden können und die Eltern-Kind-Beziehung zwischen ihnen durch Indizes dargestellt werden kann. Es wird hier betont, dass die physische Struktur tatsächlich im Speicher gespeichert ist, der physisch ein Array ist, aber logisch als binärer Baum betrachtet werden sollte. Es ist, als gäbe es keine Kuh in der Milch, kein Mineral im Mineralwasser, keine Frau im Frauenkuchen~

  • Da vollständige Binärbäume und vollständige Binärbäume für die Speicherung in Arrays geeignet sind, was ist mit gewöhnlichen Binärbäumen? Zeichnungsverständnis:

Gewöhnliche Binärbäume eignen sich nicht zum Speichern in Arrays, da viel Platz verschwendet werden kann. Ein vollständiger Binärbaum eignet sich besser zur Speicherung in einer sequentiellen Struktur. Da die Raumnutzungsrate hoch ist, gibt es keine Verschwendung. In der Realität speichern wir den Heap (eine Art Binärbaum) normalerweise mit einem Array sequentieller Strukturen.Es sollte beachtet werden, dass der Heap hier und der Heap im virtuellen Prozessadressraum des Betriebssystems zwei verschiedene Dinge sind die Datenstruktur und der andere stellt das Verwaltungssystem im Betriebssystem dar. Ein Speicherbereich wird segmentiert. Wenn es sich nicht um einen vollständigen Binärbaum oder um einen vollständigen Binärbaum handelt, empfiehlt sich die Verwendung der Kettenspeicherung, die im vorherigen Blogbeitrag erläutert wurde und nicht wiederholt wird.

Der Heap ist ein vollständiger binärer Baum, der in einem Array gespeichert werden kann.Als nächstes werde ich es im Detail erklären:

Haufen-Konzept

Wie aus dem Obigen ersichtlich ist, ist der Heap ein vollständiger Binärbaum, und alle seine Elemente werden in einem eindimensionalen Array gemäß der Reihenfolge des vollständigen Binärbaums gespeichert. Es gibt zwei Arten von Haufen: kleine Wurzelhaufen und große Wurzelhaufen

  1. Kleiner Heap: Der Wert jedes übergeordneten Knotens ist kleiner oder gleich dem Wert seines entsprechenden untergeordneten Knotens, und der Wert des Wurzelknotens ist der kleinste.
  2. Großer Heap: Der Wert jedes übergeordneten Knotens ist größer oder gleich dem Wert seines entsprechenden untergeordneten Knotens, und der Wert des Wurzelknotens ist der größte.
  • Eigenschaften des Haufens:
  1. Der Wert eines Knotens im Heap ist immer nicht größer oder kleiner als der Wert seines Elternknotens.
  2. Ein Heap ist immer ein vollständiger binärer Baum.

Haufenstruktur

Aus der physikalischen Struktur können wir die folgenden zwei Punkte erkennen:

  1. bestellt muss ein Haufen sein
  2. unordered kann heap sein

2. Implementierung des Haufens

2.1 Vorbereitungen

Heap-Struktur erstellen

  • Ideen:

Aus dem Obigen ist ersichtlich, dass die Grundstruktur des Heaps ein Array ist. Beim Erstellen einer Heap-Struktur kann diese wie bisher dynamisch geöffnet werden. Auch der Vorgang ist ähnlich, und der Code wird direkt hinzugefügt. Aber nimm zuerst den kleinen Wurzelhaufen als Beispiel.

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

Haufen initialisieren

  • Ideen:

Initialisieren Sie den Heap, dann darf der übergebene Strukturzeiger nicht leer sein, er muss erst einmal bestätigt werden. Der Rest der Operationen ist derselbe wie bei der vorherigen Sequenztabellen- und Stack-Initialisierung.

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

Haufen drucken

  • Ideen:

Tatsächlich ist das Drucken des Heaps sehr einfach. Die physische Struktur des Heaps ist ein Array. Das Wesentliche beim Drucken des Heaps ist immer noch ähnlich dem Drucken der vorherigen Sequenztabelle. Sie können auf die Indizes zugreifen und der Reihe nach drucken.

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

Zerstörung des Haufens

  • Ideen:

Dynamisch geöffneter Speicher muss auch nach Gebrauch zerstört werden.

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

2.2, Heap-Anpassung

Haufen tauschen

  • Ideen:

Der Austausch des Heaps ist relativ einfach, es unterscheidet sich nicht von dem, was zuvor geschrieben wurde, denken Sie daran, die Adresse zu übergeben.

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

Heap-Up-Anpassungsalgorithmus

  • Ideen:

Dieser Algorithmus ist eine separat gekapselte Funktion, um sicherzustellen, dass der Heap nach dem Einfügen von Daten immer noch der Beschaffenheit des Heaps entspricht, genau wie die Zahl 10, die wir später einfügen möchten, zeichnen Sie zuerst ein Bild.

Um sicherzustellen, dass es nach dem Einfügen der Zahl 10 immer noch ein kleiner Wurzelhaufen ist, müssen wir also 10 und 28 vertauschen, die Größe des Elternknotens parent und des Kindknotens child wiederum vergleichen , wenn der Elternknoten kleiner ist als der untergeordneter Knoten, wird es zurückgegeben, sonst wird es immer ausgetauscht, bis die Wurzel.

Aus der vorherigen Regel parent = (child - 1) / 2 manipulieren wir ein Array, stellen es uns aber als binären Baum vor. Zeichnung zur Veranschaulichung des Einstellvorgangs:

  • Heap.c-Datei:
//向上调整算法
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;
		}
	}
}

Heap-Abwärtsanpassungsalgorithmus

  • Ideen:

Verwenden Sie zuerst ein Bild, um zu demonstrieren:

An diesem Punkt können wir sehen, dass dieser Binärbaum nicht den Eigenschaften des Haufens als Ganzes entspricht, aber der linke und der rechte Teilbaum seiner Wurzel erfüllen beide die Eigenschaften des Haufens. Passen Sie als Nächstes nach unten an, um sicherzustellen, dass es ein Haufen wird. Nur eine Trilogie.

  1. Finden Sie das jüngste der linken und rechten Kinder
  2. Mit Vater vergleichen, falls jünger als Vater, tauschen
  3. Von der getauschten Kinderposition aus weiter nach unten verstellen

Das Änderungsdiagramm sieht wie folgt aus:

  • Heap.c-Datei:
//向下调整算法
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, Kernfunktionen

Heap-Einfügung

  • Notiz:

Das Einfügen des Haufens ist nicht wie bei der vorherigen Sequenztabelle, er kann am Kopf eingefügt werden, an beliebiger Stelle eingefügt werden usw. Da es sich um einen Haufen handelt, muss er der Art des großen Wurzelhaufens oder des kleinen Wurzelhaufens entsprechen , und die ursprüngliche Struktur des Heaps kann nicht geändert werden, daher ist das Einfügen von Schwänzen am besten geeignet. , und prüfen Sie, ob es nach dem Einfügen von Schwänzen der Beschaffenheit des Haufens entspricht.

Zum Beispiel haben wir eine Reihe von Arrays, die entsprechend der Art des kleinen Wurzelhaufens gespeichert werden. Jetzt möchte ich eine Zahl 10 am Ende des Arrays einfügen, wie in der Abbildung gezeigt:

  • Ideen:

Dieser Baum ist ein kleiner Haufen, bevor die Zahl 10 eingefügt wird, und er ist es nicht nach dem Einfügen, was die Natur des kleinen Wurzelhaufens ändert. Da der Kindknoten 10 kleiner als sein Elternknoten 28 ist, was sollen wir tun?

In erster Linie müssen Sie vor dem Einfügen zunächst feststellen, ob die Kapazität des Heaps noch ausreicht, um Daten einzufügen, zuerst prüfen, ob die Kapazität erweitert werden soll, und dann, nachdem die Erweiterung abgeschlossen ist. Wir können feststellen, dass die eingefügte 10 nur die Wurzel von sich selbst bis zur Wurzel, also dem Vorfahren, beeinflusst.Solange dieser Pfad der Natur des Haufens entspricht, ist die Einfügung erfolgreich.

Kernidee: Algorithmus nach oben anpassen. Wenn wir sehen, dass die eingefügte 10 28 Stunden älter ist als der Vater, tauschen wir die Zahlen zu diesem Zeitpunkt aus, aber zu diesem Zeitpunkt ist 10 kleiner als 18, wir tauschen sie erneut aus und stellen schließlich fest, dass 10 kleiner als 15 ist, und wir wieder umtauschen. Dies ist natürlich der schlimmste Fall.Wenn die Eigenschaften des Heaps während der zwischenzeitlichen Änderung erfüllt sind, muss nicht erneut geändert werden, sondern es wird direkt zurückgekehrt. Dies wird als Aufwärtsanpassungsalgorithmus bezeichnet, und die obige Funktion kann direkt angewendet werden.

  • Heap.h-Datei:
//堆的插入
void HeapPush(HP* php, HPDataType x);
  • Heap.c-Datei:
//堆的插入
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);
}
  • Test.c-Datei:
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);
}
  • Die Wirkung ist wie folgt:

entspricht der Beschaffenheit des Haufens.

Heap-Löschung

  • Wie in der Abbildung gezeigt:

  • Ideen:

Bei der obigen Heap-Einfügung haben wir deutlich gemacht, dass es nach dem Einfügen immer noch ein Heap ist, und das Löschen des Heaps hier sorgt auch dafür, dass es nach dem Löschen immer noch ein Heap ist.Hinweis: Das Löschen des Heaps hier ist zu löschen die Daten an der Spitze des Haufens. Am Beispiel des kleinen Root-Heaps löschen Sie die Daten an der Spitze des Heaps, dh die kleinsten Daten, und stellen Sie dann sicher, dass es immer noch ein Heap ist. Die Idee, die ich gegeben habe, ist:

  • Tauschen Sie zuerst die ersten Daten mit den letzten Daten aus

Nach dem Austausch entspricht der Heap zu diesem Zeitpunkt nicht seiner Natur, da die letzten Daten größer sein müssen als die ersten Daten, und jetzt erreichen die letzten Daten die Spitze des Heaps, es ist kein Heap, sondern der linke Teilbaum des Wurzelknotens und des rechten Teilbaums sind nicht betroffen, sie sind immer noch Haufen, wenn sie einzeln betrachtet werden

  • Als nächstes stellt --size sicher, dass die Top-of-Heap-Daten gelöscht werden

Da die Daten an der Spitze des Heaps zu diesem Zeitpunkt das Ende des Heaps erreicht haben, genau wie die Sequenztabelle --size, um sicherzustellen, dass die effektiven Daten um 1 reduziert werden, dh um sicherzustellen, dass die Spitze gelöscht wird des Haufens

  • Verwenden Sie schließlich den Down-Adjustment-Algorithmus, um sicherzustellen, dass es sich um eine Heap-Struktur handelt

Das Änderungsdiagramm sieht wie folgt aus:

Zeitkomplexitätsanalyse

Der Austausch der ersten Daten und der letzten Daten ist O (1), und die Zeitkomplexität des Abwärtsanpassungsalgorithmus ist O (logN), da die Abwärtsanpassung die Höhenzeiten gemäß der Anzahl der Knoten N anpassen soll. daraus kann abgeleitet werden, dass die Höhe ungefähr logN beträgt

  • Heap.h-Datei:
//堆的删除  删除堆顶的数据
void HeapPop(HP* php);
  • Heap.c-Datei:
//堆的删除  删除堆顶的数据
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);
}
  • Test.c-Datei:
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);
}
  • Die Wirkung ist wie folgt:

Haufenlos

  • Ideen:

Die Leerbeurteilung des Heaps ist sehr einfach und unterscheidet sich nicht von der vorherigen Stapelsequenztabelle.Wenn die Größe 0 ist, kann sie direkt zurückgegeben werden.

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

Holen Sie sich die Anzahl der Elemente im Haufen

  • Ideen:

Geben Sie die Größe einfach direkt zurück.

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

Holen Sie sich das oberste Element des Haufens

  • Ideen:

Gehen Sie einfach zurück an die Spitze des Haufens. Die Prämisse ist, zu behaupten, dass size>0

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

3. Gesamtcode

Heap.h-Datei

#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);

Heap.c-Datei

#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];
}

Test.c-Datei

#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;
}

Ich denke du magst

Origin blog.csdn.net/bit_zyx/article/details/123969874
Empfohlen
Rangfolge