[Data structure] Explanation and implementation of heap

Structural Analysis of Heaps

It should be noted that the heap is a data structure and has nothing to do with the heap area of ​​the operating system.

The structure of the heap: the heap is a complete binary tree, continuous from left to right, suitable for array storage

insert image description here

insert image description here

Heap is a complete binary tree, divided into large heap and small heap

Large heap: In a tree, any parent node is greater than or equal to the child node, so the root node of the large heap is the largest

Small heap: In a tree, any parent node is less than or equal to the child node, so the root node of the small heap is the smallest

insert image description here

Implementation of the heap

For those who have known the sequence table, the implementation of the heap is very simple, using malloc dynamic memory allocation to open up space storage nodes. Because to satisfy the large heap or the small heap, the upward adjustment and downward adjustment algorithms need to be used. We directly understand in the following implementation. First, take the large heap as an example , and then only need to fine-tune the upward adjustment and downward adjustment to achieve Switching between large and small heaps

heap storage structure

typedef int HPDataType;

typedef struct Heap
{
	HPDataType* p;
	int size;//节点个数
	int capacity;//空间容量
}HP;

heap initialization

//对象传给HeapInit函数初始化
int mian()
{
	HP h;
	HeapInit(&h);
}
void HeapInit(HP* pc)
{	
	assert(pc);
	pc->capacity = pc->size = 0;
	pc->p = NULL;
}

Heap insertion push

Building a heap: In order not to destroy the original structure of the heap, it is necessary to insert tails in sequence, and to satisfy the large heap, each insertion must compare the size with the parent node, and continuously adjust upward, and finally realize the large heap

int arr[6] = { 70, 56, 30, 60, 25, 40 };

adjust up

The inserted node is constantly compared with the parent node. If the child node is larger than the parent node, it will switch positions, and then compare it with the new parent node again. If it is smaller than the parent node, it will terminate.

insert image description here

//交换函数在下面的堆删除也能复用
void HeapSwap(HPDataType* p1, HPDataType* p2)
{	
	assert(p1 && p2);
	HPDataType tmp = *p1;
	*p1 = *p2;
	*p2 = tmp;
}
void AdjustUp(HPDataType* p, int child)
{	
	assert(p);
	int parent = (child - 1) / 2;
	while (child >0)
	{
		//大于父亲节点就调整
		if (p[child] > p[parent])
		{
			HeapSwap(&p[child], &p[parent]);
			child = parent;
			parent = (child - 1) / 2;
		}
		else
			break;
	}
}
//插入节点也需要判断空间是否满了,满了就扩容
void HeapPush(HP* pc, HPDataType x)
{
	assert(pc);
	//扩容
	if (pc->capacity == pc->size)
	{
		int newcapacity = pc->capacity == 0 ? 4 : pc->capacity * 2;
		HPDataType* tmp = (HPDataType*)realloc(pc->p, sizeof(HPDataType) * newcapacity);
		if (tmp == NULL)
		{
			printf("realloc failed\n");
			exit(-1);
		}
		pc->p = tmp;
		pc->capacity = newcapacity;
	}
	pc->p[pc->size] = x;
	pc->size++;
	AdjustUp(pc->p, pc->size-1);
}

Heap is empty

When we need to delete nodes in the heap, we first need to determine whether the heap is empty and whether there are nodes. Just like the above heap insertion, we need to first determine whether the space is full.

bool HeapEmpty(HP* pc)
{
	assert(pc);

	return pc->size == 0;
}

heap delete pop

The deletion of the heap is the head deletion, but if the top element of the heap is deleted directly, it will destroy the original structure of the heap, so we can first exchange the elements at the top and the tail of the heap, then delete the tail, and then adjust it downward.

adjust down

Downward adjustment: The parent node is compared with the larger child node. If it is smaller than the child node, it will be adjusted downward. If the parent node is greater than or equal to the larger child node, it will be terminated.

insert image description here

insert image description here

Find out from the above figure:

The parent node is 0, the left child is 1=0*2+1

​ Right child: 2=0*2+2

The parent node is 1 Left child: 3=1*2+1

​ Right child: 4=1*2+2

At the same time, the left child +1 is the right child

So leftchild=parent*2+1

​ rightchild=leftchild+1

At the same time, paren=(child-1)/2, child: both left and right children are fine

The parent node is 0, the left child is 1=0*2+1

​ Right child: 2=0*2+2

The parent node is 1 Left child: 3=1*2+1

​ Right child: 4=1*2+2

At the same time, the left child +1 is the right child

So leftchild=parent*2+1
​ rightchild=leftchild+1
at the same time parent=(child-1)/2, child: left and right children are OK

AdjustDown(HPDataType* p, int n, int parent)
{	
	//左孩子
	int child = parent * 2 + 1;
	while (child < n)
	{	
		//判断是否有右孩子且右孩子大于左孩子
		if (child + 1 < n && p[child + 1] > p[child])
			++child;
		if (p[child] < p[parent])
		{
			HeapSwap(&p[child], &p[parent]);
			parent = child;
			child = parent * 2 + 1;
		}
		else
			break;
	}
}
void HeapPop(HP* pc)
{
	assert(pc && !HeapEmpty(pc));

	HeapSwap(&pc->p[pc->size - 1], &pc->p[0]);
	pc->size--;
	AdjustDown(pc->p, pc->size, 0);
}

heap destruction

void HeapDestroy(HP* pc)
{	
	assert(pc && !HeapEmpty(pc));
	//释放动态开辟的空间
	free(pc->p);
	pc->capacity = pc->size = 0;
}

operation result

A large heap formed by pushing the elements of the array in turn

insert image description here

A large pile formed at a time by pop

