二叉树
1.二叉树的概念及结构
1.1二叉树的概念
🐲 一棵二叉树是结点的一个有限集合,该集合:
1.或为空
2.或由一个根节点加上两棵称为左子树和右子树的二叉树组成
由此我们可以看出:
1️⃣二叉树不存在度大于2的结点
2️⃣二叉树的子树有左右之分,次序不能颠倒,因此二叉树是有序树
⚠️以上几种情况都是二叉树!
1.2特殊的二叉树
1️⃣满二叉树:一个二叉树,如果每一个层的结点数都达到最大值,则这个二叉树就是满二叉树。也就是说,如果一个二叉树的层数为2k-1,且结点总数是 ,则它就是满二叉树。
2️⃣完全二叉树:完全二叉树是效率很高的数据结构,完全二叉树是由满二叉树而引出来的。对于深度为K的,有n个结点的二叉树,当且当其每一个结点都与深度为K的满二叉树中编号从1至n的结点一一对应时称之为完全二叉树。 要注意的是满二叉树是一种特殊的完全二叉树。
1.3二叉树的性质
1️⃣若规定根节点的层数为1,则一棵非空二叉树的第i层上最多有2(i-1) 个结点
2️⃣若规定根节点的层数为1,则深度为h的二叉树的最大结点数是 2h-1
3️⃣对任何一棵二叉树, 如果度为0其叶结点个数为n0 , 度为2的分支结点个数为n2 ,则有 n0 =n2+1
4️⃣若规定根节点的层数为1,具有n个结点的满二叉树的深度,h= .log2(n+1) (ps: 是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否则无右孩子
1.4二叉树的存储结构
🐤二叉树一般有两种结构存储,一种是顺序结构(数组),一种是链式结构,现在我们讲的链式。
1️⃣顺序结构存储:就是使用数组来存储,一般使用数组只适合表示完全二叉树,因为不是完全二叉树会有空间的浪费。而现实中使用中只有堆才会使用数组来存储,二叉树顺序存储在物理上是一个数组,在逻辑上是一颗二叉树。
2️⃣二叉树的链式存储:用链表来表示一棵二叉树,即用链来指示元素的逻辑关系。 通常的方法是链表中每个结点由三个域组成,数据域和左右指针域,左右指针分别用来给出该结点左孩子和右孩子所在的链结点的存储地址 。
上面的图只是逻辑上的二叉树,而实际在内存中存储时链式结构
是不连续的,杂乱的,只通过左右指针域来找到下一个节点
typedef char BTDataType; //将数据类型进行宏定义,方便我们以后更改数据类型
typedef struct BinaryTreeNode
{
BTDataType data; //数据域
struct BinaryTreeNode* left; //左指针域,指向二叉树的左孩子节点
struct BinaryTreeNode* right; //右指针域,指向二叉树的右孩子节点
}BTNode;
2.二叉树的实现
🍠我们首先需要创建一个二叉树
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);
BTNode* node7 = BuyNode(G);
node1->_left = node2;
node1->_right = node3;
node2->_left = node4;
node2->_right = node5;
node3->_left = node6;
node3->_right = node7;
return node1;
}
❗️这并不是创建二叉树的方式,只是为了方便才直接链接的。
从概念中可以看出,二叉树定义是递归式的,因此后序基本操作中基本都是按照该概念实现的。
二叉树的遍历
1️⃣前序遍历(Preorder Traversal 亦称先序遍历)——访问根结点的操作发生在遍历其左右子树之前。
2️⃣中序遍历(Inorder Traversal)——访问根结点的操作发生在遍历其左右子树之中(间)。
3️⃣后序遍历(Postorder Traversal)——访问根结点的操作发生在遍历其左右子树之后。
// 二叉树前序遍历
void PreOrder(BTNode* root);
// 二叉树中序遍历
void InOrder(BTNode* root);
// 二叉树后序遍历
void PostOrder(BTNode* root);
2.1.前序遍历
⭕️遍历结果:A–>B–>D–>NULL–>NULL->E–>NULL–>NULL–>C–>F–>NULL–>NULL–>G–>NULL–>NULL
🐯**思路:**分治思想,把大问题分成小问题。先从根开始,遇到左子树不为空就把左子树作为根,然后一层一层的往下遍历,直到空,返回。
void BinaryTreePrevOrder(BTNode* root)
{
if (root == NULL) //子树为空则返回退出
{
printf("NULL ");
return;
}
printf("%c ", root->data); //先访问根
BinaryTreePrevOrder(root->left); //再访问左子树
BinaryTreePrevOrder(root->right); //后访问右子树
}
2.2.中序遍历
⭕️遍历结果:NULL–>D–>NULL–>B–>NULL–>E–>NULL–>A–>NULL–>F–>NULL–>C–>NULL–>G–>NULL
void BinaryTreeInOrder(BTNode* root)
{
if (root == NULL)
{
printf("NULL ");
return;
}
BinaryTreeInOrder(root->left); //先访问左子树
printf("%c ", root->data); //再访问根
BinaryTreeInOrder(root->right); //后访问右子树
}
2.3.后序遍历
⭕️遍历结果:NULL–>NULL–>D–>NULL–>NULL–>E–>B–>NULL–>NULL–>F–>NULL–>NULL–>G–>C–>A
void BinaryTreePostOrder(BTNode* root)
{
if (root == NULL)
{
printf("NULL ");
return;
}
BinaryTreePostOrder(root->leftchild);
BinaryTreePostOrder(root->rightchild);
printf("%c ", root->data);
}
2.4.层序遍历
层序遍历我们不是用递归,我们需要借助队列来实现。
⭕️遍历结果:A B C D E F G
🌅队列代码:
#define _CRT_SECURE_NO_WARNINGS 1
#include "Queue.h"
void QueueInit(Queue* pq)
{
assert(pq);
pq->head = NULL;
pq->tail = NULL;
pq->size = 0;
}
void QueueDestory(Queue* pq)
{
assert(pq);
QNode* cur = pq->head;
while (cur)
{
QNode* del = cur;
cur = cur->next;
free(del);
}
pq->head = pq->tail = NULL;
pq->size = 0;
}
void QueuePush(Queue* pq, QDataType x)
{
assert(pq);
QNode* newnode = (QNode*)malloc(sizeof(QNode));
if (newnode == NULL)
{
perror("malloc fail");
exit(-1);
}
newnode->data = x;
newnode->next = NULL;
//尾插
if (pq->tail == NULL)
{
pq->head = pq->tail = newnode;
}
else
{
pq->tail->next = newnode;
pq->tail = newnode;
}
pq->size++;
}
void QueuePop(Queue* pq)
{
assert(pq);
assert(!QueueEmpty(pq));
if (pq->head->next == NULL)
{
pq->head = pq->tail = NULL;
}
else
{
QNode* del = pq->head;
pq->head = pq->head->next;
free(del);
}
pq->size--;
}
QDataType QueueFront(Queue* pq)
{
assert(pq);
assert(!QueueEmpty(pq));
return pq->head->data;
}
QDataType QueueTail(Queue* pq)
{
assert(pq);
assert(!QueueEmpty(pq));
return pq->tail->data;
}
bool QueueEmpty(Queue* pq)
{
assert(pq);
return pq->head == NULL && pq->tail == NULL;
}
int QueueSize(Queue* pq)
{
assert(pq);
return pq->size;
}
🌅层序代码:
// 层序遍历
void BinaryTreeLevelOrder(BTNode* root)
{
Queue q;
QueueInit(&q);
if (root)
QueuePush(&q,root);
while (!QueueEmpty(&q))
{
BTNode* front = QueueFront(&q);
printf("%d ",front->data);
QueuePop(&q);
if (front->left)
{
QueuePush(&q, front->left);
}
if (front->right)
{
QueuePush(&q, front->right);
}
}
printf("\n");
QueueDestory(&q);
}
2.5.二叉树节点个数
int BinaryTreeSize(BTNode* root)
{
return root == NULL ? 0 : BinaryTreeSize(root->left) + BinaryTreeSize(root->right) + 1;
}
2.6.二叉树叶子节点个数
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);
}
2.7.二叉树第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);
}
2.8.二叉树查找值为x的节点
BTNode* BinaryTreeFind(BTNode* root, BTDataType x)
{
if (root == NULL)
{
return NULL;
}
if (root->data == x)
{
return root;
}
struct BinaryTreeNode* left = BinaryTreeFind(root->left, x);
if (left)
return left;
struct BinaryTreeNode* right = BinaryTreeFind(root->right, x);
if (right)
return right;
return NULL;
}
3.二叉树的创建和销毁
我们要用输入字符来创建二叉树。
构建这棵树我们需要两个参数:这个字符串的数组和数组下标
如果仅仅是值传递的话会有什么问题呢?
我们的遍历都是通过递归来实现的,那我们把下标i传过去,把数据放到下标i的位置,i++。那i++就真的会++吗?进函数创建栈帧,出函数销毁栈帧,那这次栈帧里的下标i和上次里的下标i有什么关系呢?答案是没有关系。形参进栈帧创建,出栈帧销毁。形参的改变不影响实参。
我们有两种解决方案:
1.全局变量:如果我们代码只有创建二叉树这一个功能那就不会有影响,但是一个工程不可能只有一种函数,所以当其他函数用到全局变量i时就会出错,所以我们也不用。
2.传地址:这个方法是最好的
// 通过前序遍历的数组"ABD##E#H##CF##G##"构建二叉树
BTNode* BinaryTreeCreate(BTDataType* a, int n, int* pi)
{
if (a[*pi] == '#' || *pi >= n)
{
(*pi)++;
return NULL;
}
BTNode* root = (BTNode*)malloc(sizeof(BTNode));
if (root == NULL)
{
perror("malloc fail");
exit(-1);
}
root->data = a[*pi];
(*pi)++;
root->left = BinaryTreeCreate(a, n, pi);
root->right = BinaryTreeCreate(a, n, pi);
return root;
}
销毁二叉树
由于我们创建的二叉树是链式存储的,要销毁一个二叉树那么就要销毁这个二叉树的所有节点。
怎么去操作呢?遇到一个节点销毁一个吗?那肯定是行不通的,删除当前节点那么下一个节点就找不着了。所以还是递归
void BinaryTreeDestory(BTNode* root)
{
if (root == NULL)
{
return;
}
BTNode* left = root->left;
BTNode* right = root->right;
free(root);
root = NULL;
BinaryTreeDestory(left);
BinaryTreeDestory(right);
}
二叉树的构建及遍历
这是一道牛客网上的题,如果你们可以把它做出来,那证明你是真的厉害,学会了。