本章目录
1.二叉树的概念及结构
1.1 概念
一棵二叉树是结点的一个有限集合,该集合或者为空,或者是由一个根节点加上两棵别称为左子树和右子树的二叉树组成。
二叉树的特点:
1.每个结点最多有两棵子树,即二叉树不存在度大于2的结点。
2.二叉树的子树有左右之分,其子树的次序不能颠倒。
1.2 二叉树的结构
我们常见的二叉树的结构叫逻辑结构如下图1,这是我们自己想象出来的二叉树的样子,但实际上二叉树并不是就像图中所示的那样,二叉树真正的结构,也叫物理结构,是像数组一样每个数据一行排开,如图2,图2所示及是实实在在
的在内存中存储的样子。
图1
图2
1.3 特殊的二叉树
1.满二叉树:一个二叉树,如果每一个层的结点数都达到最大值,则这个二叉树就是满二叉树。也就是说,如果一个二叉树的层数为K,且结点总数是(2^k) -1 ,则它就是满二叉树。
2.完全二叉树:完全二叉树是效率很高的数据结构,完全二叉树是由满二叉树而引出来的。对于深度为K的,有n个结点的二叉树,当且仅当其每一个结点都与深度为K的满二叉树中编号从1至n的结点一一对应时称之为完全二叉树。 要注意的是满二叉树是一种特殊的完全二叉树。
1.4二叉树的存储结构
二叉树一般可以使用两种存储结构,一种顺序结构,一种链式结构。
1.4.1 顺序存储
顺序结构存储就是使用数组来存储,一般使用数组只适合表示完全二叉树,因为不是完全二叉树会有空间的浪费。而现实中使用中只有堆才会使用数组来存储。二叉树顺序存储在物理上是一个数组,在逻辑上是一颗二叉树。
但二叉树不适合用顺序存储结构,因为顺序存储结构不够灵活,每当该二叉树是非完全二叉树时,用顺序存储结构存储会使数组中留下空位,造成空间浪费,如下图,若该二叉树的数据有很多,那么就会造成很大一片的空间浪费。
1.4.2 链式存储
二叉树的链式存储结构是指,用链表来表示一棵二叉树,即用链来指示元素的逻辑关系。 通常的方法是链表中每个结点由三个域组成,数据域和左右指针域,左右指针分别用来给出该结点左孩子和右孩子所在的链结点的存储地址 。
链式存储是我们常用来存储二叉树的存储结构,该存储结构完美的解决了顺序存储空间浪费的问题,如下图。
1.5 二叉树的重要性质
1.若规定根节点的层数为1,则一棵非空二叉树的第i层上最多有2^(i-1) 个结点.
2.若规定根节点的层数为1,则深度为h的二叉树的最大结点数是2^h- 1.
3.对任何一棵二叉树, 如果度为0其叶结点个数为 n0, 度为2的分支结点个数为 n2,则有n0=n2+1
4.若规定根节点的层数为1,具有n个结点的满二叉树的深度,h=Log2(n+1). (ps:Log2(n+1)是log以2为底,n+1为对数)
5.对于具有n个结点的完全二叉树,如果按照从上至下从左至右的数组顺序对所有节点从0开始编号,则对于序号为i的结点有:
1. 若i>0,i位置节点的双亲序号:(i-1)/2;i=0,i为根节点编号,无双亲节点
2. 若2i+1<n,左孩子序号:2i+1,2i+1>=n否则无左孩子
3. 若2i+2<n,右孩子序号:2i+2,2i+2>=n否则无右孩子
2. 用二叉树实现堆
2.1 堆的概念及结构
如果有一个关键码的集合K = {k0,k1, k2,…,kn-1},把它的所有元素按完全二叉树的顺序存储方式存储在一个一维数组中,并满足:Ki <= K2i+1 且 Ki<= K2i+2 (Ki >= K2i+1 且 Ki >= K2i+2) i = 0,1,2…,则称为小堆(或大堆)。将根节点最大的堆叫做最大堆或大根堆,根节点最小的堆叫做最小堆或小根堆。
2.2 堆的性质
1.堆中某个节点的值总是不大于或不小于其父节点的值;
2.堆总是一棵完全二叉树。
2.3 实现堆的两种重要算法
向上调整算法
向上调整算法是堆排序中的一种操作,用于将无序数组调整为堆结构。在堆排序中,堆通常被分为大根堆和小根堆。大根堆要求每个节点的值都大于或等于其子节点的值,而小根堆则要求每个节点的值都小于或等于其子节点的值。向上调整算法主要用于构建小根堆,即通过调整数组元素的位置,使得每个节点满足小根堆的性质,即父节点的值小于或等于其子节点的值。
//向上调整算法
void AdjustUp(Heap* php,int child)
{
assert(php);
int parent = (child - 1) / 2;
while (php->a[child] > php->a[parent] && parent >= 0)
{
Swap(&php->a[child], &php->a[parent]);
child = parent;
parent = (child - 1) / 2;
}
}
向下调整算法
向下调整算法是堆排序算法中的一个关键步骤,用于维护堆的性质,确保堆是一个最大堆或最小堆。当从堆中删除一个元素后,为了保持堆的特性,需要执行向下调整算法。该算法从堆的根节点开始,通过比较和交换节点值,将根节点调整到其在堆中的正确位置,以确保堆的每个父节点的值都大于或等于其子节点的值(在最大堆中),或者小于或等于其子节点的值(在最小堆中)。
//向下调整算法
void AdjustDown(Heap* php,int parent,int size)
{
assert(php);
int child = parent * 2 + 1;
while (child < size)
{
if (child + 1 < size && php->a[child] < php->a[child]+1)
++child;
if (php->a[parent] < php->a[child])
{
Swap(&php->a[parent], &php->a[child]);
parent = child;
child = parent * 2 + 1;
}
else
break;
}
}
2.4 堆的实现代码
#pragma once
#include <stdio.h>
#include <string.h>
#include <assert.h>
#include <stdbool.h>
#include <stdlib.h>
typedef int HPDataType;
typedef struct Heap
{
HPDataType* a;
int size;
int capacity;
}Heap;
//堆的初始化
void HeapInit(Heap* php);
// 堆的销毁
void HeapDestory(Heap* php);
//交换
void Swap(HPDataType* p1, HPDataType* p2);
//向上调整算法
void AdjustUp(Heap* php, int child);
// 堆的插入
void HeapPush(Heap* php, HPDataType x);
//向下调整算法
void AdjustDown(Heap* php, int parent, int size);
// 堆的删除
void HeapPop(Heap* php);
// 取堆顶的数据
HPDataType HeapTop(Heap* php);
// 堆的数据个数
int HeapSize(Heap* php);
// 堆的判空
bool HeapEmpty(Heap* php);
#include "Heap.h"
//堆的初始化
void HeapInit(Heap* php)
{
assert(php); //php都是空的
php->a = (HPDataType*)malloc(sizeof(HPDataType)*4);
if (php->a == NULL)
{
perror("malloc fail");
exit(-1);
}
php->size = 0;
php->capacity = 4;
}
// 堆的销毁
void HeapDestory(Heap* php)
{
assert(php);
free(php->a);
php->a = NULL;
free(php);
php = NULL;
}
//交换
void Swap(HPDataType* p1, HPDataType* p2)
{
HPDataType tmp;
tmp = *p1;
*p1 = *p2;
*p2 = tmp;
}
//向上调整算法
void AdjustUp(Heap* php,int child)
{
assert(php);
int parent = (child - 1) / 2;
while (php->a[child] > php->a[parent] && parent >= 0)
{
Swap(&php->a[child], &php->a[parent]);
child = parent;
parent = (child - 1) / 2;
}
}
// 堆的插入
void HeapPush(Heap* php, HPDataType x)
{
assert(php);
if (php->size == php->capacity)
{
HPDataType* cur = (HPDataType*)realloc(php->a, sizeof(HPDataType) * 2 * php->capacity);
if (cur == NULL)
{
perror("realloc fail");
return;
}
php->a = cur;
php->capacity *= 2;
}
php->a[php->size] = x;
php->size++;
AdjustUp(php, php->size - 1);
}
//向下调整算法
void AdjustDown(Heap* php,int parent,int size)
{
assert(php);
int child = parent * 2 + 1;
while (child < size)
{
if (child + 1 < size && php->a[child] < php->a[child]+1)
++child;
if (php->a[parent] < php->a[child])
{
Swap(&php->a[parent], &php->a[child]);
parent = child;
child = parent * 2 + 1;
}
else
break;
}
}
// 堆的删除
void HeapPop(Heap* php)
{
assert(php);
Swap(&php->a[0], &php->a[php->size - 1]);
php->size--;
AdjustDown(php, 0, php->size);
}
// 取堆顶的数据
HPDataType HeapTop(Heap* php)
{
assert(php);
return php->a[0];
}
// 堆的数据个数
int HeapSize(Heap* php)
{
assert(php);
return php->size;
}
// 堆的判空
bool HeapEmpty(Heap* php)
{
assert(php);
return php->size == 0;
}
3.二叉树的实现
3.1 二叉树的遍历
二叉树的遍历是指沿着某条搜索路线,依次对树中每个结点均做一次且仅做一次访问。遍历是二叉树上最重要的运算之一,是二叉树上进行其他运算的基础。二叉树的遍历主要有三种方式:先序遍历、中序遍历和后序遍历。
先序遍历顺序:
首先访问根节点,然后遍历左子树,最后遍历右子树。如果二叉树为空,则退出;否则,先访问根结点,然后先序遍历左子树,最后先序遍历右子树。
中序遍历顺序:
首先遍历左子树,然后访问根结点,最后遍历右子树。如果二叉树为空,则退出;否则,中序遍历左子树,然后访问根结点,最后中序遍历右子树。
后序遍历顺序:
首先遍历左子树,然后遍历右子树,最后访问根结点。如果二叉树为空,则退出;否则,后序遍历左子树,然后后序遍历右子树,最后访问根结点。
二叉树的实现代码
#pragma once
#include <stdio.h>
#include <string.h>
#include <stdbool.h>
#include <stdlib.h>
#include <assert.h>
typedef char BTDataType;
typedef struct BinaryTreeNode
{
BTDataType val;
struct BinaryTreeNode* left;
struct BinaryTreeNode* right;
}BTNode;
// 通过前序遍历的数组"ABD##E#H##CF##G##"构建二叉树
BTNode* BinaryTreeCreate(BTDataType* a, int* pi);
// 二叉树销毁
void BinaryTreeDestory(BTNode* root);
// 二叉树节点个数
int BinaryTreeSize(BTNode* root);
// 二叉树叶子节点个数
int BinaryTreeLeafSize(BTNode* root);
// 二叉树第k层节点个数
int BinaryTreeLevelKSize(BTNode* root, int k);
// 二叉树查找值为x的节点
BTNode* BinaryTreeFind(BTNode* root, BTDataType x);
// 二叉树前序遍历
void BinaryTreePrevOrder(BTNode* root);
// 二叉树中序遍历
void BinaryTreeInOrder(BTNode* root);
// 二叉树后序遍历
void BinaryTreePostOrder(BTNode* root);
// 层序遍历
void BinaryTreeLevelOrder(BTNode* root);
// 判断二叉树是否是完全二叉树
int BinaryTreeComplete(BTNode* root);
#include "BTree.h"
#include "Queue.h"
// 通过前序遍历的数组"ABD##E#H##CF##G##"构建二叉树
BTNode* BinaryTreeCreate(BTDataType* a, int* pi)
{
if (a[*pi] == '#')
{
(*pi)++;
return NULL;
}
BTNode* root = (BTNode*)malloc(sizeof(BTNode));
if (root == NULL)
{
perror("malloc fail!");
exit(-1);
}
root->val = a[(*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);
root = NULL;
}
// 二叉树节点个数
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)
{
assert(k > 0);
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->val == x)
return root;
BTNode* left = BinaryTreeFind(root->left, x);
if (left)
return left;
return BinaryTreeFind(root->right, x);
}
// 二叉树前序遍历
void BinaryTreePrevOrder(BTNode* root)
{
if (root == NULL)
{
printf("# ");
return;
}
printf("%c ", root->val);
BinaryTreePrevOrder(root->left);
BinaryTreePrevOrder(root->right);
}
// 二叉树中序遍历
void BinaryTreeInOrder(BTNode* root)
{
if (root == NULL)
{
printf("# ");
return;
}
BinaryTreePrevOrder(root->left);
printf("%c ", root->val);
BinaryTreePrevOrder(root->right);
}
// 二叉树后序遍历
void BinaryTreePostOrder(BTNode* root)
{
if (root == NULL)
{
printf("# ");
return;
}
BinaryTreePrevOrder(root->left);
BinaryTreePrevOrder(root->right);
printf("%c ", root->val);
}
// 层序遍历
void BinaryTreeLevelOrder(BTNode* root)
{
Queue pq;
QueueInit(&pq);
if(root)
QueuePush(&pq, root);
while (!QueueEmpty(&pq))
{
BTNode* cur = QueueFront(&pq);
QueuePop(&pq);
printf("%c ", cur->val);
if(cur->left)
QueuePush(&pq, cur->left);
if(cur->right)
QueuePush(&pq, cur->right);
}
QueueDestroy(&pq);
}
// 判断二叉树是否是完全二叉树
int BinaryTreeComplete(BTNode* root)
{
Queue pq;
QueueInit(&pq);
if (root)
QueuePush(&pq, root);
while (!QueueEmpty(&pq))
{
BTNode* head = QueueFront(&pq);
QueuePop(&pq);
if (head)
{
QueuePush(&pq, head->left);
QueuePush(&pq, head->right);
}
else
break;
}
while (!QueueEmpty(&pq))
{
BTNode* head = QueueFront(&pq);
QueuePop(&pq);
if (head)
{
QueueDestroy(&pq);
return 0;
}
}
QueueDestroy(&pq);
return 1;
}