树和二叉数超级无敌详解(c语言版)
一、树的概念和结构(了解)
1.1 树的概念
之前我们学的链表、队列、栈都是线性数据结构,而树是我们第一个接触到的非线性的数据结构,它是由n个有限节点组成的一个具有层次关系的集合,可以联系到我们现实生活中的树,只不过要倒过来看,根在上,叶子在下,而一整棵树可以看成由若干的子树组成,可以看出树是递归定义的。
接下来介绍几个树中常用的名词:
- 节点的度:一个节点含有的子树的个数称为该节点的度(B、C、D、E、F、G); 如上图:A的为6。
- 叶节点或终端节点:度为0的节点称为叶节点(可以理解成没孩子); 如上图:B、C、H、I…等节点为叶节点。
- 非终端节点或分支节点:度不为0的节点(有孩子); 如上图:D、E、F、G…等节点为分支节点。
- 双亲节点或父节点:若一个节点含有子节点,则这个节点称为其子节点的父节点; 如上图:A是B的父节点。
- 孩子节点或子节点:一个节点含有的子树的根节点称为该节点的子节点; 如上图:B是A的孩子节点。
- 兄弟节点:具有相同父节点的节点互称为兄弟节点; 如上图:B、C是兄弟节点。
- 树的度:一棵树中,最大的节点的度称为树的度; 如上图:树的度为6。
- 节点的层次:从根开始定义起,根为第1层,根的子节点为第2层,以此类推。(也可以将根算成第0层,但更推荐算成第1层)
- 树的高度或深度:树中节点的最大层次; 如上图:树的高度为4。
- 节点的祖先:从根到该节点所经分支上的所有节点;如上图:A是所有节点的祖先。
- 子孙:以某节点为根的子树中任一节点都称为该节点的子孙。如上图:所有节点都是A的子孙。
- 森林:由m(m>0)棵互不相交的多颗树的集合称为森林;(数据结构中的学习并查集本质就是一个森林)。
如何判断给定的图是不是树呢?
- 子树是不相交的
- 除根节点外每个节点只能有一个父亲
- N个节点有N-1条边
这里我给大家说一个最直接的办法,就是树不会成环,看见有环就不是树
1.2 树的表示
树的存储结构很复杂,也有很多的存储方式,例如:双亲表示法、孩子表示法、孩子兄弟表示法等等,这里我们了解一下最常用的孩子兄弟表示法。
typedef int DataType;
struct TreeNode
{
struct TreeNode* firstChild; //第一个孩子结点
struct TreeNode* nextBrother; //指向兄弟结点
DataType data;
}
我们来看图更加直观一点
二、二叉树的概念及结构
2.1二叉树的概念
二叉树(binary tree)是指树中节点的度不大于2的有序树,它是一种最简单且最重要的树。二叉树的递归定义为:二叉树是一棵空树,或者是一棵由一个根节点和两棵互不相交的,分别称作根的左子树和右子树组成的非空树;左子树和右子树又同样都是二叉树。
两个特殊的二叉树:
-
满二叉树:除最后一层外,每个节点都有2个孩子,总节点数为2^n - 1,n为层数。
-
完全二叉树:是一种特殊的满二叉树,就是在满二叉树的基础上,最后一层有序(从右往左)的缺失了一些结点,假如全部缺失了,就是满二叉树了。
2.2二叉树的存储方式
-
顺序存储:就是用数组来存储二叉树,但一般只适用完全二叉树,不然会造成空间的浪费。学过堆排序同学应该知道。在物理结构上是数组,但是在逻辑上是棵树。
-
链式存储:二叉树的链式存储结构是指,用链表来表示一棵二叉树,即用链来指示元素的逻辑系。 通常的方法是链表中每个结点由三个域组成,数据域和左右指针域,左右指针分别用来给点左孩子和右孩子所在的链结点的存储地址。
typedef int BTDataType;
struct BinaryTreeNode
{
struct BinaryTreeNode* left; //左孩子
struct BinaryTreeNode* right; //右孩子
BTDataType data; //存储数据
};
三、二叉树的性质
3.1性质
- 若规定根节点的层数为1,则一棵非空二叉树的第i层上最多有2^(i-1) 个结点.
- 若规定根节点的层数为1,则深度为h的二叉树的最大结点数是2^h- 1(满二叉树).
- 对任何一棵二叉树, 如果度为0其叶结点个数为 n0, 度为2的分支结点个数为 n2,则有n0=n2
+1 - 若规定根节点的层数为1,具有n个结点的满二叉树的深度,h=LogN(等比求和反解得出)
3.2习题
- 某二叉树共有 399 个结点,其中有 199 个度为 2 的结点,则该二叉树中的叶子结点数为( )
A 不存在这样的二叉树
B 200
C 198
D 199
解析:利用性质三很快得出答案为199+1=200,B - 在具有 2n 个结点的完全二叉树中,叶子结点个数为( )
A n
B n+1
C n-1
D n/2
解析:二叉树的节点的度的情况只有3种,故设度为1的节点个数为a,度为2的节点个数为b,则度为0(叶子节点)的节点的个数为(b+1),又因为这是完全二叉树,根据该数的性质可以得出,度为1的节点的个数要么为0要么为1,故有2种情况 0+b+b+1=2n,解出b=(2n-1)/2。或者1+b+b+1=2n,解出b=n-1。综上答案为A - 一棵完全二叉树的节点数位为531个,那么这棵树的高度为( )
A 11
B 10
C 8
D 12
解析:根据二叉树的性质和完全二叉树的特点可得节点个数为2^h - 1 - x,其中h为高度,x为最后一层缺失的节点个数,范围是0~2^(i-1),将选项带入得出答案B
答案:B A B
四、二叉树的遍历
4.1前序遍历(根、左子树、右子树)
void PrevOrder(struct BinaryTreeNode* root)
{
if(root == NULL)
return; //遍历到空就返回
printf("%d",root->data); //打印数据
prevOrder(root->left); //递归左子树
prevOrder(root->right); //递归右子树
}
4.2中序遍历(左子树、根、右子树)
void InOrder(struct BinaryTreeNode* root)
{
if(root == NULL)
return; //遍历到空就返回
InOrder(root->left); //递归左子树
printf("%d",root->data); //打印数据
InOrder(root->right); //递归右子树
}
4.3后序遍历(左子树、右子树、根)
void PostOrder(struct BinaryTreeNode* root)
{
if(root == NULL)
return; //遍历到空就返回
PostOrder(root->left); //递归左子树
PostOrder(root->right); //递归右子树
printf("%d",root->data); //打印数据
}
4.4层序遍历(所在二叉树的根节点出发,首先访问第一层的树根节点,然后从左到右访问第2层上的节点,接着是第三层的节点,以此类推)
void LeverOrder(struct BinaryTreeNode* root)
{
//这里队列函数就不展示了,主要看树
Queue q;
//要借助队列这个数据结构来实现
if(root == NULL)
{
return;
)
QueuePush(&q, root); //根不为空将根(地址)入队列
while(!QueueEmpty(&q)) // 当队列为空时结束循环
{
struct BinaryTreeNode* tmp = QueueFront(&q); //取出队头的数据
printf("%d",tmp->data); //打印队头数据
QueuePop(&q); //出队列
//出队列的时候将自己的孩子带进队列
if(tmp->left != NULL)
{
QueuePush(&q,tmp->left);
}
if(tmp->right != NULL)
{
QueuePush(&q, tmp->right);
}
}
}
4.5几道选择题
- 某完全二叉树按层次输出(同一层从左到右)的序列为 ABCDEFGH 。该完全二叉树的前序序列为()
A ABDHECFG
B ABCDEFGH
C HDBEAFCG
D HDEBFGCA
解析:层序输出则是一层一层按顺序从左往右输出,根据二叉树的性质先构造出二叉树!
易得出前序序列为ABDHECFG
2. 二叉树的先序遍历和中序遍历如下:先序遍历:EFHIGJK;中序遍历:HFIEJKG.则二叉树根结点为
()
A E
B F
C G
D H
解析:先从先序遍历顺序得出根E,然后在中序遍历,从根向左右分为左子树(HFI)和右子树(JKG),以此类推得到树的结构
易得根节点为E
- 设一课二叉树的中序遍历序列:badce,后序遍历序列:bdeca,则二叉树前序遍历序列为____。
A adbce
B decab
C debac
D abcde
解析:和上题类似,只需将后序遍历倒过来看,先找到根a,再通过中序遍历得到左子树(b)和右子树(dce),以此类推得到树的结构
易得前序遍历为abcde
答案:A A D
五、常见oj题
5.1二叉树的最大深度
int TreeDepth(struct BinaryTreeNode* root)
{
if(root == NULL)
return 0;
int leftDepth= TreeDepth(root->left);
int rightDepth = TreeDepth(root->right);
return (leftHeight > rightHeight)? leftHeight+1 : rightHeight+1;
}
5.2判断是否为平衡二叉树(每个节点的深度相差的绝对值不超过1)
bool IsBalance(struct BinaryTreeNode* root)
{
if(root == NULL)
return true;
return abs(TreeDepth(root->left) - TreeDepth(root->left)) <= 1 && IsBalance(root->left) && IsBalance(root->right)
}
5.3二叉树高度
int TreeHeight(struct BinaryTreeNode* root)
{
if(root == NULL)
return 0;
int leftHeight = TreeHeight(root->left); //左树高度
int rightHeight = TreeHeight(root->right); //右树高度
return (leftHeight > rightHeight)? leftHeight+1 : rightHeight+1; //返回左、右高度高的高度再加上本身这一层高度(规定第一层高度为1不为0)
}
5.4构建及遍历二叉树
#include <stdio.h>
#include <stdlib.h>
//二叉树节点
struct BinaryTreeNode
{
struct BinaryTreeNode* left;
struct BinaryTreeNode* right;
char data;
};
//前序建立二叉树
struct BinaryTreeNode* CreatTree(char* s,int* pi)
{
//pi是记录访问的字符串的位置
if(s == NULL)
{
return NULL;
}
if(s[*pi] == '#')
{
(*pi)++;
return NULL;
}
struct BinaryTreeNode* Node = (struct BinaryTreeNode*)malloc(sizeof(struct BinaryTreeNode));
Node-> data = s[*pi];
(*pi)++;
Node->left = CreatTree(s,pi);
Node->right = CreatTree(s,pi);
return Node;
}
//中序遍历
void InOrder(struct BinaryTreeNode* root)
{
if(root == NULL)
return; //遍历到空就返回
prevOrder(root->left); //递归左子树
printf("%d",root->data); //打印数据
prevOrder(root->right); //递归右子树
}
int main()
{
char s[100];
scanf("%s",s);
int i = 0;
struct BinaryTreeNode* root = CreatTree(s,&i);
InOrder(root);
return 0;
}
5.5节点个数
int TreeSize(struct BinaryTreeNode* root)
{
if(root == NULL)
return 0;
int left = TreeSize(root->left); //左树节点个数
int right = TreeSize(root->right); //右树节点个数
return left + right + 1; //返回左、右节点个数再加上本身
}
5.6叶子节点个数
int TreeLeafSize(struct BinaryTreeNode* root)
{
if(root == NULL)
return 0;
if(root->left == NULL && root->right == NULL)
return 1;
return TreeLeafSize(root->left) + TreeLeafSize(root->right);
}