Binary tree and heap

Table of contents

1. Binary tree

1. Concept and structure of binary tree

1.1. The concept of tree

1.2. The concept of binary tree

1.3. Storage structure of binary tree

1.3.1. Sequential structure storage

1.3.2. Chain structure storage

2. Implementation of binary tree (chain structure)

2.1. Binary tree traversal

2.1.1. Pre-order, in-order and post-order traversal

2.1.2. Layer-order traversal

2.2. Code implementation of binary tree chain structure

2. Heap

1. Concept and structure of heap

1.1. Concept of heap

1.2. Heap structure

2. Implementation of heap

2.1. Heap downward adjustment algorithm

2.2. Creation of heap

2.3. Insertion into the heap

2.4. Deletion of heap

2.5. Heap code implementation


1. Binary tree

1. Concept and structure of binary tree

1.1. The concept of tree

        Tree is a non-linear data structure, which is a collection of n limited nodes with hierarchical relationships. It is called a tree because its structure looks like an upside down tree. As shown below:

        Trees have the following properties:

        (1) The tree has a special node called the root node, and the root node has no predecessor node.

        (2) Except for the root node, the remaining nodes are divided into multiple disjoint sets, and each set is a subtree with a structure similar to that of a tree. The root node of each subtree has only one predecessor node and can have multiple successor nodes.

        (3) In the tree structure, there cannot be intersection between subtrees, otherwise it will not be a tree structure.

        (4). The tree is defined recursively.

1.2. The concept of binary tree

        Binary tree is a special tree structure. Each node of the binary tree has at most two successor nodes, which can be divided into left and right nodes. The order cannot be reversed. It is an ordered tree. A binary tree consists of three parts: the root node, the left subtree of the root node, and the right subtree of the root node.

        Two special binary trees:

        (1) Full binary tree: The nodes at each level of the binary tree reach the maximum value. This binary tree is a full binary tree. That is, if the number of levels of a binary tree is k and the number of summary points is 2^k-1, it is a full binary tree.

        (2) Complete binary tree: All other layers of the binary tree except the last layer conform to the properties of a full binary tree, and the last layer is continuous from left to right. The binary tree is a complete binary tree. A full binary tree is a special kind of complete binary tree.

1.3. Storage structure of binary tree

        Binary trees can generally use two structures for data storage: one is a sequential structure and the other is a chain structure.

1.3.1. Sequential structure storage

        Sequential structure storage is to use arrays for storage. This storage method is only suitable for complete binary trees, because using sequential storage to store incomplete binary tree data will cause a waste of space. In actual applications, only the heap uses sequential storage. The content of the heap will be discussed in the next section and will not be introduced here.

typedef struct Heap
{
	HPDataType* a;    //指向动态开辟的数组的指针
	int size;        //存储的数据个数
	int capacity;    //数组的容量
}Heap;

1.3.2. Chain structure storage

        The linked storage of a binary tree refers to using a linked list to represent the logical relationship between elements. The usual method is that each node in the linked list consists of three parts, namely a data field and two left and right pointer fields. The left and right pointers are used to store the addresses of the left child node and the right child node of the node respectively.

typedef struct BinaryTreeNode
{
	BTDataType data;    //该节点存储的数据
	struct BinaryTreeNode* left;    //指向左孩子节点
	struct BinaryTreeNode* right;    //指向右孩子节点
}BTNode;

2. Implementation of binary tree (chain structure)

2.1. Binary tree traversal

        Binary tree traversal refers to performing corresponding operations on the nodes in the binary tree in sequence according to certain specific rules, and each node is operated only once. Traversal is the basis for other operations on binary trees.

2.1.1. Pre-order, in-order and post-order traversal

        (1) Pre-order traversal: The operation of accessing the root node occurs before traversing its left and right subtrees.

// 二叉树前序遍历 
void BinaryTreePrevOrder(BTNode* root)
{
	if (root == NULL)
	{
		return;
	}
	printf("%c", root->data);
	BinaryTreePrevOrder(root->left);
	BinaryTreePrevOrder(root->right);
}

        (2) In-order traversal: The operation of accessing the root node occurs between traversing its left and right subtrees.

// 二叉树中序遍历
void BinaryTreeInOrder(BTNode* root)
{
	if (root == NULL)
	{
		return;
	}
	BinaryTreePrevOrder(root->left);
	printf("%c", root->data);
	BinaryTreePrevOrder(root->right);
}

        (3) Post-order traversal: The operation of accessing the root node occurs after traversing its left and right subtrees.

// 二叉树后序遍历
void BinaryTreePostOrder(BTNode* root)
{
	if (root == NULL)
	{
		return;
	}
	BinaryTreePrevOrder(root->left);
	BinaryTreePrevOrder(root->right);
	printf("%c", root->data);
}

