A simple implementation of binary trees and heaps

Table of contents

1: What is a tree

[1] The concept of tree

[2] Several other important concepts of trees

[3] Several representation methods of trees

Two: What is a binary tree

【1】Concept and features

[2] Two special binary trees

[3] The nature of the binary tree

[4] Two storage methods of binary tree

Three: Implementation of the heap


1: What is a tree

[1] The concept of tree

The sequence table and linked list we learned earlier belong to a linear structure, while a tree is a nonlinear data structure , which is a set of hierarchical relationships composed of n (n>=0) finite nodes . It is called a tree because it looks like an upside-down tree, which means it has the roots pointing up and the leaves pointing down .

Illustration:

(1) The tree has a special node called the root node, which is node A in the figure .

(2) Except the root node, other nodes are divided into M (M>0) mutually disjoint sets T1 , T2 , ... , Tm , and each set Ti (1<= i <= m) is a A subtree whose structure is similar to a tree . The root node of each subtree has one and only one predecessor, and can have zero or more successors.

(3) Parent-child nodes: For example, A is the parent node of B, C, and D, while E and F are the child nodes of B.

(4) Leaf node: a node without child nodes, such as E, F, C, G, H.

Note: The subtrees of the tree are disjoint, and other nodes except the root node have one and only one parent node.

[2] Several other important concepts of trees

Illustration:

(1) Degree of node: The number of subtrees contained in a node is called the degree of the node ; as shown in the figure above: A is 6
(2) Non-terminal nodes or branch nodes: nodes whose degree is not 0; as shown in the figure above: nodes such as D , E , F , G... are branch nodes
(3) Brother nodes: nodes with the same parent node are called brother nodes ; as shown in the above figure: B and C are brother nodes
(4) Degree of the tree: In a tree, the degree of the largest node is called the degree of the tree ; as shown above: the degree of the tree is 6
(5) The hierarchy of nodes: starting from the definition of the root, the root is the first layer, the child nodes of the root are the second layer , and so on;
(6) The height or depth of the tree: the maximum level of nodes in the tree ; as shown in the figure above: the height of the tree is 4
(7) Ancestors of nodes: from the root to all nodes on the branches of the node ; as shown in the figure above: A is the ancestor of all nodes
(8) Children and grandchildren: Any node in the subtree rooted at a certain node is called the descendant of the node . As shown above: all nodes are descendants of A
(9) Forest: A collection of m ( m>0) disjoint trees is called a forest.

[3] Several representation methods of trees

(1) Use the sequence table to store the address of the child node (complex structure)

(2) The degree of the tree (the degree of the largest node) has been explained, and a pointer array is set to store the address of the child node

(3) Structure array storage 

 (4) Notation of left child and right brother (commonly used, with a relatively simple structure and relatively clear logic)

Two: What is a binary tree

【1】Concept and features

concept:

A binary tree is a finite set of nodes, which is either empty or consists of a root node plus two binary trees called left and right subtrees .

Features:

1. Each node has at most two subtrees, that is, there is no node with degree greater than 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] Two special binary trees

1. Full binary tree:

All leaf nodes are in the last layer

All branch nodes have two children

2. Complete binary tree:

Assuming that this binary tree has N layers, the first N-1 layers must be full

The last layer can be full, but it must be continuous from left to right

[3] The nature of the binary tree

1. If the number of layers of the root node is specified as 1, then there are at most 2^(i-1) nodes on the i-th layer of a non-empty binary tree .
2. If the number of layers of the root node is specified as 1, then the maximum number of nodes in a binary tree with depth h is 2^h- 1 .
3. For any binary tree, if the degree is 0, the number of leaf nodes is n0 , and the number of branch nodes with degree 2 is n2 , then there is n0=n2+1.
4. If the number of layers of the root node is specified as 1, the depth of a full binary tree with n nodes, h=LogN.

[4] Two storage methods of binary tree

(1) Sequential structure storage (array)

Sequential structure storage is to use arrays to store . Generally, arrays are only suitable for representing complete binary trees , because not complete binary trees will waste space. In reality, only the heap will use arrays for storage . Binary tree sequential storage is physically an array and logically a binary tree.

(2) Chain structure storage

A linked list is used to represent a binary tree , that is, a chain is used to indicate the logical relationship of elements.

The usual method is that each node in the linked list is composed of three fields, the data field and the left and right pointer fields , and the left and right pointers are used to give the storage addresses of the link points where the left child and right child of the node are located.

The chain structure is further divided into binary chains and triple chains . At present, binary chains are generally used, and high-level data structures such as red-black trees will use triple chains.

Three: Implementation of the heap

Heaps are binary trees implemented with arrays, and are generally used to implement complete binary trees.

heaps are divided into large heaps and small heaps

Big pile: father >= child

Small pile: child >= father

This paper implements a lot of

The implementation of the heap is similar to the sequence table, so I won't go into details here, only the main interface implementation.

Attach the sequence table link: https://blog.csdn.net/2301_76269963/article/details/129352041?spm=1001.2014.3001.5501

【1】Insert data

(1) Judgment expansion is consistent with the sequence table.

(2) Store data, which is consistent with the sequence table.

(3) After data insertion, we must ensure that there are still large piles after insertion, so we must adjust the parent-child relationship.

Before considering adjustments, let's observe the relationship between the father's subscript and the child's subscript 

We can find such a rule

Father subscript = (child subscript - 1)/2. 

We design the adjustment function according to this rule. If the data to be inserted is larger than the parent , the two are replaced until it is smaller than the parent or becomes the root node .

