[Data structure: Heap] Large root heap, small root heap, heap upward adjustment algorithm, downward adjustment algorithm and heap function implementation!

Preface

This series of articles [数据结构] will use C/C++ for design and implementation by default! For implementation methods in other languages, please refer to the analysis and design ideas to implement it yourself!


注[1]:文章属于学习总结,相对于课本教材而言,不具有相应顺序性!(可在合集中自行查看是否存在相应文章)!
注[2]:如有问题或想让博主进行思路分析的内容,可在后台私信!



Understanding of complete binary trees

  • The definition of a complete binary tree: A binary tree with n nodes is numbered in hierarchical order. If the node number is i (1 <= i <= n) and the node number i in a full binary tree of the same depth is in the binary tree If the positions in are exactly the same, then this binary tree is called a complete binary tree.
  • A simple understanding of a complete binary tree (characteristics described in vernacular): Except for the bottom layer, all other layers are full of nodes (constituting a full binary tree). The bottom layer must satisfy a binary tree that does not contain empty leaf nodes from left to right!

Insert image description here


Basic understanding of heap

  • Heap is a special type of data structure in computer science and is the most efficient priority queue.
  • The heap is usually an array object that can be viewed as a tree 完全二叉树 .

Insert image description here

The second line of formula in the above picture describes:堆的特性:堆中某个结点的值总是不大于或不小于其父结点的值!


The nature of the heap and the size of the root heap [Important]

  • 堆中某个结点的值总是不大于或不小于其父结点的值!

  • 堆总是一棵完全二叉树!

  • 大根堆:即根节点的值最大!

  • 小根堆:即根节点的值最小!

Insert image description here


The structure of the heap and its sequential structure (features)

Understanding the structure of the heap

  • Logically, one of the properties of a heap is that the heap must be a complete binary tree!
  • In terms of storage structure, due to the hierarchical "arrangement characteristics" of a complete binary tree, we generally use arrays or other sequential storage structures as storage objects to simulate a heap!

Insert image description here

sequential storage structure

From the graphical structure of complete binary numbers, it is not difficult to see that if we traverse them in layer order and arrange them in a row, we can form an array structure that does not contain empty nodes (numeric values)!

Insert image description here

As shown in the picture above,将根节点存储在索引值为:0 的位置! (has the following characteristics!)

If the node with index i has left and right child nodes, then:

  • 左子树结点索引:2 * i + 1
  • 右子树结点索引:2 * i + 2

If it is known that: the index value of the left/right child node is: n, then:

  • 父节点索引为:(n-1) / 2

Adjust the algorithm upwards

Insert image description here

Basic idea of ​​the algorithm (taking small root heap as an example):

  1. Find the node that does not conform to the heap properties! Recorded as: target node! As shown in the picture above: 0.
  2. Compare the values ​​of目标结点 with its父节点!
  • If the target node value 小于 is the value of the parent node, then perform parent-child exchange!
  • If the value of the target node is greater than the value of its parent node, the upward adjustment will stop. At this time, the tree is already a small heap.

As shown above, the process description:

  • The first time, 0 < 8, exchange 0 and 8. At this time, the original 8 position is the original target value!
  • The second time, 0 < 4, exchange 0 and 4,

    As shown in the figure above, the target value 0 must be adjusted upward to The root node position of the entire tree!

How to confirm index value in exchange:

  • If it is known that the index value of the left/right child node is: n, then:
    • The parent node index is: (n-1) / 2

C/C++ language code design

  • Since there is no container in the C language, we need to dynamically apply for a piece of memory as an array to store our data elements (the dynamic memory application part will be implemented later).
  • C++ can directly use vector as a container to store data.
void Swap(DataType* x, DataType* y)
{
    
    
	DataType tmp = *x;
	*x = *y;
	*y = tmp;
}

/* 向上调整算法 */
// void AdjustUp(vector<DataType>& vec, int idx)	// C++
void AdjustUp(DataType* vec, int idx)
{
    
    
	int parent = (idx-1) / 2;	// 记录当前结点的父节点位置!
	while(idx > 0){
    
    	
	// 循环条件:目标节点的位置必须合法!
	// 注:当目标节点索引为 1 或 2 时,若发生交换则一定会被调整到 0 处!
		// 小根堆为例:特点:父小于子!
		if( vec[idx] < vec[parent] ){
    
    
			Swap(&vec[idx], &vec[parent]);	// 值交换
			idx = parent;			// 更新目标值的索引!
			parent = (idx-1) / 2;	// 更新父节点的索引!
		}else break;		
	}
}