Take preorder traversal as an example to graphically analyze the recursive traversal process:

2.1.2. Layer-order traversal

        Different from pre-order, in-order and post-order traversal, level-order traversal traverses the tree from top to bottom (that is, starting from the first level where the root node is located, and visiting it downwards layer by layer), and the same layer is visited from left to The process of accessing the nodes of the tree in right order.

(The functions called in the code are given in the next section of this article)

// 层序遍历
void BinaryTreeLevelOrder(BTNode* root)
{
	Queue q;
	QueueInit(&q);
	if (root)
	{
		QueuePush(&q, root);
	}
	while (!QueueEmpty(&q))
	{
		BTNode* front = QueueFront(&q);
		QueuePop(&q);
		printf("%c", front->data);
		if (front->left)
		{
			QueuePush(&q, front->left);
		}
		if (front->right)
		{
			QueuePush(&q, front->right);
		}
	}
	printf("\n");
	QueueDestroy(&q);
}

The traversal process is as shown below:

2.2. Code implementation of binary tree chain structure

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

typedef char BTDataType;
typedef struct BinaryTreeNode
{
	BTDataType data;
	struct BinaryTreeNode* left;
	struct BinaryTreeNode* right;
}BTNode;

// 链式结构:表示队列成员节点
typedef BTNode* QDataType;
typedef struct QListNode
{
	struct QListNode* next;
	QDataType data;
}QNode;

// 队列的结构 
typedef struct Queue
{
	QNode* front;
	QNode* rear;
	int size;
}Queue;

// 初始化队列 
void QueueInit(Queue* q)
{
	assert(q);
	q->front = q->rear = NULL;
	q->size = 0;
}

// 检测队列是否为空,如果为空返回非零结果,如果非空返回0 
int QueueEmpty(Queue* q)
{
	assert(q);
	return q->size == 0 ? 1 : 0;
}

// 获取队列头部元素 
QDataType QueueFront(Queue* q)
{
	assert(q);
	assert(!QueueEmpty(q));
	return q->front->data;
}

// 队尾入队列 
void QueuePush(Queue* q, QDataType data)
{
	assert(q);

	QNode* NewNode = (QNode*)malloc(sizeof(QNode));
	if (NewNode == NULL)
	{
		perror("malloc:");
		return;
	}
	NewNode->data = data;
	NewNode->next = NULL;
	if (q->size == 0)
	{
		q->front = q->rear = NewNode;
	}
	else
	{
		q->rear->next = NewNode;
		q->rear = q->rear->next;
	}
	q->size++;
}


// 队头出队列 
void QueuePop(Queue* q)
{
	assert(q);
	//assert(!QueueEmpty(q));
	if (q->front == q->rear)
	{
		if (q->front == NULL)
		{
			return;
		}
		else
		{
			free(q->front);
			q->front = q->rear = NULL;
		}
	}
	else
	{
		QNode* Tmp = q->front;
		q->front = Tmp->next;
		free(Tmp);
		Tmp = NULL;
	}
	q->size--;
}

// 销毁队列 
void QueueDestroy(Queue* q)
{
	assert(q);
	if (q->size != 0)
	{
		free(q->front);
		free(q->rear);
		q->front = q->rear = NULL;
	}
}

//创建节点
BTNode* BuyNode(BTDataType x)
{
	BTNode* Node = (BTNode*)malloc(sizeof(BTNode));
	if (Node == NULL)
	{
		perror("BuyNode::malloc:");
		return NULL;
	}
	Node->data = x;
	Node->left = NULL;
	Node->right = NULL;
	return Node;
}

// 通过前序遍历的数组"ABD##E#H##CF##G##"构建二叉树
BTNode* BinaryTreeCreate(BTDataType* a, int* pi)
{
	if (a[*pi] == '#')
	{
		(*pi)++;
		return NULL;
	}
	BTNode* root = BuyNode(a[*pi]);
	(*pi)++;
	root->left = BinaryTreeCreate(a, pi);
	root->right = BinaryTreeCreate(a, pi);
	return root;
}

// 二叉树销毁
void BinaryTreeDestory(BTNode* root) 
{
	if (root == NULL)
	{
		return;
	}
	BinaryTreeDestory(root->left);
	BinaryTreeDestory(root->right);
	free(root);
}

// 二叉树节点个数
int BinaryTreeSize(BTNode* root)
{
	if (root == NULL)
	{
		return 0;
	}
	return BinaryTreeSize(root->left) + BinaryTreeSize(root->right) + 1;
}