Because the subsequent deletion also requires swap adjustment, we can package the swap separately into the function HeapSwap( ).

code:

[2] Delete data (the basis of heap sort)

Basic idea:

(1) The data deletion of the heap requires the deletion of root data .

(2) It should be noted that it cannot be overwritten directly like the sequence table, which will destroy the structure of the original heap .

Illustration:

(3) We need to delete the original root data and make adjustments .

Delete: We can exchange the root with the last leaf, and then directly reduce the size (number of valid data) by 1.

Illustration:

 Adjustment: Adjust downward from subscript 0. If the child is bigger than the father, exchange it until it is bigger than both children or the leaf has been adjusted . Before each judgment, compare the two children who is bigger to prevent Destroy the structure of the big heap , swap the older child with the parent , and iterate.

Illustration:

code:

 

Full code:

Heap.h (necessary header file inclusion, function and structure declarations)

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

typedef int HPDataType;
typedef struct Heap
{
	//存储数据
	HPDataType* a;
	//有效数据个数
	int size;
	//容量
	int capacity;
}HP;
//初始化
void HeapInit(HP* hp);
//销毁
void HeapDestory(HP* hp);
//交换函数
void HeapSwap(int* p1, int* p2);
//判空函数
bool HeapEmpty(HP* hp);
//调整函数
void AdjustUp(HPDataType* a,int child);
//向下调整,n是有效个数
void AdjustDown(HPDataType* a, int n, int parent);
//插入数据
void HeapPush(HP* hp, HPDataType x);
//打印数据
void HeapPrint(HP* hp);
//删除数据
void HeapPop(HP* hp);

Heap.c (interface implementation)

#define _CRT_SECURE_NO_WARNINGS 1
#include "Heap.h"
//初始化
void HeapInit(HP* hp)
{
	//断言,不能传空的结构体指针
	assert(hp);
	hp->a = NULL;
	//初始化size和容量都为0
	hp->size = hp->capacity = 0;
}

//销毁
void HeapDestory(HP* hp)
{
	free(hp->a);
	hp->size = hp->capacity = 0;
}

//交换函数
void HeapSwap(int* p1, int* p2)
{
	int tmp = *p1;
	*p1 = *p2;
	*p2 = tmp;
}

//判空函数
bool HeapEmpty(HP* hp)
{
	return hp->size == 0;
}

//调整函数
//向上调整
void AdjustUp(HPDataType* a, int child)
{
	//断言,不能传空指针
	assert(a);
	//找到父结点的下标
	int parent = (child - 1) / 2;
	//循环,以child到树根为结束条件
	while (child > 0)
	{
		//如果父结点比child下,交换并更新
		if (a[child] > a[parent])
		{
			HeapSwap(&a[child], &a[parent]);
			child = parent;
			parent = (child - 1) / 2;
		}
		//如果父结点比child大,跳出循环
		else
		{
			break;
		}
	}
}
//向下调整
void AdjustDown(HPDataType* a, int n, int parent)
{
	//默认左孩子最大
	int child = parent * 2 + 1;
	//当已经调整到超出数组时结束
	while (child<n)
	{
		//找出两个孩子中大的一方
		//考虑右孩子不存在的情况
		if (child+1<n&&a[child + 1] > a[child])
		{
			//如果右孩子大,child加1变成右孩子
			child++;
		}
		//如果父亲比大孩子小,进行调整,否则跳出
		if (a[child] > a[parent])
		{
			HeapSwap(&a[child], &a[parent]);
			//迭代
			parent = child;
			child = parent * 2 + 1;
		}
		else
		{
			break;
		}
	}
}

//插入数据
void HeapPush(HP* hp, HPDataType x)
{
	if (hp->size == hp->capacity)
	{
		//判断扩容多少
		int newcapacity = hp->capacity == 0 ? 4 : hp->capacity * 2;
		//扩容
		HPDataType* tmp =
			(HPDataType*)realloc(hp->a, sizeof(HPDataType) * newcapacity);
		//更新
		hp->capacity = newcapacity;
		hp->a = tmp;
	}
	//存储数据
	hp->a[hp->size] = x;
	hp->size++;
	//进行调整
	AdjustUp(hp->a, hp->size-1);
}

//打印数据
void HeapPrint(HP* hp)
{
	//断言,不能传空的结构体指针
	assert(hp);
	int i = 0;
	for (i = 0; i < hp->size; i++)
	{
		printf("%d ", hp->a[i]);
	}
	printf("\n");
}

//删除数据
void HeapPop(HP* hp)
{
	//断言,不能传空的结构体指针
	assert(hp);
	//如果为空,不能删除,避免数组越界
	assert(!HeapEmpty(hp));
	//不为空,先交换根和最后一片叶子,然后size减1
	HeapSwap(&hp->a[0], &hp->a[hp->size - 1]);
	hp->size--;
	AdjustDown(hp->a, hp->size, 0);
}

text.c (test)

#define _CRT_SECURE_NO_WARNINGS 1
#include "Heap.h"

void text1()
{
	HP hp;
	HeapInit(&hp);
	HPDataType a[] = { 70,30,56,25,15,10.85,79};
	int i = 0;
	for (i = 0; i < sizeof(a) / sizeof(a[0]); i++)
	{
		HeapPush(&hp, a[i]);
	}
	HeapPrint(&hp);
	HeapPop(&hp);
	HeapPrint(&hp);
	HeapDestory(&hp);
}

int main()
{
	text1();
}

Guess you like

Origin blog.csdn.net/2301_76269963/article/details/129941907