Data structure learning_05_binary tree_heap

1 tree

A tree is a non-linear data structure, which is a hierarchical collection composed of n finite nodes. It is called a tree because it has the roots up and the leaves down
.
Insert picture description here

1.1 Related nouns for trees

Insert picture description here

2 Binary tree

2.1 Concept of Binary Tree

A binary tree is a finite set of nodes. The set is either empty or consists of a root node plus two trees called the left subtree and the right subtree.
As shown in the figure:
Insert picture description here
Binary tree has the following characteristics:
1. Each binary tree has at most two subtrees, so there is no node with degree 2 in the binary tree.
2. The subtrees of the binary tree are divided into left and right, and the order of the subtrees cannot be reversed.

2.2 The nature of binary trees

1. If the number of root nodes is specified as the first layer, then there are at most z^(k-1) nodes on the i-th level of a non-empty binary tree.
2. If the number of root nodes is specified as the first At one level, the maximum number of nodes of a binary tree with depth h is 2^k-1.
3. For any binary tree, the number of nodes (leaf nodes) with degree 0 is n0, and the number of nodes with degree 2 is n2, so there must be n0 = n2 + 1.
4. If the level of root node is specified as the first level, the depth of a full binary tree with N nodes is h = log2(N+1) [Note: the logarithm of N+1 with base 2], this It can be derived from property 2.
5. For a complete binary tree with n nodes, if all nodes are numbered starting from 0 in the array order from top to bottom and left to right, that is, for the nodes of the array subscript i:
1) i>1 ,The subscript of the parent node at position i in the array is (i-1)/2.
2) The subscript of the left child node of the node at position i is 2 i+1, and the subscript of the right node is 2 i+2.

2.3 Special Binary Tree

1. Full binary tree: a binary tree, if the number of nodes at each level has reached the maximum, if the level of the binary tree is k, the number of nodes is 2^k-1.
2. Complete binary tree: In the most straightforward terms, the depth or height of the tree is k, the nodes of the first k-1 layer are full, and only the nodes of the last k-th layer are dissatisfied but from left to right Must be continuous.
In fact, a full binary tree is a special kind of complete binary tree.
As shown in the figure: the
Insert picture description here
picture is a complete binary tree, if the last layer is full, it is a full binary tree.
Satisfies:
2^k-1-x = N;
The value range of x is [0,2^(k-1)-1]

3 piles

3.1 The concept of heap

Ordinary binary trees are not suitable for storage with arrays, because there may be a lot of waste of space, and complete binary trees are more suitable for sequential storage. The random access feature of sequential storage will have huge advantages. We usually store the heap (which is a complete binary tree) in sequential order. 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 a data structure and the other is an area segment for managing memory in the OS .
The pile is divided into big root pile and small root pile.
Big root heap: father node>=child node
Small root heap: father node<=child node The
Insert picture description here
upper part is the logical structure of the heap, and the lower part is the physical structure

3.2 Implementation of the heap

First of all, to build the heap, we need to understand an algorithm called the downward adjustment algorithm. Let's take the small root heap as an example. We construct the complete binary tree shown in the figure as a small heap. This binary tree has a condition that the two subtrees of the root node are both small heaps before the downward adjustment algorithm can be performed.Insert picture description here

3.2.1 Downward adjustment algorithm

Insert picture description here
The left and right subtrees of the root node are all small piles, and the root node 27 and the smaller root node of the left and right subtrees exchange positions, and then proceed in sequence until the leaf node. Just float the smallest 15 nodes to the top of the pile. The premise of this algorithm is that the left and right subtrees of the root node are all small heaps. If we want to build any array into a small heap, it obviously does not satisfy the condition. Below we introduce the method of constructing arbitrary arrays into small piles.
The code for the downward adjustment algorithm is as follows:

