文章目录
一、树的基础概念
1、学会看树中的树
在数据结构中,树对比现实中的树,根在上,枝干在下。
并且树在连线中不会构成回路。
下面就是一颗数据结构所表示的树,它以A为根节点,D、E、F为叶节点。
如果以A为根看,那么整个就是一颗以A为根的树。

从A向下看,
当以B为根看,那么就有以B为根的子树(包括B、D、E节点)
以C为根看,那么就有以C为根的子树(包括C、F节点),并且还可以继续往下分,比如最后将D为根,那么它本身就是一个树。

2、树的基础概念
- 节点的度:一个节点有多少个子树。(下面A节点的度为6)
- 叶节点或终端节点:没有子节点的结点,度为0.(比如下面的BCHIPQKLMN)
- 父节点:若节点有子结点,这节点就是父节点。(A有子节点,所以A是父节点)
- 孩子节点:一个节点含有的子树的根节点为该节点的子节点
- 兄弟结点:来自同一个父节点
- 树的度:树中节点最大的度,就是树的度(6嘛)
- 节点的层次:从根开始为第一层,根的子节点为第二层,以此类推
- 树的高度或深度:树中节点的最大层数。(该树高度为4)
- 结点的祖先:从根到该节点所经分支上的所有节点。(比如P的祖先有JEA)
二、二叉树的概念和结构
1、二叉树的特点
- 每个结点最多有两颗子数,既二叉树不存在度数大于2的节点
- 二叉树的子树有左右之分,其子树的次序不能颠倒。

2、二叉树的前序、中序、后序排列
前序(先根):根 左子树 右子树
中序(中根):左子树 根 右子树
后序(后根):左子树 右子树 根
在这些顺序遍历中,最重要的就是要将大问题化成小问题,小问题一直划分到不能分。

比如在这颗树的前序中:
先看一整颗树(大问题),根为A,有着分别以B为根的左子树和以C为根的右子树,而前序是先根再左子树,所以来到以B为根的左子树(小问题),B又有以D和以E为根的左右子树,这次遍历到D,D之后没有了,所以遍历到E,E之后也没有了,所以以B为根的左子树遍历完了,之后在开始以C为根的右子树。
所以整个过程可以表示为:
A->(B->(D->NULL->NULL)->(E->NULL->NULL))->(C->(F->NULL->NULL)->NULL)
而中序和后序可以表示为:
中序:
((NULL->D->NULL)->B->(NULL->E->NULL))->A->((NULL->F->NULL)->C->NULL)
后序:
((NULL->NULL->D)->(NULL->NULL->E)->B)->((NULL->NULL->F)->NULL->C)->A
如果将一个节点看成链表中的一个结点,对于前中后序的遍历,似乎可以用递归实现,因为限定条件很明显就是结点为不为空,并且每个结点都可以当作以自己为根的树。
typedef char BTDatatype;
typedef struct BinaryTreeNode {
struct BinaryTreeNode* left;
struct BinaryTreeNode* right;
BTDatatype data;
}BT;
//前序遍历打印
void PrevOrder(BT* root)
{
if (root == NULL) {
printf("NULL ");
return;
}
//前中后序的打印顺序,只要在这三行进行调换就行了。
printf("%c ", root->data);//先打印根
PrevOrder(root->left);//再打印左子树
PrevOrder(root->right);//再打印右子树
}
3、二叉树的性质
首先让我们来认识一下
满二叉树和完全二叉树
满二叉树的每一次结点都达到最大值。
完全二叉树最后一层结点没有达到最大值,且最后一层的结点是连续的。

满二叉树中: 完全二叉树中:
若高度为h
结点总数为:(2^h)-1 结点总数为: [2^(h-1) +1,2^h-2] 并且只能取整
最后一层结点树为:2^(h-1) 最后一层结点树为:(0,2^(h-1)) 并且只能取整
推出:h=log2(N+1) 假设缺少X个节点 h=log2(N+1+X)
可以得出以下重要的性质:
- 对于任意一个二叉树,如果度为0的结点有N0个,度为2的结点有N1个,那么一样会有N0=N1+1。
- 在完全二叉树中,度为1的结点数一定是1或者0。
4、搜索二叉树
其中,任意一棵树,左子树都要比根小,右子树都要比根大。
因为凭借它的这个性质,就可以找一个数,而且,如果左右子树均匀的话,那么时间复杂度就是O(LogN),查找高度次。