// 二叉树叶子节点个数
int BinaryTreeLeafSize(BTNode* root)
{
	if (root == NULL)
	{
		return 0;
	}
	if (root->left == NULL && root->right == NULL)
	{
		return 1;
	}
	return BinaryTreeLeafSize(root->left) + BinaryTreeLeafSize(root->right);
}

// 二叉树第k层节点个数
int BinaryTreeLevelKSize(BTNode* root, int k)
{
	if (root == NULL)
	{
		return 0;
	}
	if (k == 1)
	{
		return 1;
	}
	return BinaryTreeLevelKSize(root->left, k - 1) + BinaryTreeLevelKSize(root->right, k - 1);
}

// 二叉树查找值为x的节点
BTNode* BinaryTreeFind(BTNode* root, BTDataType x)
{
	if (root == NULL)
	{
		return NULL;
	}
	if (root->data == x)
	{
		return root;
	}
	return BinaryTreeFind(root->left, x) || BinaryTreeFind(root->right, x);
}

// 二叉树前序遍历 
void BinaryTreePrevOrder(BTNode* root)
{
	if (root == NULL)
	{
		return;
	}
	printf("%c", root->data);
	BinaryTreePrevOrder(root->left);
	BinaryTreePrevOrder(root->right);
}

// 二叉树中序遍历
void BinaryTreeInOrder(BTNode* root)
{
	if (root == NULL)
	{
		return;
	}
	BinaryTreePrevOrder(root->left);
	printf("%c", root->data);
	BinaryTreePrevOrder(root->right);
}

// 二叉树后序遍历
void BinaryTreePostOrder(BTNode* root)
{
	if (root == NULL)
	{
		return;
	}
	BinaryTreePrevOrder(root->left);
	BinaryTreePrevOrder(root->right);
	printf("%c", root->data);
}

// 层序遍历
void BinaryTreeLevelOrder(BTNode* root)
{
	Queue q;
	QueueInit(&q);
	if (root)
	{
		QueuePush(&q, root);
	}
	while (!QueueEmpty(&q))
	{
		BTNode* front = QueueFront(&q);
		QueuePop(&q);
		printf("%c", front->data);
		if (front->left)
		{
			QueuePush(&q, front->left);
		}
		if (front->right)
		{
			QueuePush(&q, front->right);
		}
	}
	printf("\n");
	QueueDestroy(&q);
}

// 判断二叉树是否是完全二叉树
bool BinaryTreeComplete(BTNode* root)
{
	Queue q;
	QueueInit(&q);
	if (root)
	{
		QueuePush(&q, root);
	}
	while (!QueueEmpty(&q))
	{
		BTNode* front = QueueFront(&q);
		QueuePop(&q);

		//遇到空就跳出
		if (front == NULL)
		{
			break;
		}
		QueuePush(&q, front->left);
		QueuePush(&q, front->right);
	}

	//检查后面节点是否全为空,若存在非空即非完全二叉树
	while (!QueueEmpty(&q))
	{
		BTNode* front = QueueFront(&q);
		QueuePop(&q);
		if (front)
		{
			QueueDestroy(&q);
			return false;
		}
	}
	QueueDestroy(&q);
	return true;
}

2. Heap

1. Concept and structure of heap

1.1. Concept of heap

        The heap is a complete binary tree. The value of each node in the heap is always no greater than (large heap) or no less than (small heap) the value of the parent node. That is, the root node of a large heap is the largest, and the root node of a small heap is the smallest. There are only two types of heaps: large heaps and small heaps. If a complete binary tree has a situation where the value of the parent node is greater than the value of the child node, and there is also a situation where the value of the child node is greater than the value of the parent node, then the complete binary tree is not a heap.

1.2. Heap structure

        The heap is stored using an array of sequential structure, that is, the physical structure of the heap is an array, and its logical structure is a complete binary tree.

typedef struct Heap
{
	HPDataType* a;
	int size;
	int capacity;
}Heap;

2. Implementation of heap

2.1. Heap downward adjustment algorithm

        An array is logically a complete binary tree. We can adjust it into a heap through the downward adjustment algorithm starting from the root node. The premise of the downward adjustment algorithm is that both the left and right subtrees must be a heap, and a heap can be formed by simply adjusting the root node downward to the appropriate position.

2.2. Creation of heap

        A complete binary tree can be constructed as a heap by either a downward adjustment algorithm or an upward adjustment algorithm. However, whether it is a downward adjustment algorithm or an upward adjustment algorithm, all nodes except the node to be adjusted must satisfy the heap properties, so we can only adjust downward from the subtree of the first non-leaf node from the last to The root node can be adjusted into a heap.

2.3. Insertion into the heap

        Heap insertion is to insert the element to be inserted after the last child node of the heap. If the properties of the heap are destroyed after the insertion, the newly inserted element needs to be adjusted upward until the properties of the heap are met again. Note: The premise for upward adjustment is that except for the leaf node, all other nodes above the leaf node satisfy the properties of the heap.