void AdjustDown(HeapDataType* a, int n, int root)
{
    
    
	//父子下标的初始化
	int parent = root;
	int child = parent * 2 + 1;
	//循环向下调整,把最小值(或者最大值浮到堆顶)
	while (child < n)
	{
    
    
		//选出左右孩子中较小的孩子,作为child,child+1 < n保证下标不能越界
		if (child+1 < n && a[child + 1] < a[child])
		{
    
    
			++child;
		}
		//父亲比孩子小二者交换位置,并更新迭代孩子的位置
		if (a[child] < a[parent])
		{
    
    
			Swap(&a[child], &a[parent]);
			//迭代parent child
			parent = child;
			child = parent * 2 + 1;
		}
		else
		{
    
    
			break;//当孩子 >= 父亲的时候,满足小堆的条件,跳出循环节课,否则就会死循环
		}
	}
}

3.2.2 Heap construction

Insert picture description here
Turn the given data into a logical structure of a complete binary tree. As shown in the figure above, this array (binary tree) obviously cannot meet the ideal conditions of the downward adjustment algorithm, so we split the problem. For example, if you think about this problem first, put the left child The tree and the right subtree are all built into small piles. What if the left and right subtrees of the subtree are not small piles? Then, for the same reason, it is enough to construct it into subtrees. Then we can start from the leaves It’s okay to adjust the algorithm downwards while executing on each node. In fact, we don't have to start with leaf nodes because leaf nodes without subtrees can actually be regarded as small piles or large piles. So start the adjustment from the first non-leaf node.

Insert picture description here
That is, start adjusting from the one drawn by the purple circle in the figure, until the root node 93, an array will be built into a small pile (large pile).

for (int i = (n - 1 - 1) / 2; i >= 0; i--)
	{
    
    
		AdjustDown(a, n, i);
	}

(N-1-1)/2 is the subscript of the first non-leaf node.

3.2.3 Heap sort

With the above knowledge as a pavement, the heap sort can be well understood.
1. Construct the array into a large pile or a small pile. The data at the top of the pile is the maximum or minimum value. The data at the top of the pile is exchanged with the data at the last node position (the last element of the array). Then adjust the first n-1 nodes down to large or small piles. Sorting is completed until the last root node is left.
Just ascending order: build a large pile, and descending order: build a small pile, and you can take a closer look.
code show as below:


```c
//堆排序
void HeapSort(int* a, int n)
{
    
    
	//堆排序的第一步就是构建堆,构建堆的时间复杂度是O(n),此时是小堆
	for (int i = (n - 1 - 1) / 2; i >= 0; i--)
	{
    
    
		AdjustDown(a, n, i);
	}
	//如果是升序,构建大堆
	//如果是降序,构建小堆
	//是反着的,因为要和最后一个进行交换
	int end = n - 1;
	while (end>0)
	{
    
    
		//把堆顶(最小或者最大)和最后的的元素交换,然后从0到n-2继续向下调整
		//把次小(次大)的元素也选出来,直到剩最后一个,堆排序完成
		Swap(&a[0], &a[end]);
		AdjustDown(a, end, 0);
		--end;
	}
}


### 3.2.4 堆的的增删查改
声明:
```c
#pragma once
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
#include <string.h>
#include <windows.h>

typedef int HeapDataType;

typedef struct Heap
{
	HeapDataType* arr;
	int size;
	int capacity;
} Heap;

//堆的初始化,内部有堆的创建
void HeapInit(Heap* php, HeapDataType* a, int n);
//堆的销毁
void HeapDestory(Heap* php);
//堆的插入数据
void HeapPush(Heap* php, HeapDataType x);
//堆的删除数据
void HeapPop(Heap* php);
//获取堆顶数据
HeapDataType HeapTop(Heap* php);
//对传入的数组内的数据进行堆排序
void HeapSort(int* a, int n);

definition:

#include "Heap.h"
//堆是一种完全二叉树,用顺序存储(数组)比较好
//用于交换两个数据
void Swap(HeapDataType* n1, HeapDataType* n2)
{
    
    
	HeapDataType temp = *n1;
	*n1 = *n2;
	*n2 = temp;
}
//向下调整算法___老重要了,这是理解堆排序和topk问题以及堆这里相关题的基础
//向下调整结束的情况有两个一个是a[parent]<a[child],另一个是从堆顶到数组结束全部比较完
void AdjustDown(HeapDataType* a, int n, int root)
{
    
    
	//父子下标的初始化
	int parent = root;
	int child = parent * 2 + 1;
	//循环向下调整,把最小值(或者最大值浮到堆顶)
	while (child < n)
	{
    
    
		//选出左右孩子中较小的孩子,作为child,child+1 < n保证下标不能越界
		if (child+1 < n && a[child + 1] < a[child])
		{
    
    
			++child;
		}
		//父亲比孩子小二者交换位置,并更新迭代孩子的位置
		if (a[child] < a[parent])
		{
    
    
			Swap(&a[child], &a[parent]);
			//迭代parent child
			parent = child;
			child = parent * 2 + 1;
		}
		else
		{
    
    
			break;//当孩子 >= 父亲的时候,满足小堆的条件,跳出循环节课,否则就会死循环
		}
	}
}
//向上调整算法
//用在HeapPush中
void AdjustUp(HeapDataType* a, int n, 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;
		}
	}
}
//堆的初始化,内部有堆的创建
void HeapInit(Heap* php, HeapDataType* a, int n)
{
    
    
	HeapDataType* temp = (HeapDataType*)malloc(sizeof(HeapDataType)*n);
	if (temp)
	{
    
    
		php->arr = temp;
		
	}
	else
	{
    
    
		printf("内存申请失败!");
		exit(-1);
	}
	//将传进来的数组拷贝给malloc出来的空间,用来后续的堆的创建,删除,插入数据等操作
	memcpy(php->arr, a, sizeof(HeapDataType)*n);
	php->size = n;
	php->capacity = n;

	//把拷贝进来的数组,构建成堆
	//从倒数第一个非叶子节点进行构建(直接把数组画成一个完全二叉树可以直接由图得到第一个
	//非叶子节点的下标)
	for (int i = (n - 1 - 1) / 2; i >= 0; i--)
	{
    
    
		AdjustDown(php->arr, php->size, i);
	}
}
//堆排序
void HeapSort(int* a, int n)
{
    
    
	//堆排序的第一步就是构建堆,构建堆的时间复杂度是O(n),此时是小堆
	for (int i = (n - 1 - 1) / 2; i >= 0; i--)
	{
    
    
		AdjustDown(a, n, i);
	}
	//如果是升序,构建大堆
	//如果是降序,构建小堆
	//是反着的,因为要和最后一个进行交换
	int end = n - 1;
	while (end>0)
	{
    
    
		//把堆顶(最小或者最大)和最后的的元素交换,然后从0到n-2继续向下调整
		//把次小(次大)的元素也选出来,直到剩最后一个,堆排序完成
		Swap(&a[0], &a[end]);
		AdjustDown(a, end, 0);
		--end;
	}
}

//销毁堆
void HeapDestory(Heap* php)
{
    
    
	assert(php);
	free(php->arr);
	php->arr = NULL;
	php->capacity = php->size = 0;
}

//堆的插入数据
void HeapPush(Heap* php, HeapDataType x)
{
    
    
	assert(php);
	if (php->size == php->capacity)
	{
    
    
		php->capacity *= 2;
		HeapDataType* tmp = (HeapDataType*)realloc(php->arr, sizeof(HeapDataType)*php->capacity);
		if (tmp)
		{
    
    
			php->arr = tmp;
		}
		else
		{
    
    
			printf("扩容失败!\n");
			exit(-1);
		}
	}
	php->arr[php->size++] = x;
	AdjustUp(php->arr, php->size, php->size - 1);
}

//堆的删除数据,要删除堆顶的数据
void HeapPop(Heap* php)
{
    
    
	assert(php);
	assert(php->size > 0);
	Swap(&php->arr[0], &php->arr[php->size - 1]);
	php->size--;
	AdjustDown(php->arr, php->size, 0);
}

//获取堆顶数据
HeapDataType HeapTop(Heap* php)
{
    
    
	assert(php);
	assert(php->size > 0);
	return php->arr[0];
}

The friends here can test the code by themselves.

Guess you like

Origin blog.csdn.net/CZHLNN/article/details/112481962