二叉树和堆(三)
一、二叉树链式结构及实现
💦 前置说明
普通二叉树的增删查改复杂且没有意义,所以我们并不打算学习它的增删查改,主要是学习它的结构
在学习二叉树的基本操作前,需先要创建一棵二叉树,然后才能学习其相关的基本操作。
由于现在对二叉树结构掌握还不够深入,为了降低学习成本,此处手动快速创建一棵简单的二叉树,以此快速进入二叉树操作学习,等二叉树结构了解的差不多时,我们反过头再来研究二叉树真正的创建方式。
⚠ 以下代码并不是创建二叉树的方式,真正创建二叉树方式后序详解重点讲解
❗ 回顾二叉树 ❕
1️⃣ 空树
2️⃣ 非空:根节点,根节点的左子树、根节点的右子树组成
💨 从概念中可以看出,二叉树定义是递归式的,因此后序基本操作中基本都是按照该概念实现的。
typedef int BTDataType;
typedef struct BinaryTreeNode
{
BTDataType _data;
struct BinaryTreeNode* _left;
struct BinaryTreeNode* _right;
}BTNode;
BTNode* CreatBinaryTree()
{
BTNode* node1 = BuyNode('A');
BTNode* node2 = BuyNode('B');
BTNode* node3 = BuyNode('C');
BTNode* node4 = BuyNode('D');
BTNode* node5 = BuyNode('E');
BTNode* node6 = BuyNode('F');
node1->_left = node2;
node1->_right = node3;
node2->_left = node4;
node3->_left = node5;
node3->_right = node6;
return node1;
}
💦 二叉树的遍历
学习二叉树结构,最简单的方式就是遍历。所谓二叉树遍历 (Traversal) 是按照某种特定的规则,依次对二叉树中的节点进行相应的操作,并且每个节点只操作一次。
访问结点所做的操作依赖于具体的应用问题。 遍历是二叉树上最重要的运算之一,也是二叉树上进行其它运算的基础。
其实这种就是递归的思想,且在现实生活中也经常使用到 —— 比如 1 位校长要统计学校的人数,他不可能亲自去挨个数,一般是通过院长、系主任、辅导员、班长、舍长的层层反馈才得到结果的
1️⃣ 前序遍厉: 根 左子树 右子树
2️⃣ 中序遍厉: 左子树 根 右子树
3️⃣ 后序遍厉: 左子树 右子树 根
4️⃣ 层序遍厉: 一层一层的遍
在之前就提到过深度和广度,其实指的就是这个:
1️⃣ 深度优先遍厉:前序遍厉、中序遍厉、后序遍厉,注意有些说法只认同前序遍厉
2️⃣ 广度优先遍厉:层序遍厉
1、前序、中序以及后序遍历
1️⃣ 前序遍历(Preorder Traversal 亦称先序遍历) —— 访问根结点的操作发生在遍历其左右子树之前。
2️⃣ 中序遍历(Inorder Traversal) —— 访问根结点的操作发生在遍历其左右子树之中(间)。
3️⃣ 后序遍历(Postorder Traversal) —— 访问根结点的操作发生在遍历其左右子树之后。
由于被访问的结点必是某子树的根,所以N(Node)、L(Left subtree)和R(Right subtree)又可解释为根、根的左子树和根的右子树。NLR、LNR和LRN分别又称为先根遍历、中根遍历和后根遍历。
// 二叉树前序遍历
void PreOrder(BTNode* root);
// 二叉树中序遍历
void InOrder(BTNode* root);
// 二叉树后序遍历
void PostOrder(BTNode* root);
❗ 实现 ❕
#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 = malloc(sizeof(BTNode));
node->_data = x;
node->_left = NULL;
node->_right = NULL;
return node;
}
BTNode* CreatBinaryTree()
{
BTNode* node1 = BuyNode('A');
BTNode* node2 = BuyNode('B');
BTNode* node3 = BuyNode('C');
BTNode* node4 = BuyNode('D');
BTNode* node5 = BuyNode('E');
BTNode* node6 = BuyNode('F');
node1->_left = node2;
node1->_right = node3;
node2->_left = node4;
node3->_left = node5;
node3->_right = node6;
return node1;
}
//二叉树前序遍历
void PreOrder(BTNode* root)
{
if(root == NULL)
{
printf("NULL ");
return;
}
printf("%c ", root->_data);
PreOrder(root->_left);
PreOrder(root->_right);
}
//二叉树中序遍历
void InOrder(BTNode* root)
{
if(root == NULL)
{
printf("NULL ");
return;
}
InOrder(root->_left);
printf("%c ", root->_data);
InOrder(root->_right);
}
//二叉树后序遍历
void PostOrder(BTNode* root)
{
if(root == NULL)
{
printf("NULL ");
return;
}
PostOrder(root->_left);
PostOrder(root->_right);
printf("%c ", root->_data);
}
//构建二叉树
void TestTree()
{
BTNode* root = CreatBinaryTree();
PreOrder(root);
}
int main()
{
TestTree();
return 0;
}
📝 分析:
以上真正的核心代码是 PreOrder、InOrder、PostOrder 函数的递归调用短短几行代码却执行了如此复杂的计算,这里豌豆太懒了就只画一个展开图:
❗ PreOrder ❕
2、层序遍历
除了先序遍历、中序遍历、后序遍历外,还可以对二叉树进行层序遍历。
设二叉树的根节点所在层数为1,层序遍历就是从所在二叉树的根节点出发,首先访问第一层的树根节点,然后从左到右访问第2层上的节点,接着是第三层的节点,
以此类推,自上而下,自左至右逐层访问树的结点的过程就是层序遍历。
// 层序遍历 - 在下面实现
void LevelOrder(BTNode* root);
3、概念选择题 (如何利用已知限有条件构建二叉树)
1.某完全二叉树按层次输出(同一层从左到右)的序列为 ABCDEFGH 。该完全二叉树的前序序列为( )
A. ABDHECFG
B. ABCDEFGH
C. HDBEAFCG
D. HDEBFGCA
📝 分析
所以选择 A 选项
2.二叉树的先序遍历和中序遍历如下:先序遍历:EFHIGJK; 中序遍历:HFIEJKG; 则二叉树根结点为( )
A. E
B. F
C. G
D. H
📝 分析
根据这棵树的先序遍厉、中序遍厉并不能重建 (其实可以重建的,详情看下题) 这棵树。但是这里并不需要重建,因为先序遍厉是从根开始的。
所以选择 A 选项
–
3.设—课二叉树的中序遍历序列: badce,后序遍历序列: bdeca, 则二叉树前序遍历序列为( )
A. adbce
B. decab
C. debac
D. abcde
📝 分析
这里在没有 # 的情况下,满足以下条件即可构建出二叉树
所以选择 D 选项
4.某二叉树的后序遍历序列与中序遍历序列相同,均为 ABCDEF,则按层次输出 (同一层从左到右) 的序列为( )
A. FEDCBA
B. CBAFED
C. DEFCBA
D. ABCDEF
📝 分析
显然这道题作为选择题来说一眼就能知道答案:根据它的后序遍厉知道根是 F
所以选择 A 选项
❓ 如果知道了前中后序遍历序列,不包括 #(空) ,能否构建一棵二叉树 ❔
不能构建,但满足以下的条件即可构建:
💦 节点个数以及高度等
❓ 实现以下接口 ❔
// 二叉树节点个数
int BinaryTreeSize(BTNode* root);
// 二叉树叶子节点个数
int BinaryTreeLeafSize(BTNode* root);
// 二叉树第k层节点个数
int BinaryTreeLevelKSize(BTNode* root, int k);
//二叉树深度/高度
int BinaryTreeDepth(BTNode* root)
// 二叉树查找值为x的节点
BTNode* BinaryTreeFind(BTNode* root, BTDataType x);
❗ 实现代码 ❕
❤ BinaryTreeSize ❤
🔑 核心思想1 :使用前序 || 中序 || 后序遍历,全局变量记录
❌ 但是以下代码有 Bug :如果采用前序重复遍历 2 次
✔ 经查,问题出在全局变量上,这里只需要在第 2 次遍历时置 0 即可
⚠ 在以后的知识面更大后,其实你会发现这种操作还有线程安全的问题
🔑 核心思想 2:函数使用带返回值的方式,其内部的递归本质上是一个后序遍厉
❤ BinaryTreeLeafSize ❤
🔑 核心思想 :以 left 和 right 为标志,如果都为 NULL,则累加
❤ BinaryTreeLevelKSize ❤
🔑 核心思想 :求当前树的第 k 层 = 左子树的第 k - 1 层 + 右子树的第 k - 1 层 (当 k = 1 时,说明此层就是目标层)
❤ BinaryTreeDepth ❤
🔑 核心思想 :当前树的深度 = max (左子树的深度,右子树的深度) + 1
❤ BinaryTreeFind ❤
🔑 核心思想 :
1、先判断是不是当前节点,是就返回;
2、先去左树找,找到就返回
3、左树没找到,再找右树
#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 = malloc(sizeof(BTNode));
node->data = x;
node->left = NULL;
node->right = NULL;
return node;
}
// 二叉树节点个数
//1、前序递归遍历
int sz1 = 0;
void BinaryTreeSize1(BTNode* root)
{
if (root == NULL)
return;
else
sz1++;
BinaryTreeSize1(root->left);
BinaryTreeSize1(root->right);
}
//2、中序递归遍历
int sz2 = 0;
void BinaryTreeSize2(BTNode* root)
{
if (root == NULL)
return;
BinaryTreeSize2(root->left);
if (root != NULL)
sz2++;
BinaryTreeSize2(root->right);
}
//3、后序递归遍历
int sz3 = 0;
void BinaryTreeSize3(BTNode* root)
{
if (root == NULL)
return;
BinaryTreeSize3(root->left);
BinaryTreeSize3(root->right);
if (root != NULL)
sz3++;
}
// 二叉树节点个数
int BinaryTreeSize(BTNode* root)
{
return root == NULL ? 0 : 1 + BinaryTreeSize(root->left) + BinaryTreeSize(root->right);
}
//二叉树叶子节点个数
int BinaryTreeLeaSize(BTNode* root)
{
if (root == NULL)
{
return 0;
}
else if (root->left == NULL && root->right == NULL)
{
return 1;
}
else
{
return BinaryTreeLeaSize(root->left) + BinaryTreeLeaSize(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);
}
//二叉树的深度/高度
int BinaryTreeDepth(BTNode* root)
{
if (root == NULL)
{
return 0;
}
int leftDepth = BinaryTreeDepth(root->left);
int rightDepth = BinaryTreeDepth(root->right);
return leftDepth > rightDepth ? leftDepth + 1 : rightDepth + 1;
}
// 二叉树查找值为x的节点
BTNode* BinaryTreeFind(BTNode* root, BTDataType x)
{
if (root == NULL)
{
return NULL;
}
if (root->data == x)
{
return root;
}
BTNode* retleft = BinaryTreeFind(root->left, x);
if (retleft)
{
return retleft;
}
BTNode* retright = BinaryTreeFind(root->right, x);
if (retright)
{
return retright;
}
return NULL;
}
BTNode* CreatBinaryTree()
{
BTNode* node1 = BuyNode('A');
BTNode* node2 = BuyNode('B');
BTNode* node3 = BuyNode('C');
BTNode* node4 = BuyNode('D');
BTNode* node5 = BuyNode('E');
BTNode* node6 = BuyNode('F');
node1->left = node2;
node1->right = node3;
node2->left = node4;
node3->left = node5;
node3->right = node6;
return node1;
}
void PreOrder(BTNode* root)
{
if (root == NULL)
{
printf("NULL ");
return;
}
printf("%c ", root->data);
PreOrder(root->left);
PreOrder(root->right);
}
int main()
{
BTNode* root = CreatBinaryTree();
//遍厉前中后序输出二叉树节点的个数
BinaryTreeSize1(root);
BinaryTreeSize2(root);
BinaryTreeSize3(root);
printf("BinaryTreeSize:%d\n", sz1);
printf("BinaryTreeSize:%d\n", sz2);
printf("BinaryTreeSize:%d\n", sz3);
printf("-----------------cur-----------------\n");
//优化二叉树节点的个数
printf("BinaryTreeSize:%d\n", BinaryTreeSize(root));
printf("-----------------cur-----------------\n");
//二叉树叶子节点个数
printf("BinaryTreeLeaSize:%d\n", BinaryTreeLeaSize(root));
printf("-----------------cur-----------------\n");
//二叉树第k层节点个数
printf("BinaryTreeLeveLKSize:%d\n", BinaryTreeLeveLKSize(root, 3));
printf("-----------------cur-----------------\n");
//二叉树的深度/高度
printf("BinaryTreeDepth:%d\n", BinaryTreeDepth(root));
printf("-----------------cur-----------------\n");
// 二叉树查找值为x的节点
BTNode* ret = BinaryTreeFind(root, 'D');
if(ret)
{
printf("找到了\n");
}
else
{
printf("没找到\n");
}
PreOrder(root);
return 0;
}
💦 二叉树的创建和销毁
//二叉树创建
BTNode* BinaryCreatBinaryTree();
// 二叉树销毁
void BinaryTreeDestroy(BTNode* root);
❗ BinaryCreatBinaryTree && BinaryTreeDestroy ❕
注意对于 BinaryTreeDestroy 使用后序的方式销毁
#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 = malloc(sizeof(BTNode));
node->_data = x;
node->_left = NULL;
node->_right = NULL;
return node;
}
//创建二叉树
BTNode* BinaryCreatBinaryTree()
{
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;
node1->_right = node3;
node2->_left = node4;
node3->_left = node5;
node3->_right = node6;
return node1;
}
//后序销毁二叉树
void BinaryTreeDestroy(BTNode* root)
{
if (root == NULL)
return;
BinaryTreeDestroy(root->_left);
BinaryTreeDestroy(root->_right);
free(root);
}
int main()
{
BTNode* root = BinaryCreatBinaryTree();
BinaryTreeDestroy(root);
root = NULL;
return 0;
}
💦 二叉树的层序遍厉
//层序遍厉
void BinaryTreeLeveOrder(BTNode* root);
//判断二叉树是否是完全二叉树
bool BinaryTreeComplete(BTNode* root);
BinaryTreeLeveOrder:
🔑 核心思想 🔑
使用队列的方式:先入第一层,出上一层,再入下一层 … …
需要使用之前实现的队列
BinaryTreeComplete:
🔑 核心思想 🔑
想必完全二叉树在此处出现一定跟层序有关系
层序遍厉,把空也入队列
完全二叉树,非空是连续的,空也是连续的
非完全二叉树,非空就不是连续的,空也是不连续的
❗ 这里需要三个文件 ❕
1️⃣ Queue_LeveOrder.h,用于函数的声明
#pragma once
//头
#include<stdio.h>
#include<assert.h>
#include<stdbool.h>
#include<stdlib.h>
//结构体
typedef struct BinaryTreeNode* QDataType;
typedef struct QueueNode
{
struct QueueNode* next; //指向下一个节点
QDataType data; //存储整型数据
}QueueNode;
typedef struct Queue
{
QueueNode* phead;//头指针
QueueNode* ptail;//尾指针
}Queue;
//函数
void QueueInit(Queue* pq);
void QueuePush(Queue* pq, QDataType x);
bool QueueEmpty(Queue* pq);
void QueuePop(Queue* pq);
QDataType QueueSize(Queue* pq);
QDataType QueueFront(Queue* pq);
QDataType QueueBack(Queue* pq);
void QueueDestory(Queue* pq);
2️⃣ Queue_LeveOrder.c,用于函数的实现
#include"Queue_LeveOrder.h"
void QueueInit(Queue* pq)
{
assert(pq);
//把2个指针置空
pq->phead = pq->ptail = NULL;
}
void QueuePush(Queue* pq, QDataType x)
{
assert(pq);
//malloc空间,如果需要频繁的开辟空间建议再实现一个BuyQueueNode用于malloc
QueueNode* newnode = (QueueNode*)malloc(sizeof(QueueNode));
if (newnode == NULL)
{
printf("malloc fail\n");
exit(-1);
}
newnode->data = x;
newnode->next = NULL;
//第一次插入
if (pq->phead == NULL)
{
pq->phead = pq->ptail = newnode;
}
//非第一次插入
else
{
pq->ptail->next = newnode;
pq->ptail = newnode;
}
}
bool QueueEmpty(Queue* pq)
{
assert(pq);
//空链表返回true,非空链表返回false
return pq->phead == NULL;
}
void QueuePop(Queue* pq)
{
assert(pq);
//链表为空时不能删除
assert(!QueueEmpty(pq));
//只有一个节点的情况
if (pq->phead->next == NULL)
{
free(pq->phead);
pq->phead = pq->ptail = NULL;
}
//多个节点的情况
else
{
QueueNode* next = pq->phead->next;
free(pq->phead) ;
pq->phead = next;
}
}
QDataType QueueSize(Queue* pq)
{
assert(pq);
//如果需要频繁的调用QueueSize这个接口,可以在Queue这个结构体中增加一个成员用于记录长度
int sz = 0;
QueueNode* cur = pq->phead;
while (cur)
{
sz++;
cur = cur->next;
}
return sz;
}
QDataType QueueFront(Queue* pq)
{
assert(pq);
//链表为空时不能取头
assert(!QueueEmpty(pq));
return pq->phead->data;
}
QDataType QueueBack(Queue* pq)
{
assert(pq);
//链表为空时不能取尾
assert(!QueueEmpty(pq));
return pq->ptail->data;
}
void QueueDestory(Queue* pq)
{
assert(pq);
QueueNode* cur = pq->phead;
//遍历链表
while (cur)
{
QueueNode* next = cur->next;
free(cur);
cur = next;
}
pq->phead = pq->ptail = NULL;
}
3️⃣ Test.c,用于函数的测试
#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
#include<stdlib.h>
typedef int BTDataType;
typedef struct BinaryTreeNode
{
BTDataType _data;
struct BinaryTreeNode* _left;
struct BinaryTreeNode* _right;
}BTNode;
//从此处包含Queue.h文件
#include"Queue_LeveOrder.h"
BTNode* BuyNode(BTDataType x)
{
BTNode* node = malloc(sizeof(BTNode));
node->_data = x;
node->_left = NULL;
node->_right = NULL;
return node;
}
//创建二叉树
BTNode* BinaryCreatBinaryTree()
{
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;
node1->_right = node3;
node2->_left = node4;
node3->_left = node5;
node3->_right = node6;
return node1;
}
//层序遍厉
void BinaryTreeLeveOrder(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);
}
}
printf("\n");
QueueDestory(&q);
}
//判断二叉树是否是完全二叉树
bool BinaryTreeComplete(BTNode* root)
{
Queue q;
QueueInit(&q);
if (root)
{
//入队列
QueuePush(&q, root);
}
while (!QueueEmpty(&q))
{
//取队头的数据
BTNode* front = QueueFront(&q);
//出队
QueuePop(&q);
//遇到空就break出去再判断
if(front == NULL)
{
break;
}
//入左子树
QueuePush(&q, front->_left);
//入右子树
QueuePush(&q, front->_right);
}
//队列中全是空,就是完全二叉树;还有非空,就不是完全二叉树
while(!QueueEmpty(&q))
{
BTNode* front = QueueFront(&q);
QueuePop(&q);
//非空
if(front)
{
QueueDestory(&q);
return false;
}
}
//全是空
QueueDestory(&q);
return true;
}
//后序销毁二叉树
void BinaryTreeDestroy(BTNode* root)
{
if (root == NULL)
return;
BinaryTreeDestroy(root->_left);
BinaryTreeDestroy(root->_right);
free(root);
}
int main()
{
BTNode* root = BinaryCreatBinaryTree();
BinaryTreeLeveOrder(root);
printf("BinaryTreeComplete:%d\n", BinaryTreeComplete(root));
BinaryTreeDestroy(root);
root = NULL;
return 0;
}
- 点赞
- 收藏
- 关注作者
评论(0)