< data structure > heap implementation

content

1 Introduction

        heap concept

        heap structure

2. Implementation of the heap

      2.1 . Preparations

         Create heap structure

         initialize heap

         heap print

         Destruction of the heap

      2.2, heap adjustment

         heap swap

         Heap Up Adjustment Algorithm

         Heap Downward Adjustment Algorithm

      2.3, core functions

         Heap insertion

         heap deletion

         Empty of heap

         Get the number of elements in the heap

         Get the top element of the heap

3. Total code

        Heap.h file

        Heap.c file

        Test.c file


1 Introduction

  • Use a picture to review the knowledge points explained at the end of the last blog post:

Through the explanation of the previous blog post, we know that complete binary trees and full binary trees can be stored by arrays, and the parent-child relationship between them can be represented by subscripts. It is emphasized here that the physical structure is actually stored in memory, which is an array physically, but logically it should be seen as a binary tree. It's like there is no cow in milk, no mineral in mineral water, no wife in wife cake~

  • Since complete binary trees and full binary trees are suitable for storage in arrays, what about ordinary binary trees? Drawing comprehension:

Ordinary binary trees are not suitable for storing in arrays, because there may be a lot of wasted space. A complete binary tree is more suitable for storage in a sequential structure. Because the space utilization rate is high, there will be no waste. In reality, we usually store the heap (a kind of binary tree) using an array of sequential structures. It should be noted that the heap here and the heap in the virtual process address space of the operating system are two different things. One is the data structure and the other is the management system in the operating system. A region of memory is segmented. If it is not a complete binary tree or a full binary tree, it is recommended to use chain storage, which has been explained in the previous blog post and will not be repeated.

The heap is a complete binary tree, which can be stored in an array. Next, I will explain it in detail:

heap concept

As can be seen from the above, the heap is a complete binary tree, and all its elements are stored in a one-dimensional array according to the order of the complete binary tree. There are two types of heaps: small root heap and large root heap

  1. Small heap: The value of each parent node is less than or equal to the value of its corresponding child node, and the value of the root node is the smallest.
  2. Large heap: The value of each parent node is greater than or equal to the value of its corresponding child node, and the value of the root node is the largest.
  • Properties of the heap:
  1. The value of a node in the heap is always not greater or less than the value of its parent.
  2. A heap is always a complete binary tree.

heap structure

From the physical structure, we can know the following two points:

  1. ordered must be a heap
  2. unordered may be heap

2. Implementation of the heap

2.1. Preparations

Create heap structure

  • Ideas:

It can be seen from the above that the basic structure of the heap is an array. When creating a heap structure, it can be opened dynamically as before. The operation process is also similar, and the code is directly added. But first take the small root heap as an example.

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

initialize heap

  • Ideas:

Initialize the heap, then the passed structure pointer cannot be empty, first of all, it must be asserted. The rest of the operations are the same as the previous sequence table and stack initialization.

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

heap print

  • Ideas:

In fact, the printing of the heap is very simple. The physical structure of the heap is an array. The essence of printing the heap is still similar to the printing of the previous sequence table. You can access the subscripts and print in turn.

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

Destruction of the heap

  • Ideas:

For dynamically opened memory, it needs to be destroyed even after use.

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

2.2, heap adjustment

heap swap

  • Ideas:

The exchange of the heap is relatively simple, it is no different from what was written before, remember to pass the address.

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

Heap Up Adjustment Algorithm

  • Ideas:

This algorithm is a function that is encapsulated separately to ensure that the heap after inserting data is still in line with the nature of the heap, just like the number 10 we want to insert later, draw a picture first.

In order to ensure that it is still a small root heap after inserting the number 10, so we need to exchange 10 and 28, compare the size of the parent node parent and the child node child in turn , when the parent is smaller than the child node, it will return, otherwise it will always be exchanged , until the root.

From the previous rule, parent = (child - 1) / 2 , we are manipulating an array, but think of it as a binary tree. Drawing to demonstrate the adjustment process:

  • Heap.c file:
//向上调整算法
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 Downward Adjustment Algorithm

  • Ideas:

First use a picture to demonstrate:

At this point, we can see that this binary tree does not conform to the properties of the heap as a whole, but the left and right subtrees of its root both satisfy the properties of the heap. Next, adjust down to make sure it ends up being a heap. Just a trilogy.

  1. Find the youngest of the left and right children
  2. Compare with father, if younger than father, exchange
  3. Continue to adjust downward from the exchanged child position

The change diagram is as follows:

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

Heap insertion

  • Notice:

The insertion of the heap is not like the previous sequence table. It can be inserted at the head, inserted at any position, etc. Because it is a heap, it must conform to the nature of the large root heap or the small root heap, and the original structure of the heap cannot be changed, so the tail insertion is the most suitable. , and check whether it conforms to the nature of the heap after tail insertion.

For example, we have a string of arrays, which are stored according to the nature of the small root heap. Now I want to insert a number 10 at the end of the array, as shown in the figure:

  • Ideas:

This tree is a small heap before the number 10 is inserted, and it is not after the insertion, which changes the nature of the small root heap. Because the child node 10 is smaller than its parent node 28, what should we do?

First and foremost, before inserting, you must first determine whether the capacity of the heap is still enough to insert data, first check whether to expand the capacity, and then after the expansion is completed. We can find that the 10 inserted will only affect the root from itself to the root, that is, the ancestor. As long as this path conforms to the nature of the heap, the insertion is successful.

Core idea: adjust the algorithm upwards. When we see that the inserted 10 is 28 hours older than the father, we exchange the numbers at this time, but at this time 10 is smaller than 18, we exchange it again, and finally find that 10 is smaller than 15, and we exchange it again. Of course, this is the worst case. If the properties of the heap are satisfied during the intermediate change, then there is no need to change again, just return directly. This is called the upward adjustment algorithm, and the above function can be directly applied.

  • Heap.h file:
//堆的插入
void HeapPush(HP* php, HPDataType x);
  • Heap.c file:
//堆的插入
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 file:
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);
}
  • The effect is as follows:

conforms to the nature of the heap.

heap deletion

  • As shown in the figure:

  • Ideas:

In the above heap insertion, we made it clear that it is still a heap after the insertion, and the deletion of the heap here also ensures that it is still a heap after the deletion. Note: the deletion of the heap here is to delete the data at the top of the heap. Taking the small root heap as an example, delete the data at the top of the heap, that is, delete the smallest data, then ensure that it is still a heap. The idea I gave is:

  • First , exchange the first data with the last data

After the exchange, the heap at this time does not conform to its nature, because the last data must be larger than the first data, and now the last data reaches the top of the heap, it is not a heap, but the left subtree of the root node and the right subtree are not affected, they are still heaps when viewed alone

  • Next , --size ensures that the top-of-heap data is deleted

Because the data at the top of the heap has reached the end of the heap at this time, just like the sequence table --size, to ensure that the effective data is reduced by 1, that is, to ensure the deletion of the top of the heap

  • Finally , use the down-adjustment algorithm to make sure it's a heap structure

The change diagram is as follows:

time complexity analysis

The exchange of the first data and the last data is O(1), and the time complexity of the downward adjustment algorithm is O(logN), because the downward adjustment is to adjust the height times, according to the number of nodes N, it can be deduced that the height is approximately logN

  • Heap.h file:
//堆的删除  删除堆顶的数据
void HeapPop(HP* php);
  • Heap.c file:
//堆的删除  删除堆顶的数据
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 file:
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);
}
  • The effect is as follows:

Empty of heap

  • Ideas:

The empty judgment of the heap is very simple, and it is no different from the previous stack sequence table. If the size is 0, it can be returned directly.

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

Get the number of elements in the heap

  • Ideas:

Just return size directly.

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

Get the top element of the heap

  • Ideas:

Just go back to the top of the heap. The premise is to assert that size>0

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

3. Total code

Heap.h file

#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 file

#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 file

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

Guess you like

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