文章目录
1. 树的概念及结构(了解)
1.1 树的概念
树是一种非线性的数据结构,它是由 n(n >= 0)个有限个节点组成的一个具有层次关系的集合。把它叫做树是因为他看起来像一颗倒挂的树,也就是说,它是根朝上,而叶朝下的。
树的定义有两点: 1)有且仅有一个特定的称为根(root)的结点; 2)当结点数量>1时,其余结点可分为 若干个互不相交的有限集 ,其中每个集合也可以看作一颗树,称之为根的子树。
- 有一个特殊的节点,称为根节点(root),根节点没有前驱节点。
- 除根节点外,其余节点别分为M(M > 0)个互不相交的集合T1、T2……其中每一个集合又是一棵树结构与树类似的子树。每颗子树的根节点有且只有一个前驱,可以有0个或多个后继。
- 树是递归定义的。
判断是否是一个树
- 子树是不相交的
- 出了根节点外,每个节点有且只有一个父节点
- 一颗N个节点的树有N-1条边
几个重要的知识点:
- 节点的度:一个节点含有的子树的个数称为该节点的度;如上图:A的度为6,B的度为0
- 叶节点或终端节点:度为0的节点称为叶节点;如上图:B、C、H、I、P……等都是叶节点
- 非终端节点或分支节点:度不为0的节点;如上图:D、E、F……等节点为分支节点
- 双亲节点或父节点:若一个节点含有子节点,则这个节点称为其子节点的父节点;如上图:A是B的父节点
- 孩子节点或子节点:一个节点含有的子树的根节点称为该节点的子节点;如上图:B是A的子节点
- 兄弟节点:具有相同父节点的节点称为兄弟节点;如上图:B、C是兄弟节点
- 树的度:一棵树中,最大节点的度称为树的度;如上图:树的度为6
- 节点的层次:从根开始定义起,根为第一层,根的子节点为第二层,以此类推;
- 树的高度或深度:树中节点的最大层次;如上图:树的高度为4
- 节点的祖先:从根到该节点所经的分支上所有的节点;如上图:A是所有节点的祖先
- 子孙:以某点为根的子树中任一节点都称为该节点的子孙;如上图:所有节点都是A的子孙
- 森林:有m(m>0)棵互不相交的多棵树的集合称为树;(数据结构中的学习并查集本质就是一个森林)
1.2 树的表示方法
树的结构相对线性表就相对复杂很多,要存储起来更加复杂。实际中树有很多种表示方法,如:双亲表示法、孩子表示法、孩子兄弟表示法等等
- 左孩子右兄弟表示法
typedef int DataType;
struct Node
{
struct Node* firstChild1;//第一个孩子节点
struct Node* pNextBrother;//指向下一个兄弟节点
DataType data;//数据域
}
- 双亲表示法
1.3 树在实际中的应用
- 表示文件系统的目录
2. 二叉树的概念及节点(重点)
2.1 二叉树的概念
一棵二叉树是节点的一个有限集合,该集合或者为空,或者是由一个根节点加上两棵别称为左子树和右子树的二叉树组成。
2.2 二叉树的特点
- 每个节点最多有两棵子树,即二叉树不存在度大于2的节点
- 二叉树的子树有左右之分,其子树的次序不能颠倒
2.3 特殊的二叉树
- 满二叉树:一个二叉树,如果每一层的节点的度都打到最大数,则这个二叉树就是满二叉树。也就是说,如果一个二叉树的层数为k,且节点总数为(2^k)- 1,则他就是满二叉树
- 完全二叉树:完全二叉树是效率最高的数据结构,完全二叉树是由满二叉树而引起的。对于深度为k的,有n个节点的二叉树,当且仅当其每一个节点都与深度为k的满二叉树中编号从1至n的节点都一一对应时称为完全二叉树。注意:满二叉树是一种特殊的完全二叉树
2.5 二叉树的性质
- 若规定根节点层数为1,则一颗非空二叉树的第i层上最多有2^(n-1)个节点(满二叉树)
- 若规定根节点层数为1,则深度为h的二叉树最大节点数为2^h - 1(满二叉树)
- 对任何一颗二叉树,如果度数为0的节点个数为n0,度为2的节点个数为n2,则有n0=n2+1
- 若规定根节点层数为1,具有n个节点的满二叉树的深度,h = log2(N+1) (以2为底,(N+1)为对数)
相关习题:
解析2:
解析3:
2.6 二叉树的遍历
所谓遍历就是指沿着某条搜索路线,依次对树中的每个节点均做一次且仅做一次的访问。遍历二叉树是很重要的运算之一,是二叉树上进行其他运算的基础。
2.6.1 深度优先遍历
二叉树的前序、中序、后序遍历也称为深度优先遍历,因为他们都是先往下遍历的,而不管左右。
遍历方式:
- 前序遍历(NLR):访问根节点的操作发生在遍历其左右子树之前(根左右)
- 中序遍历(LNR):访问根节点的操作发生在遍历其左右子树之种(左根右)
- 后序遍历(LRN):访问根节点的操作发生在遍历其左右子树之后(左右根)
代码实现:
#include <stdio.h>
#include <stdlib.h>
typedef char BTDataType;
typedef struct BinaryTreeNode
{
struct BinaryTreeNode* left;//左孩子
struct BinaryTreeNode* right;//右孩子
BTDataType data;//数据域
}BTNode;
void PrevOrder(BTNode* root)//前序
{
if (root == NULL)
{
printf("NULL ");
return;
}
printf("%c ", root->data);
PrevOrder(root->left);
PrevOrder(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);
}
int main()
{
BTNode* A = (BTNode*)malloc(sizeof(BTNode));
A->data = 'A';
A->left = NULL;
A->right = NULL;
BTNode* B = (BTNode*)malloc(sizeof(BTNode));
B->data = 'B';
B->left = NULL;
B->right = NULL;
BTNode* C = (BTNode*)malloc(sizeof(BTNode));
C->data = 'C';
C->left = NULL;
C->right = NULL;
BTNode* D = (BTNode*)malloc(sizeof(BTNode));
D->data = 'D';
D->left = NULL;
D->right = NULL;
BTNode* E = (BTNode*)malloc(sizeof(BTNode));
E->data = 'E';
E->left = NULL;
E->right = NULL;
A->left = B;
A->right = C;
B->left = D;
B->right = E;
PrevOrder(A);
printf("\n");
InOrder(A);
printf("\n");
PostOrder(A);
printf("\n");
system("pause");
return 0;
}
我们主要要了解具体递归的顺序,我们可以自己在纸上画一画
2.6.2 广度优先遍历
层序遍历:除了先序遍历、中序遍历、后序遍历外,还可以对二叉树进行层序遍历。设二叉树的根节点所在的层数为1,层序遍历就是从所在二叉树的根节点出发,首先访问第一层的根节点,然后从左到右访问第二层上的节点,以此类推,自上而下,自左至右逐层访问树的节点的过程就是层序遍历,也称为广度优先遍历。
二叉树的广度优先遍历思想:我们利用队列(先进先出),上一层出的时候带着下一层进
//层序遍历 广度优先遍历
void LevelOrder(BTNode* root)
{
//核心思想——上一层出的时候带下一层节点进
queue<BTNode*> q;//创建队列容器
if (root)
{
q.push(root);//入队
}
while (!q.empty())
{
BTNode* front = q.front();
q.pop();
printf("%c ", front->data);
if (front->left)
{
q.push(front->left);
}
if (front->right)
{
q.push(front->right);
}
}
printf("\n");
}
二叉树遍历的例题:
答案为:A A D
2.7 计算二叉树的节点个数
很多人看到计算二叉树的节点个数,心想那么简单,直接遍历一下二叉树不就好了吗。
//节点个数
int size = 0;
void TreeSize(BTNode* root)
{
if (root == NULL)
{
return;
}
else
{
size++;
}
TreeSize(root->left);
TreeSize(root->right);
}
其实,vs2022已经报错了,为什么呢?因为我们定义了一个全局的size,在我们计算了节点为A的节点个数时,得到的大小是5,但是在计算节点为B的节点个数为8,。就是因为我们这个变量A是个全局变量,会累加的。
void TreeSize(BTNode* root, int* psize)
{
if (root == NULL)
{
return;
}
else
{
++(*psize);
}
TreeSize(root->left, psize);
TreeSize(root->right, psize);
}
int Asize = 0;
TreeSize(A, &Asize);
printf("TreeSize(A): %d", Asize);
int Bsize = 0;
TreeSize(B, &Bsize);
printf("TreeSize(A): %d", Bsize);
我们要记得把地址传过去。
但是,如果我们知道多线程的时候,程序在运行时,可能有多个进程在同时进行,我们就无法保证在求一个节点的数时的节点前,先把size赋值为0了。怎么办呢?看下面的代码就很好的解决了这个问题。
//返回节点个数
int TreeSize(BTNode* root)
{
return root == NULL ? 0 : TreeSize(root->left) + TreeSize(root->right) + 1;
}
这个程序很巧,我们求一棵二叉树节点个数,就计算它左子树节点个数 + 右子树节点个数 + 1(根节点)
2.8 计算二叉树叶子节点个数
计算叶子节点个数和求节点个数的思想是一样的
//求叶子节点个数
int TreeLeafSize(BTNode* root)
{
if (root == NULL)
{
return 0;
}
if (root->left == NULL && root->right == NULL)
return 1;
return TreeLeafSize(root->left) + TreeLeafSize(root->right);
}