目录
[1. 二叉树存储结构](#1. 二叉树存储结构)
[1.1 顺序结构](#1.1 顺序结构)
[1.2 链式结构](#1.2 链式结构)
[2. 实现链式结构二叉树](#2. 实现链式结构二叉树)
[3. 二叉树的前、中、后序遍历](#3. 二叉树的前、中、后序遍历)
[3.1 遍历规则](#3.1 遍历规则)
[3.2 代码演示](#3.2 代码演示)
[3.3 图解遍历](#3.3 图解遍历)
[4. 求二叉树结点个数及高度等](#4. 求二叉树结点个数及高度等)
[4.1 求二叉树结点个数](#4.1 求二叉树结点个数)
[4.2 求二叉树叶子结点个数](#4.2 求二叉树叶子结点个数)
[4.3 求二叉树深度/高度](#4.3 求二叉树深度/高度)
[4.4 求二叉树第k层所含结点的个数](#4.4 求二叉树第k层所含结点的个数)
[4.5 找二叉树值为x的结点](#4.5 找二叉树值为x的结点)
[5. 层序遍历](#5. 层序遍历)
正文开始:
1. 二叉树存储结构
二叉树一般可以使用两种结构存储,一种顺序结构,一种链式结构。
1.1 顺序结构
顺序结构存储就是使用数组来存储,一般使用数组只适合表示完全二叉树,因为不是完全二叉树会有空间的浪费,完全二叉树更适合使用顺序结构存储。
现实中我们通常把堆(一种二叉树)使用顺序结构的数组来存储,需要注意的是这里的堆和操作系统虚拟进程地址空间中的堆是两回事,一个是数据结构,一个是操作系统中管理内存的一块区域分段。

1.2 链式结构
二叉树的链式存储结构是指,用链表来表示一棵二叉树,即用链来指示元素的逻辑关系。通常的方法是链表中每个结点由三个域组成,数据域和左右指针域,左右指针分别用来给出该结点左孩子和右孩子所在的链结点的存储地址。链式结构又分为二叉链和三叉链,当前只介绍二叉链。


2. 实现链式结构二叉树
用链表来表示一棵二叉树,即用链来指示元素的逻辑关系。通常的方法是链表中每个结点由三个域组成,数据域和左右指针域,左右指针分别用来给出该结点左孩子和右孩子所在的链结点的存储地址,其结构如下:
cpp
typedef int BTDataType;
// 二叉链
typedef struct BinaryTreeNode
{
struct BinTreeNode* left; //指向当前结点左孩子
struct BinTreeNode* right; //指向当前结点右孩子
BTDataType val; // 当前结点值域
}BTNode;
手动创建一棵链式二叉树用以辅助理解,示例如下:
cpp
BTNode* BuyBTNode(int val)
{
BTNode* newnode = (BTNode*)malloc(sizeof(BTNode));
if (newnode == NULL)
{
perror("malloc fail");
return;
}
newnode->val = val;
newnode->left = NULL;
newnode->right = NULL;
return newnode;
}
BTNode* CreateTree()
{
BTNode* n1 = BuyBTNode(1);
BTNode* n2 = BuyBTNode(2);
BTNode* n3 = BuyBTNode(3);
BTNode* n4 = BuyBTNode(4);
BTNode* n5 = BuyBTNode(5);
BTNode* n6 = BuyBTNode(6);
n1->left = n2;
n1->right = n4;
n2->left = n3;
n4->left = n5;
n4->right = n6;
return n1;
}
构建相应的二叉树逻辑图如下:

- 回顾二叉树的概念,二叉树分为空树和非空二叉树,非空二叉树由根结点、根结点的左子树、根结点的右子树组成的
- 根结点的左子树和右子树分别又是由子树结点、子树结点的左子树、子树结点的右子树组成的,因此二叉树定义是递归式的,后序链式二叉树的操作中基本都是按照该概念实现的。
3. 二叉树的前、中、后序遍历
二叉树的操作离不开树的遍历。

3.1 遍历规则
按照规则,二叉树的遍历有:前序/中序/后序的递归结构遍历:
- 前序遍历 (Preorder Traversal 亦称先序遍历):访问根结点的操作发生在遍历其左右子树之前
- 访问顺序为:根结点、左子树、右子树
- 中序遍历 (Inorder Traversal):访问根结点的操作发生在遍历其左右子树之中(间)
- 访问顺序为:左子树、根结点、右子树
3.后序遍历 (Postorder Traversal):访问根结点的操作发生在遍历其左右子树之后
- 访问顺序为:左子树、右子树、根结点
3.2 代码演示
cpp
//前序遍历
void PreOrder(BTNode* root)
{
if (root == NULL)
{
printf("N ");
return;
}
printf("%d ", root->val);
PreOrder(root->left);
PreOrder(root->right);
}
//中序遍历
void InOrder(BTNode* root)
{
if (root == NULL)
{
printf("N ");
return;
}
InOrder(root->left);
printf("%d ", root->val);
InOrder(root->right);
}
//后序遍历
void PostOrder(BTNode* root)
{
if (root == NULL)
{
printf("N ");
return;
}
InOrder(root->left);
InOrder(root->right);
printf("%d ", root->val);
}
3.3 图解遍历
**以前序遍历为例:**访问顺序为:根结点、左子树、右子树

根据前序遍历规则,亦是根据所编写的代码运行,则其逻辑的运行过程为:程序开始,1. 根据判断,根结点1的val值不为空,则打印val,根据访问顺序要求,此时根结点1访问结束,开始进入根结点1的左子树访问(即递归进入结点2)。2. 在根结点1的左子树中,结点2为这个子树中的根结点(可理解此为一个套娃式的递归过程),经判断其val值不为空,打印val值,则按规则再递归进入以结点2为根结点的二叉树的左子树中,即结点3。3. 在以结点3为根结点的二叉树中,经判断后,打印结点3的val值,后递归进入结点3的左子树中,发现其val值为NULL,则此时此部分的左子树的递归结束,则需回退到(即代码中的return指令)以结点3为根结点的二叉树结构中,根据遍历顺序规则,此时结点3已经完成了先根结点再左子树的任务,后开始递归进入其右子树中,判断其为空后,此时此处的递归结束,开始回退。在这一小阶段中因其以结点3为根结点的子树中,先根结点再左子树再右子树的前序遍历顺序要求结束。此时再一次回退。 4. 即此时回退到了以结点2为根节点的子树中(即结点1的左子树),此时这个子树已经完成了其先根结点再左子树的过程,则开始递归进入其右子树,发现其为空则此处递归结束,开始回退,同理,回退到结点1,开始进入其右子树的遍历。余下过程依次类推不再赘述。
其物理的运行过程设计栈帧的知识点,不做过多的解释,大概此过程就是一段代码不断执行多次,因为此过程会不断建立多个栈帧,相同代码在不同的栈帧中其函数的形参从实参处获得不同的值,所以达到了不同的运行结果。
最终其遍历结果为: 1 2 3 N N N 4 5 N N 6 N N

亦以此二叉树结构进行前、中、后序的遍历结果如下:

4. 求二叉树结点个数及高度等
4.1 求二叉树结点个数
错误示范:
cpp
//错误示范
#include <stdio.h>
int TreeSize(BTNode* root)
{
static int size = 0;
if (root == NULL)
return 0;
else
++size;
TreeSize(root->left);
TreeSize(root->right);
return size;
}
int main()
{
printf("TreeSize:%d\n", TreeSize(root));
printf("TreeSize:%d\n", TreeSize(root));
printf("TreeSize:%d\n", TreeSize(root));
}

若进行多次连续调用,因为static,计算结果会出现累加的现象。
正确求法:
cpp
int TreeSize(BTNode* root)
{
return root == NULL ? 0 :
TreeSize(root->left) + TreeSize(root->right) + 1;
}

求取规则:1. 若结点为NULL,返回0,2. 若结点不为空,返回值为 左子树+右子树+1。
4.2 求二叉树叶子结点个数
错误示范:

正确解法:
cpp
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);
}
4.3 求二叉树深度/高度
解法一:此解法可行,但是效率很低。
cpp
int TreeHeight(BTNode* root)
{
if (root == NULL)
return 0;
return TreeHeight(root->left) > TreeHeight(root->right) ?
TreeHeight(root->left) + 1 : TreeHeight(root->right) + 1;
}
解法二:优化解法
cpp
int TreeHeight(BTNode* root)
{
if (root == NULL)
return 0;
int leftHeight = TreeHeight(root->left);
int rightHeight = TreeHeight(root->right);
return leftHeight > rightHeight ?
leftHeight + 1 : rightHeight + 1;
}
4.4 求二叉树第k层所含结点的个数

即如果需要求的是第k层的结点的个数,可以转换为求第k-1层的所有结点的子结点个数,得到的所求值即为第k层结点的个数。
cpp
int TreeLevelKSize(BTNode* root, int k)
{
if (root == NULL)
return 0;
if (k == 1)
return 1;
//子问题
return TreeLevelKSize(root->left, k - 1)
+ TreeLevelKSize(root->right, k - 1);
}
4.5 找二叉树值为x的结点
cpp
BTNode* TreeFind(BTNode* root, BTDataType x)
{
if (root == NULL)
return NULL;
if (root->data == x)
return root;
BTNode* ret1 = TreeFind(root->left, x);
if (ret1)
return ret1;
BTNode* ret2 = TreeFind(root->right, x);
if (ret2)
return ret2;
return NULL;
}
5. 层序遍历
除了先序遍历、中序遍历、后序遍历外,还可以对⼆叉树进⾏层序遍历。设⼆叉树的根结点所在层数为1,层序遍历就是从所在⼆叉树的根结点出发,⾸先访问第⼀层的树根结点,然后从左到右访问第2层上的结点,接着是第三层的结点,以此类推,⾃上⽽下,⾃左⾄右逐层访问树的结点的过程就是层序遍历
实现层序遍历需要额外借助数据结构:队列

cpp
//层序遍历
void LevelOrder(BTNode* root)
{
Queue q;
QueueInit(&q);
QueuePush(&q, root);
while (!QueueEmpty(&q))
{
BTNode* top = QueueFront(&q);
printf("%c ", top->data);
QueuePop(&q);
if (top->_left)
{
QueuePush(&q, top->_left);
}
if (top->_right)
{
QueuePush(&q, top->_right);
}
}
QueueDesTroy(&q);
}
5.1判断是否为完全二叉树

cpp
//判断⼆叉树是否是完全⼆叉树
bool BinaryTreeComplete(BTNode* root)
{
Queue q;
QueueInit(&q);
if (root)
QueuePush(&q, root);
while (!QueueEmpty(&q))
{
BTNode * top = QueueFront(&q);
QueuePop(&q);
//遇到第一个空,就开始判断,如果队列中还有非空,就不是完全二叉树
if (top == NULL)
{
break;
}
QueuePush(&q, top->_left);
QueuePush(&q, top->_right);
}
while (!QueueEmpty(&q))
{
BTNode * top = QueueFront(&q);
QueuePop(&q);
//如果有非空,就不是完全二叉树
if (top != NULL)
{
QueueDesTroy(&q);
return false;
}
}
QueueDesTroy(&q);
return true;
}