insert image description here

We have already implemented the large heap. If we want to replace it with a small heap, the parent node is always less than or equal to the child node.

1. In the upward adjustment, the parent node > the smaller child node is exchanged

2. In the downward adjustment, the parent node > the smaller child node is exchanged

time complexity analysis

The time complexity analysis of the heap, that is, the time complexity of the upward adjustment and downward adjustment algorithms, and their time complexity is related to the height of the tree.
Take downward adjustment as an example:
suppose the height of the tree is h:

Layer 1, 20 nodes, need to move down the h-1 layer

Layer 2, 2 1 nodes, need to move down h-2 layers

Layer 3, 2 2 nodes, need to move down h-3 layers

Layer 4, 2 3 nodes, need to move down h-4 layers

……

Layer h, 2 h-2 nodes, need to move down by h-4 layers

Then the total number of steps required to move the node is:

T(n)=20(h-1)+21(h-2)+22(h-3)+23(h-4)+2h-3*2+2h-2*1 1

2T(n)=21(h-1)+22(h-2)+23(h-3)+24*(h-4)+2h-2*2+2h-1*1 2

2-1 dislocation subtraction:

T(n)=1-h+21+22+23+24+……+2h-2+2h-1

T(n)=20+21+22+23+24+2h-2+2h-1-h

T(n)=2h-1-h

n=2h-1 h=log2(n+1)

T(n)=n-log 2 (n+1) is approximately equal to n

source code

Heap.h

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

typedef int HPDataType;

//实现大堆
typedef struct Heap
{
	HPDataType* p;
	int size;
	int capacity;
}HP;

//堆初始化
void HeapInit(HP* pc);
//堆中元素交换
void HeapSwap(HPDataType* p1, HPDataType* p2);
//打印元素
void HeapPrint(HP* pc);
//堆插入
void HeapPush(HP* pc, HPDataType x);
//堆删除
void HeapPop(HP* pc);
//堆销毁
void HeapDestroy(HP* pc);
//堆判空
bool HeapEmpty(HP* pc);
//向上调整
void AdjustUp(HPDataType* p, int child);
//向下调整
AdjustDown(HPDataType* p, int n, int parent);
//获取堆顶元素
HPDataType HeapTop(HP* pc);

Heap.c

#define _CRT_SECURE_NO_WARNINGS 1

#include "Heap.h"

void HeapInit(HP* pc)
{
    
    	
	assert(pc);
	pc->capacity = pc->size = 0;
	pc->p = NULL;
}

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

void HeapSwap(HPDataType* p1, HPDataType* p2)
{
    
    	
	assert(p1 && p2);
	HPDataType tmp = *p1;
	*p1 = *p2;
	*p2 = tmp;
}
void AdjustUp(HPDataType* p, int child)
{
    
    	
	assert(p);
	int parent = (child - 1) / 2;
	while (child >0)
	{
    
    
		//大于父亲节点就调整
		if (p[child] > p[parent])
		{
    
    
			HeapSwap(&p[child], &p[parent]);
			child = parent;
			parent = (child - 1) / 2;
		}
		else
			break;
	}
}

void HeapPush(HP* pc, HPDataType x)
{
    
    
	assert(pc);
	//扩容
	if (pc->capacity == pc->size)
	{
    
    
		int newcapacity = pc->capacity == 0 ? 4 : pc->capacity * 2;
		HPDataType* tmp = (HPDataType*)realloc(pc->p, sizeof(HPDataType) * newcapacity);
		if (tmp == NULL)
		{
    
    
			printf("realloc failed\n");
			exit(-1);
		}
		pc->p = tmp;
		pc->capacity = newcapacity;
	}
	pc->p[pc->size] = x;
	pc->size++;
	AdjustUp(pc->p, pc->size-1);
}

bool HeapEmpty(HP* pc)
{
    
    
	assert(pc);

	return pc->size == 0;
}

AdjustDown(HPDataType* p, int n, int parent)
{
    
    	
	//左孩子
	int child = parent * 2 + 1;
	while (child < n)
	{
    
    	
		//判断是否有右孩子且右孩子大于左孩子
		if (child + 1 < n && p[child + 1] > p[child])
			++child;
		if (p[child] > p[parent])
		{
    
    
			HeapSwap(&p[child], &p[parent]);
			parent = child;
			child = parent * 2 + 1;
		}
		else
			break;
	}
}
void HeapPop(HP* pc)
{
    
    
	assert(pc && !HeapEmpty(pc));

	HeapSwap(&pc->p[pc->size - 1], &pc->p[0]);
	pc->size--;
	AdjustDown(pc->p, pc->size, 0);
}
HPDataType HeapTop(HP* pc)
{
    
    
	assert(pc && !HeapEmpty(pc));
	return pc->p[0];
}

void HeapDestroy(HP* pc)
{
    
    	
	assert(pc && !HeapEmpty(pc));
	
	free(pc->p);
	pc->capacity = pc->size = 0;
}

Test.c

#define _CRT_SECURE_NO_WARNINGS 1

#include "Heap.h"

void Heap()
{
    
    
	int arr[6] = {
    
     70, 56, 30, 60, 25, 40};
	HP h;
	HeapInit(&h);
	//将数组的元素依次插入堆
	for (int i = 0; i < sizeof(arr) / sizeof(arr[0]); i++)
	{
    
    
		HeapPush(&h, arr[i]);
	}
	HeapPrint(&h);

	HeapPop(&h);
	HeapPrint(&h);

	HeapDestroy(&h);
}
int main()
{
    
    	
	Heap();

	return 0;
}

Guess you like

Origin http://43.154.161.224:23101/article/api/json?id=324208094&siteId=291194637