Adjust algorithm downwards

Insert image description here

Basic idea of ​​the algorithm (taking the large root heap as an example):

The downward adjustment algorithm needs to meet a premise:
 If you want to adjust it to a small heap, then the left and right subtrees of the root node must both be small heaps.
 If you want to adjust it to a large heap, then the left and right subtrees of the root node must both be large heaps.

  1. Find the node that does not conform to the heap properties! Recorded as: target node! As shown in the picture above: 20.
  2. Compare the values ​​of目标结点 with its较大子节点! (Big root pile); Compare the values ​​of 目标结点 and 较小子节点! (small root pile).
  3. Take the large root heap as an example. If the target node value (parent) 小于 is larger than the value of the child node, parent-child exchange will be performed!

Using the downward adjustment algorithm of the heap, in the worst case (that is, nodes need to be exchanged all the time), the number of loops required is: h - 1 (h is the height of the tree). And h = log2(N+1) (N is the number of summary points of the tree). so堆的向下调整算法的时间复杂度为:O(logN) 。


As shown above, the process description:

  • The first time, 9 < 36, the maximum value is: 36! 20 < 36, exchange 20 and 36. At this time, the original 36 position is the original target value!
  • The second time, -54 < 10, the maximum value is: 10! 20 > 10, the adjustment is over!

How to confirm index value in exchange:

  • If it is known that the index value of the parent node is: n, then:
    • Left subtree node index: 2 * n + 1
    • Right subtree node index: 2 * n + 2

C/C++ language code design

void Swap(DataType* x, DataType* y)
{
    
    
	DataType tmp = *x;
	*x = *y;
	*y = tmp;
}

/* 向下调整算法:大根堆 */
// void AdjustUp(vector<DataType>& vec, int size, int idx)	// C++
// 参数:size:数组的大小
void AdjustDown(DataType* vec, int size, int idx){
    
    
	int child = idx*2+1;	// child 表示子树索引!
	// 此处假设较大值为:左子节点
	while( child < size ){
    
    
		// 判断 左右子结点的大小关系
		// 大根堆:选较大的
		// 小根堆:选较小的
		if( child+1 < size && vec[child+1] > vec[child] ) child++;
		if( vec[idx] < vec[child]){
    
    
			//将父结点与较大的子结点交换
			Swap(&vec[child], &vec[idx]);
			//继续向下进行调整
			idx= child;
			child = 2 * idx+ 1;
		}else break;
	}
}

Any binary tree => Heap (concern): How to ensure that the subtree must be a large/small heap?

There is a constraint in the previous downward adjustment algorithm!

The downward adjustment algorithm needs to meet a premise:
 If you want to adjust it to a small heap, then the left and right subtrees of the root node must both be small heaps.
 If you want to adjust it to a large heap, then the left and right subtrees of the root node must both be large heaps.


Question: How to ensure that the subtree must be a large/small heap?

  • Based on the characteristics of the heap, 根节点要么总是小于左右子结点,要么总是大于左右子结点! as long as any subtree conforms to this characteristic!
  • 实现思路:从倒数第一个课子树开始,即尾结点的父结点开始!进行向下调整即可!

The figure below demonstrates adjusting to a small root heap as an example!

Insert image description here

Code:

void ToHeap(DataType* vec, int size){
    
    
	for(int i = (size - 1 - 1) / 2;i >= 0; i--)
		AdjustDown(vec, size, i);
}

Design and implementation of heap

  • This article will use C design to implement a sequential storage heap!

Storage structure design

Taking the C language as an example to make the heap applicable to more data types, we adopt the following design method!

// 堆中的类型设定
typedef int DataType;

// 数据类型的设计
typedef struct _Heap{
    
    
	DataType* heapArray;	// 底层数据存储依托于数组
	int size;				// 记录当前堆中的元素个数
	int capacity;			// 记录当前栈的最大可存储的元素个数!!!
}Heap;

initialize heap

  • 此处的设计思路为:将已有的序列调整成堆!

What needs to be done:

  • Memory application
  • data copy
  • Heap adjustment: any binary tree => heap
