目录
1. 树与二叉树的基石
1.1 树的本质特性
树是典型的非线性数据结构,其核心特征体现在:
-
层次结构:每个节点(除根节点)有且仅有一个父节点
-
递归定义:子树具有与整棵树完全相同的结构特征
1.2 树的相关概念
节点的度:一个节点含有的子树的个数称为该节点的度; 如上图:A的为6
叶节点或终端节点:度为0的节点称为叶节点; 如上图:B、C、H、I...等节点为叶节点
非终端节点或分支节点:度不为0的节点; 如上图:D、E、F、G...等节点为分支节点
双亲节点或父节点:若一个节点含有子节点,则这个节点称为其子节点的父节点; 如上图:A是B的父节点
孩子节点或子节点:一个节点含有的子树的根节点称为该节点的子节点; 如上图:B是A的孩子节点
兄弟节点:具有相同父节点的节点互称为兄弟节点; 如上图:B、C是兄弟节点
树的度:一棵树中,最大的节点的度称为树的度; 如上图:树的度为6
节点的层次:从根开始定义起,根为第1层,根的子节点为第2层,以此类推;
树的高度或深度:树中节点的最大层次; 如上图:树的高度为4
堂兄弟节点:双亲在同一层的节点互为堂兄弟;如上图:H、I互为兄弟节点
节点的祖先:从根到该节点所经分支上的所有节点;如上图:A是所有节点的祖先
子孙:以某节点为根的子树中任一节点都称为该节点的子孙。如上图:所有节点都是A的子孙
森林:由m(m>0)棵互不相交的树的集合称为森林;
2.二叉树的概念
定义:每个节点最多拥有2个子节点的有序树
特殊形态:
-
满二叉树:一棵高度为 h 的满二叉树,其每一层的节点数都达到最大值,深度h的树具有2^h-1个节点(每一层的节点数都满员,没有任何缺失)
-
完全二叉树:一棵高度为 h 的二叉树,如果从根节点开始,从上到下、从左到右依次填满节点,直到最后一个节点为止,则这棵二叉树是完全二叉树。完全二叉树的前 h−1 层是满的,最后一层的节点可能不满,但必须从左到右依次填满。
性质:
1. 若规定根节点的层数为1,则一棵非空二叉树的第i层上最多有 个结点.
2. 若规定根节点的层数为1,则深度为h的二叉树的最大结点数是 .
3. 对任何一棵二叉树, 如果度为0其叶结点个数为n0 , 度为2的分支结点个数为n2 ,则有 n0=n2+1.
4. 若规定根节点的层数为1,具有n个结点的满二叉树的深度,h=
5. 对于具有n个结点的完全二叉树,如果按照从上至下从左至右的数组顺序对所有节点从0开始编号,则表示二叉树在数组位置中父子下标关系有:
父节点:parent=(child-1)/2
左孩子节点:leftchild = 2*parent+1
右孩子节点:rightchild = 2*parent+2
3. 顺序存储:堆的实现艺术
3.1. 顺序存储
顺序结构存储就是使用数组来存储,一般使用数组只适合表示完全二叉树,因为不是完全二叉树会有空 间的浪费。而现实中使用中只有堆才会使用数组来存储。二叉树顺序存储在物理上是一个数组,在逻辑上是一颗二叉树。
3.2.堆
堆是一种特殊的完全二叉树,满足以下性质:
-
大堆(大根堆):父节点值 ≥ 子节点
-
小堆(小根堆):父节点值 ≤ 子节点
3.3.堆的实现
//模拟实现大堆
#include<stdio.h>
#include<stdlib.h>
#include<assert.h>
#include<stdbool.h>
typedef int HPDataType;
typedef struct Heap
{
HPDataType* a;
int size;
int capacity;
}HP;
//初始化堆
void HeapInit(HP* php);
//销毁堆
void HeapDestroy(HP* php);
//插入数据
void HeapPush(HP* php, HPDataType x);
//删除数据
void HeapPop(HP* php);
//判断堆数据是否为空
bool HeapEmpty(HP* php);
//查看堆顶数据
HPDataType HeapTop(HP* php);
//返回元素个数
int HeapSize(HP* php);
// 定义堆的数据类型
typedef int HPDataType;
// 定义堆的结构体
typedef struct Heap
{
HPDataType* a; // 堆数组
int size; // 当前堆的大小
int capacity; // 堆的最大容量
} HP;
// 辅助函数:交换两个元素的值
void Swap(HPDataType* p1, HPDataType* p2)
{
HPDataType tmp = *p1;
*p1 = *p2;
*p2 = tmp;
}
// 向上调整(上浮操作)
void AdjustUp(HPDataType* a, int child)
{
int parent = (child - 1) / 2; // 计算父节点的索引
while (child > 0) // 当前节点不是根节点时
{
if (a[parent] < a[child]) // 如果父节点小于子节点
{
Swap(&a[parent], &a[child]); // 交换父子节点
child = parent; // 更新当前节点为父节点
parent = (child - 1) / 2; // 更新父节点的索引
}
else
{
break; // 如果父节点大于子节点,调整完成
}
}
}
// 向下调整(下沉操作)
void AdjustDown(HPDataType* a, int size, int parent)
{
int child = parent * 2 + 1; // 假设左孩子是最大的
while (child < size) // 当前节点有子节点时
{
if (child + 1 < size && a[child + 1] > a[child]) // 如果右孩子存在且大于左孩子
{
child++; // 将child指向较大的子节点
}
if (a[child] > a[parent]) // 如果子节点大于父节点
{
Swap(&a[child], &a[parent]); // 交换父子节点
parent = child; // 更新当前节点为子节点
child = parent * 2 + 1; // 更新子节点的索引
}
else
{
break; // 如果父节点大于子节点,调整完成
}
}
}
// 初始化堆
void HeapInit(HP* php)
{
assert(php); // 确保传入的堆指针非空
php->a = (HPDataType*)malloc(sizeof(HPDataType) * 4); // 分配初始内存空间
if (php->a == NULL)
{
perror("malloc fail"); // 如果分配失败,打印错误信息
return;
}
php->capacity = 4; // 设置堆的初始容量为4
php->size = 0; // 堆的初始大小为0
}
// 销毁堆
void HeapDestroy(HP* php)
{
assert(php); // 确保堆指针非空
free(php->a); // 释放堆数组的内存
php->capacity = 0; // 将堆的容量重置为0
php->size = 0; // 将堆的大小重置为0
}
// 插入元素到堆中
void HeapPush(HP* php, HPDataType x)
{
assert(php); // 确保堆指针非空
if (php->size == php->capacity) // 如果堆已满,扩容
{
HPDataType* tmp = realloc(php->a, sizeof(HPDataType) * php->capacity * 2);
if (tmp == NULL)
{
perror("realloc fail"); // 如果扩容失败,打印错误信息
return;
}
php->a = tmp; // 更新堆数组指针
php->capacity *= 2; // 更新堆的容量
}
php->a[php->size] = x; // 将新元素放在堆的末尾
php->size++; // 堆的大小加1
AdjustUp(php->a, php->size - 1); // 对新插入的元素进行上浮操作
}
// 删除堆顶元素
void HeapPop(HP* php)
{
assert(php); // 确保堆指针非空
assert(!HeapEmpty(php)); // 确保堆不为空
Swap(&php->a[0], &php->a[php->size - 1]); // 将堆顶元素与最后一个元素交换
php->size--; // 堆的大小减1
AdjustDown(php->a, php->size, 0); // 对新的堆顶元素进行下沉操作
}
// 判断堆是否为空
bool HeapEmpty(HP* php)
{
assert(php); // 确保堆指针非空
return php->size == 0; // 返回堆是否为空
}
// 获取堆顶元素
HPDataType HeapTop(HP* php)
{
assert(php); // 确保堆指针非空
return php->a[0]; // 返回堆顶元素
}
// 获取堆的大小
int HeapSize(HP* php)
{
assert(php); // 确保堆指针非空
return php->size; // 返回堆的大小
}
4.二叉树链式结构的实现
4.1.创建二叉树
#include <stdio.h>
#include <stdlib.h>
// 定义二叉树节点的数据类型
typedef int BTDataType;
// 定义二叉树节点的结构体
typedef struct BinaryTreeNode {
BTDataType data; // 节点存储的数据
struct BinaryTreeNode* left; // 指向左孩子的指针
struct BinaryTreeNode* right; // 指向右孩子的指针
} BTNode;
// 创建一个新的二叉树节点
BTNode* BuyNode(BTDataType x) {
BTNode* node = (BTNode*)malloc(sizeof(BTNode)); // 分配内存空间
if (node == NULL) {
perror("malloc fail"); // 如果分配失败,打印错误信息
return NULL;
}
node->data = x; // 初始化节点数据
node->left = NULL; // 初始化左孩子指针
node->right = NULL; // 初始化右孩子指针
return node; // 返回创建的节点
}
// 创建一个简单的二叉树
BTNode* CreateBinaryTree() {
// 创建各个节点
BTNode* node1 = BuyNode(1); // 根节点
BTNode* node2 = BuyNode(2);
BTNode* node3 = BuyNode(3);
BTNode* node4 = BuyNode(4);
BTNode* node5 = BuyNode(5);
BTNode* node6 = BuyNode(6);
// 构建二叉树的结构
node1->left = node2; // 1 的左孩子是 2
node1->right = node4; // 1 的右孩子是 4
node2->left = node3; // 2 的左孩子是 3
node4->left = node5; // 4 的左孩子是 5
node4->right = node6; // 4 的右孩子是 6
return node1; // 返回根节点
}
结构:
4.2.二叉树的三序遍历
// 前序遍历:根 左子树 右子树
void PreOrder(BTNode* root)
{
if (root == NULL)
{
printf("NULL "); // 如果当前节点为空,打印 NULL
return;
}
printf("%d ", root->data); // 先访问根节点
PreOrder(root->left); // 递归遍历左子树
PreOrder(root->right); // 递归遍历右子树
}
// 中序遍历:左子树 根 右子树
void InOrder(BTNode* root)
{
if (root == NULL)
{
printf("NULL "); // 如果当前节点为空,打印 NULL
return;
}
InOrder(root->left); // 递归遍历左子树
printf("%d ", root->data); // 访问根节点
InOrder(root->right); // 递归遍历右子树
}
// 后序遍历:左子树 右子树 根
void PostOrder(BTNode* root)
{
if (root == NULL)
{
printf("NULL "); // 如果当前节点为空,打印 NULL
return;
}
PostOrder(root->left); // 递归遍历左子树
PostOrder(root->right); // 递归遍历右子树
printf("%d ", root->data); // 最后访问根节点
}
//层序遍历:自上而下,从左至右
void LevelOrder(BTNode* root)
{
Queue q;
QueueInit(&q);
if (root)
{
QueuePush(&q,root);
}
while (!QueueEmpty(&q))
{
BTNode* front = QueueFront(&q);
QueuePop(&q);
printf("%d ", front->data);
if (front->left)
{
QueuePush(&q,front->left);
}
if (front->right)
{
QueuePush(&q,front->right);
}
}
QueueDestroy(&q);
}
// 二叉树节点个数
int BinaryTreeSize(BTNode* root)
{
if (root == NULL)
{
return 0;
}
return BinaryTreeSize(root->left) + BinaryTreeSize(root->right) + 1;
}
// 二叉树叶子节点个数:如果当前节点是叶子,返回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);
}
//求高度,当前树高度 = 左右子树高度大的那个 + 1
int BinaryTreeHeight(BTNode* root)
{
if (root == NULL)
{
return 0;
}
/*return BinaryTreeHeight(root->left) > BinaryTreeHeight(root->right) ?
BinaryTreeHeight(root->left) + 1 : BinaryTreeHeight(root->right) + 1;*/
int leftheight = BinaryTreeHeight(root->left);
int rightheight = BinaryTreeHeight(root->right);
return BinaryTreeHeight(root->left) > BinaryTreeHeight(root->right) ?
leftheight + 1 : rightheight + 1;
}
// 二叉树查找值为x的节点
BTNode* BinaryTreeFind(BTNode* root, BTDataType x)
{
/*if (root == NULL)
{
return NULL;
}
if (root->data == x)
{
return root;
}
BTNode* result = BinaryTreeFind(root->left, x);
if (result != NULL)
{
return result;
}
return BinaryTreeFind(root->right, x);*/
if (root == NULL)
{
return NULL;
}
if (root->data == x)
{
return root;
}
BTNode* lret = BinaryTreeFind(root->left, x);
if (lret)
{
return lret;
}
BTNode* rret = BinaryTreeFind(root->right, x);
if (rret)
{
return rret;
}
return NULL;
}
//判断是不是完全二叉树
bool TreeComplete(BTNode* root)
{
Queue q;
QueueInit(&q);
if (root)
{
QueuePush(&q,root);
}
while (!QueueEmpty(&q))
{
BTNode* front = QueueFront(&q);
QueuePop(&q);
if (front == NULL)
{
break;
}
else
{
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;
}
层序遍历:队列里面存的树的结点的指针,还有下一个结点的地址。出一层,入一层