2.4. Deletion of heap

        Heap deletion deletes the top element of the heap by default. Exchange the top data of the heap with the last data of the heap, then delete the last data, and then adjust the root node downward until it meets the properties of the heap. (You can refer to the illustration of the downward adjustment algorithm, and the analysis will not be repeated here)

2.5. Heap code implementation

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


typedef int HPDataType;
typedef struct Heap
{
	HPDataType* a;
	int size;
	int capacity;
}Heap;

//堆的初始化
void HeapInitial(Heap* hp)
{
	assert(hp);
	hp->a = NULL;
	hp->size = 0;
	hp->capacity = 0;
}

// 堆的构建
void HeapCreate(Heap* hp, HPDataType* a, int n)
{
	//assert(hp);
	HPDataType* tmp = (HPDataType*)malloc(sizeof(HPDataType) * n);
	if (tmp == NULL)
	{
		perror("HeapCreate::malloc:");
		return;
	}
 	hp->a = tmp;
	hp->capacity = n;
	hp->a[0] = a[0];
	for (int i = 1; i < n; i++)
	{
		int child = i;
		hp->a[child] = a[child];
		while (child>0)
		{
			int parent = (child - 1) / 2;
			//小堆:
			if (hp->a[child] < hp->a[parent])
			{
				HPDataType tmp = hp->a[parent];
				hp->a[parent] = hp->a[child];
				hp->a[child] = tmp;
				child = parent;
			}
			else
			{
				break;
			}
		}
	}
	hp->size = n;
}

// 堆的判空(如果为空返回1,非空返回0)
int HeapEmpty(Heap* hp)
{
	assert(hp);
	if (hp->size == 0)
	{
		return 1;
	}
	return 0;
}

// 取堆顶的数据
HPDataType HeapTop(Heap* hp)
{
	assert(hp);
	if (!HeapEmpty(hp))
	{
		return hp->a[0];
	}
	return -1;
}

// 堆的数据个数
int HeapSize(Heap* hp)
{
	assert(hp);
	return hp->size;
}

// 堆的插入
void HeapPush(Heap* hp, HPDataType x)
{
	assert(hp);
	if (hp->size == hp->capacity)
	{
		int newcapacity = hp->capacity == 0 ? 5 : 2 * hp->capacity;
		HPDataType* tmp = (HPDataType*)realloc(hp->a,sizeof(HPDataType) * newcapacity);
		if (tmp == NULL)
		{
			perror("HeapPush::realloc:");
			return;
		}
		hp->capacity = newcapacity;
	}
	hp->a[hp->size] = x;
	hp->size++;

	//向上调整算法
	int child = hp->size - 1;
	while (child > 0)
	{
		int parent = (child - 1) / 2;
		if (hp->a[child] < hp->a[parent])
		{
			HPDataType tmp = hp->a[parent];
			hp->a[parent] = hp->a[child];
			hp->a[child] = tmp;
			child = parent;
		}
		else
		{
			break;
		}
	}
}

// 堆的删除(默认删除堆顶元素)
void HeapPop(Heap* hp)
{
	assert(hp);
	if (HeapEmpty(hp))
	{
		return;
	}

	//交换首尾元素
	HPDataType tmp = hp->a[0];
	hp->a[0] = hp->a[hp->size - 1];
	hp->a[hp->size - 1] = tmp;

	//删除最后一个元素
	hp->size--;

	//向下调整算法
	int parent = 0;
	while (parent <= (hp->size - 2) / 2)
	{
		int childleft = 2 * parent + 1;
		int childright = 2 * parent + 2;
		int small = hp->a[childleft] < hp->a[childright] ? childleft : childright;
		if (hp->a[parent] > hp->a[small])
		{
			HPDataType tmp1 = hp->a[parent];
			hp->a[parent] = hp->a[small];
			hp->a[small] = tmp1;
			parent = small;
		}
		else
		{
			break;
		}
	}
	for (int i = 1; i < hp->size; i++)
	{
		int child = i;
		while (child > 0)
		{
			int parent = (child - 1) / 2;
			//小堆:
			if (hp->a[child] < hp->a[parent])
			{
				HPDataType tmp = hp->a[parent];
				hp->a[parent] = hp->a[child];
				hp->a[child] = tmp;
				child = parent;
			}
			else
			{
				break;
			}
		}
	}
}

// 堆的销毁
void HeapDestory(Heap* hp)
{
	assert(hp);
	free(hp->a);
	hp->a = NULL;
	hp->capacity = 0;
}

Guess you like

Origin blog.csdn.net/libj2023/article/details/132644219