/*
Heap* heap		:输出型参数,构建堆
DataType* vec	:数据序列
*/
void InitHeap(Heap* heap, DataType* vec, int size){
    
    
	assert(heap);
	// 内存申请
	DataType* temp = (DataType*)malloc(sizeof(DataType)*size);
	if(temp == NULL){
    
    
		printf("malloc fail\n");
		exit(-1);
	}
	// 堆结构的初始化
	heap->heapArray = temp;
	heap->size = size;
	heap->capacity = size;

	// 数据拷贝
	memcpy(heap->heapArray, vec, sizeof(DataType)*size);

	// 堆的调整:任意二叉树 => 堆
	for(int i = (size-1-1)/2; i>=0;i--)
		AdjustDown(heap->heapArray, size, i);
}

Insert data (involving expansion)

Ideas for data insertion:

  • For arrays, the location for efficient data addition and deletion operations must be at the tail!
  • 数据插入策略:先把新元素放置在数组尾部!再使用向上调整算法,进行堆调整!

Operating procedures:

  • Check the number of data elements in the heap (whether expansion is needed)
  • Heap adjustment (tail insertion data => upward adjustment algorithm)

About expansion:


In the initialization of the heap, we just applied for an array with the same size as the data source sequence to store data!

  • If there is no data insertion, the space utilization is the highest!
  • If data is inserted, expansion is required!

Conditions for expansion:

  • 堆中的数据元素个数 = 堆可存储的最多元素个数

Regarding the expansion strategy: The main purpose of this article is to simulate the implementation of the heap, so the usage of space is not considered here!

  • The default expansion strategy is: 2x expansion!
void Insert(Heap* heap, DataType val){
    
    
	assert(heap);
	// 是否扩容检查
	if(heap->size == heap->capacity){
    
    
		 DataType* temp= (DataType*)realloc( heap->heapArray, 2 * (heap->capacity) * sizeof(DataType) );
		 if(temp == NULL){
    
    
			printf("insert fail, may be realloc fail");
			return;
		 }
		 heap->heapArray = temp;
		 heap->capacity *= 2;
	}
	heap->heapArray[heap->size] = val;
	heap->size++;
	AdjustUp(heap->heapArray, heap->size-1);
}

Heap destruction

Regarding the destruction of the heap, you only need to grasp that the memory requested for dynamic memory needs to be released using free. In order to prevent wild pointers from appearing, the pointers need to be set empty!

/销毁堆
void HeapDestroy(Heap* heap)
{
    
    
	assert(heap);
	free(heap->heapArray);  	//释放动态开辟的数组
	php->a = NULL;				//防止野指针出现
	php->size = 0;				//元素个数置0
	php->capacity = 0;			//容量置0
}

Determine whether the heap is empty

The key to whether the heap is empty:堆中的元素个数是否为:0!
There is a bool type in subsequent versions of the C language, but for better adaptability, we use the int type as the basis for judgment!

int IsEmpty(Heap* heap){
    
    
	if(heap->size == 0) return 1;	// 空,返回:1
	return 0;						// 非空,返回:0
}

Get the top element of the heap

uses an array as a storage container. The top element of the heap is naturally the root node, which is stored at the index: 0.
Note: Make sure the heap is not empty!

int HeapTop(Heap* heap){
    
    
	// 非空才有堆顶!
	assert(heap->size != 0);
	return heap->heapArray[0];
}


Delete elements (note)

important point:

  • 堆的删除:是指删除堆顶元素!
  • The prerequisite for deletion: there are elements!

Heap deletion strategy:

  • Swap the values ​​of the top and bottom elements of the heap!
  • Adjust the exchanged value downward!
  • Modify the number of elements in the heap (size–)
void HeapDel(Heap* heap){
    
    
	assert(heap);
	assert(!IsEmpty(heap));
	// 交换元素值:堆顶元素 与 尾元素
	Swap(&heap->heapArray[0], &heap->heapArray[size-1]);
	heap->size--;
	AdjustDown(heap->heapArray, heap->size, 0);
}

Get the number of elements in the heap

int HeapSize(Heap* heap){
    
    
	return heap->size;
}

Conclusion

This article is just a simple design to implement heap-related operations! Heap application or algorithm questions will be updated in the future!

Guess you like

Origin blog.csdn.net/weixin_53202576/article/details/133777442