5、求二叉树的结点数、叶结点数和二叉树的最大深度
//求树的结点个数
int TreeSize(BT* root)
{
if (root == NULL)
{
return 0;
}
//不为0先加1,然后再往左子树看,左子树看完看右子树。
return 1 + TreeSize(root->left) + TreeSize(root->right);
}
//求树的叶结点个数
int TreeLeafSize(BT* root)
{
if (root->left == NULL && root->right == NULL) //叶节点左右都为空
{
return 1;
}
//不为叶节点继续先往左子树走。
return TreeLeafSize(root->left) + TreeLeafSize(root->right);
}
//求树的最大深度
int maxDepth(struct TreeNode* root){
if(root==NULL)
return 0;
//用变量保存左树深度
int leftDepth=maxDepth(root->left);
//用变量保存右树深度
int rightDepth=maxDepth(root->right);
//如果左树大于右树深度,则大的深度加1。
return leftDepth>rightDepth?leftDepth+1:rightDepth+1;
}
6、二叉树的层序遍历
层序遍历顾名思义,就是从第一层开始将数据一层一层从左到右顺序往下遍历。
如果需要对二叉树进行层序遍历,使用递归深度优先肯定是不合适的,所以这里考虑到广度优先,从头到尾进行操作,但又因为二叉树结构问题难以实现非递归遍历,所以通过队列结构来储存二叉树中数据是一个选择。
代码实现
//二叉树层序遍历
void BinaryTreeLevelOrder(BTNode* root)
{
Queue q;
QueueInit(&q);
if (root != NULL)
{
QueuePush(&q, root);
}
while (!QueueEmpty(&q))
{
BTNode* front = QueueFront(&q);
printf("%d ", front->data);
if (front->left != NULL)
QueuePush(&q, front->left);
if (front->right != NULL)
QueuePush(&q, front->right);
QueuePop(&q);
}
QueueDestroy(&q);
}
如何判断一个树是否是完全二叉树?
完全二叉树有一个特点,就是任何空结点后不会有其它结点,抓住这个特点,在层序遍历后,如果遇到空结点,那么看队列空结点后是否有非空结点,没有那么就是完全二叉树。
代码实现:
bool BinaryTreeComplete(BTNode* root)
{
Queue q;
QueueInit(&q);
if (root != NULL)
{
QueuePush(&q, root);
}
//入队
while (!QueueEmpty(&q))
{
BTNode* front = QueueFront(&q);
if (front == NULL)
{
break;
}
QueuePush(&q, front->left);
QueuePush(&q, front->right);
QueuePop(&q);
}
//找非空结点
while (!QueueEmpty(&q))
{
BTNode* front = QueueFront(&q);
QueuePop(&q);
if (front != NULL)
{
QueueDestroy(&q);
return false;
}
}
QueueDestroy(&q);
return true;
}
三、简单练习题
1、平衡二叉树
【leetcode】平衡二叉树
很简单的思路,只要确定任何左右两个子树的深度,然后相减看看是否大于1就行了。
int maxDepth(struct TreeNode* 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(struct TreeNode* 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);
}
2、二叉树遍历
【nowcoder】二叉树遍历
先通过输入前序序列,还原二叉树,再通过中序打印。
#include<stdio.h>
#include<stdlib.h>
typedef char BTDataType;
typedef struct BinaryTree
{
struct BinaryTree* left;
struct BinaryTree* right;
BTDataType val;
}BTNode;
BTNode* CreatTree(char* a,int* pi)
{
if(a[*pi]=='#')
{
(*pi)++;//注意下标需要加一
return NULL;
}
//利用链表,还原树。
BTNode* newnode=(BTNode*)malloc(sizeof(BTNode));
if(newnode==NULL)
{
exit(-1);
}
newnode->val=(a[*pi]);
(*pi)++;
newnode->left=CreatTree(a,pi);
newnode->right=CreatTree(a,pi);
return newnode;
}
//中序打印
void InOrder(BTNode* root)
{
if(root==NULL)
{
return;
}
InOrder(root->left);
printf("%c ",root->val);
InOrder(root->right);
}
int main()
{
//先输入二叉树的前序序列
char str[100];
scanf("%s",str);
int i=0;//定义字符串下标,方便访问
BTNode* root=CreatTree(str,&i);//因为字符串的下标值需要改变,所以传地址
InOrder(root);//中序打印
return 0;
}