之前的顺序表和链表都是一种线性的结构,树是非线性的结构。
文章目录
一、树的概念
树是一种非线性的数据结构,它是由n(n>=0)个有限结点组成,当 n=0 时,称为空树。
- 树的起始节点是一个特殊的结点,称为根结点,它没有前驱结点。
- 除根节点外,其余结点也是一个类似于树结构的子树,也就是说这个节点也有子节点。所以树是一种递归结构。
- 节点的度:一个节点含有的子树的个数称为该节点的度,比如A的度是3,B的度是2
- 叶节点或终端节点:度为0的节点称为叶节点。
- 非终端节点或分支节点:度不为0的节点。
- 双亲节点或父节点:若一个节点含有子节点,则这个节点称为其子节点的父节点; 如上图:A是B的父节点。
- 孩子节点或子节点:一个节点含有的子树的根节点称为该节点的子节点; 如上图:B是A的孩子节点。
- 兄弟节点:具有相同父节点的节点互称为兄弟节点; 如上图:B、C是兄弟节点,但F、G不是兄弟节点,因为它们的父节点不相同。
- 树的度:一棵树中,最大的节点的度称为树的度,上图中树的度是3.
- 节点的层次:从根开始定义起,根为第1层,根的子节点为第2层。
- 树的高度或深度:树中节点的最大层次; 如上图:树的高度为4。
- 节点的祖先:从根到该节点所经分支上的所有节点;如上图:J节点的祖先是EBA
- 子孙:以某节点为根的子树中任一节点都称为该节点的子孙。如上图:所有节点都是A的子孙。
- 森林:由m(m>0)棵互不相交的多颗树的集合称为森林。
注意,子树和子树之间是不能相交的,除了根节点外的其他节点只能有一个父节点
比如这种结构就不能称为树:
1.1.树用代码表示
树要用C语言代码表示起来还是比较麻烦的,因为一个节点的度是不确定性的,因此父节点该存几个指针指向子节点是不清楚的。常见的表示法有双亲表示法,孩子表示法、孩子兄弟表示法等等
最常用的是孩子兄弟表示法:让兄弟节点指向兄弟节点,父节点指向第一个孩子节点,因此只需要存两个指针就可以了:
typedef int DataType;
struct Node
{
struct Node* firstChildl; // 第一个孩子结点
struct Node* ![pNextBrother] // 指向其下一个兄弟结点
DataType data; // 结点中的数据域
};
比如上面的树用这种方法表示:
二、二叉树
顾名思义,二叉树就是节点的度不超过2的树,也就是最多有两个子树,二叉树的子树有左右之分,其子树的次序不能颠倒,一般称为左子树和右子树。
2.1特殊的二叉树
- 满二叉树
一个二叉树,如果每一个层的结点数都达到最大值,则这个二叉树就是满二叉树。也就是说,如果一个二叉树的层数为K,且结点总数是(2K) -1(因为第一层节点是20,第二层是21,一直到最后一层K是2K-1,用等比数列求和可以得出总结点个数) ,则它就是满二叉树。
- 完全二叉树
如果二叉树的层数是K,前K-1层都是满的,只有最后一层不满,但是最后一层从左往右都是连续的,这种数就是完全二叉树
注意完全二叉树最后一层从左往右必须是连续的,下面的情况就不是完全二叉树,因为D少一个左子树,最后一层不连续:
2.2.二叉树的性质
二叉树的这些性质可以帮助我们做题:
- 若规定根节点的层数为1,则一棵非空二叉树的第i层上最多有2(i-1)个结点,最少有1个节点.
- 若规定根节点的层数为1,则深度为h的二叉树的最大结点数是2h- 1,最小节点数是2h-1+1(最后一层最少有一个节点).
- 对任何一棵二叉树, 度为0的节点个数比度为2的节点个数多1
- 若规定根节点的层数为1,具有n个结点的满二叉树的深度,h=Log2(n+1)(由2h- 1=n得出)。
2.3.二叉树的存储形式
二叉树一般可以使用两种结构存储,一种顺序结构,一种链式结构,也就是类似于顺序表和链表的形式。
2.3.1.顺序存储
顺序结构存储就是使用数组来存储,一般使用数组只适合表示完全二叉树,因为不是完全二叉树
会有空间的浪费。二叉树顺序存储在物理上是一个数组,在逻辑上是一颗二叉树。
2.3.2.链式存储
用链表来表示一棵二叉树,即用链来指示元素的逻辑关系。 通常的方法是链表中每个结点由三个域组成,数据域和左右指针域,左右指针分别用来给出该结点左孩子和右孩子所在的链结点的存储地址,这种形式被称作二叉链。如果再加入一个指向父节点的指针,那么这种形式的链表被称作三叉链 。这里主要用到的形式是二叉链。
2.4.二叉树的遍历
遍历是指沿着某条搜索路线,依次对树中每个结点均做一次且仅做一次访问。
二叉树的遍历分为三种:前序遍历、中序遍历和后续遍历。
任何一颗二叉树分为三个部分:1.根节点,2.左子树,3.右子树。这三种遍历方式的区别在于访问根节点的时机不同。
二叉树的遍历采用的是分治算法:分而治之,大问题分成类似的子问题,子问题再分成子问题,直到子问题不可再分割。因此用代码实现采用的是递归方式。
2.4.1.前序遍历
前序遍历也就是先访问根,它的遍历顺序是:根 左子树 右子树
每到一个节点,都会优先访问以该节点为子树的根节点,然后访问左子树,再访问右子树。
根节点A永远被第一个遍历:
2.4.2.中序遍历
中序遍历的顺序是:左子树 根 右子树
每到一个节点,都会优先访问以该节点为子树的左子树,然后根节点,再访问右子树。
根节点A的左边是左子树,右边是右子树
2.4.3.后序遍历
后序遍历的顺序是:左子树 右子树 根
每到一个节点,都会优先访问以该节点为子树的左子树,然后访问右子树,最后访问根。
根节点A最后一个被遍历:
2.5.二叉树的一些题目
某二叉树共有 399 个结点,其中有 199 个度为 2 的结点,则该二叉树中的叶子结点数为( )
A 不存在这样的二叉树
B 200
C 198
D 199
由性质可知:对任何一棵二叉树, 度为0的节点个数比度为2的节点个数多1。因此叶子节点的个数是200
在具有 2n 个结点的完全二叉树中,叶子结点个数为( )
A n
B n+1
C n-1
D n/2
假设度为0的节点有X0个,度为1的节点有X1个,度为2的节点有X2个,由题意可知X0+X1+X2=2n
因为度为0的节点比度为2的节点多1,所以X2=X0-1,因此2X0+X1-1=2n,
完全二叉树中度为1的节点最多有一个,所以X1=1,因此X0=n
(如果X1=0,则算不出整数,所以X1=1)
一棵完全二叉树的节点数位为531个,那么这棵树的高度为( )
A 11
B 10
C 8
D 12
假设这棵树的高度是h,由于是完全二叉树,所以假设最后一层缺了X个节点
由题意得出公式2^h-1-X=531;
最后一层最少有一个,最多一个都不缺,因此X的范围是0~~2^(h-1)-1
带入ABCD的范围可以得出结果h=10
某完全二叉树按层次输出(同一层从左到右)的序列为 ABCDEFGH 。该完全二叉树的前序序列为()
A ABDHECFG
B ABCDEFGH
C HDBEAFCG
D HDEBFGCA
二叉树的前序遍历和中序遍历如下:前序遍历:EFHIGJK;中序遍历:HFIEJKG.则二叉树根结点为
()
A E
B F
C G
D H
前序和后序确定根,中序确定左右子树的区间,因此只需要前序+中序或者后序+中序即可还原二叉树:通过前序遍历确定根节点为E,左子树的根为F,再通过中序确定F的左右子树为HI,再通过前序确定右子树的根是G,通过中序确定G没有右子树,再通过前序确定G的左子树节点是J,最后通过中序确定J的右子树是K,这里如果K是J的左子树,那么中序遍历应该是KJ,而不是JK
设一课二叉树的中序遍历序列:badce,后序遍历序列:bdeca,则二叉树前序遍历序列为____。
A adbce
B decab
C debac
D abcde
前序遍历是abcde
三、二叉树的代码实现
- 声明二叉树的结构体
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);
}
为了更好的说明前序遍历的逻辑,这里以下面的代码举例:
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);
}
这个二叉树的结构如下:
程序的流程图:
- 中序
//中序
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 TreeSize(BTNode* root)
{
return root == NULL ? 0 : TreeSize(root->left) + TreeSize(root->right) + 1;
}
如果数的节点不等于空指针,则返回这个左子树的函数和右子树的函数再加上这个节点的个数1:
- 求叶子节点的个数
//求叶子个数
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);
}
- 求树的深度
//求树的深度
int maxDepth(BTNode* root)
{
if (root == NULL)
{
return 0;
}
int leftDepth = maxDepth(root->left);
int rightDepth = maxDepth(root->right);
return leftDepth > rightDepth ? leftDepth + 1 : rightDepth + 1;
}
如果是空,则高度为0.
如果是非空,则对于一个节点,先求该节点左右子树的深度,该节点的深度等于左右子树深度中大的那个深度+1
- 判断是否为平衡二叉树(每个节点的左右子树的高度差的绝对值不超过1)
//判断是否为平衡二叉树
bool isBalanced(BTNode* root) {
if (root == NULL)
{
return true;
}
int leftDepth = maxDepth(root->left);
int rightDepth = maxDepth(root->right);
return abs(leftDepth - rightDepth) < 2 && isBalanced(root->left)
&& isBalanced(root->right);//abs是求绝对值
}
- 销毁二叉树
//销毁二叉树
void DestoryTree(BTNode** root)//要将root置为空指针,这里传指针的地址
{
if ((*root) = NULL)
{
return;
}
DestoryTree((*root)->left);
DestoryTree((*root)->right);
free(*root);
(*root) = NULL;
}
3.1.测试代码
#include<stdio.h>
typedef char BTDataType;
typedef struct BinaryTreeNode
{
struct BinaryTreeNode* left;
struct BinaryTreeNode* right;
BTDataType data;
}BTNode;
//求节点的个数
int TreeSize(BTNode* root)
{
return root == NULL ? 0 : TreeSize(root->left) + TreeSize(root->right) + 1;
}
//求叶子个数
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);
}
//求树的深度
int maxDepth(BTNode* root)
{
if (root == NULL)
{
return 0;
}
int leftDepth = maxDepth(root->left);
int rightDepth = maxDepth(root->right);
return leftDepth > rightDepth ? leftDepth + 1 : rightDepth + 1;
}
//判断是否为平衡二叉树
bool isBalanced(BTNode* root) {
if (root == NULL)
{
return true;
}
int leftDepth = maxDepth(root->left);
int rightDepth = maxDepth(root->right);
return abs(leftDepth - rightDepth) < 2 && isBalanced(root->left)
&& isBalanced(root->right);
}
//销毁二叉树
void DestoryTree(BTNode** root)
{
if ((*root) = NULL)
{
return;
}
DestoryTree((*root)->left);
DestoryTree((*root)->right);
free(*root);
(*root) = NULL;
}
//前序
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");
printf("%d\n", TreeSize(A));
printf("%d\n", TreeSize(B));
}