定义节点的结构
typedef int BTDataType;//二叉树中存储的数据类型,这里以int为例
typedef struct BTNode
{
BTDataType data;//当前节点要存储的数据
struct BTNode* left;//存储当前节点的左孩子的地址
struct BTNode* right;//存储当前节点的右孩子的地址
}BTNode;
二叉树的前序遍历
前序遍历又称为先根遍历,前序遍历的访问顺序是:根 左子树 右子树。
假设我们要对一棵二叉树进行前序遍历,则对于这棵二叉树及其所有的子树,我们都要按根、左子树、右子树的顺序依次访问。
void PreOrder(BTNode* root)
{
if (root == NULL)
{
printf("NULL ");
return;
}
printf("%d ",root->data);
PreOrder(root->left);
PreOrder(root->right);
}
例如:我们要对下面这棵二叉树进行前序遍历。
先访问根节点1,再访问1的左子树;
1的左子树以结点2为根,先访问2,再访问2的左子树;
2的左子树以3为根,先访问3;
3的左子树是空,返回;
3的左子树访问完成之后,访问3的右子树,3的右子树是空,返回;
此时3的左右子树都访问完成,返回;
此时2的左子树访问完成了,访问2的右子树,2的右子树是空,返回;
此时2的左右子树都访问完成了,返回;
此时1的左子树访问完成了,访问1的右子树;
1的右子树以节点4为根,先访问4,再访问4这棵树的左子树;
4的左子树以5为根,先访问5,再访问5这棵树的左子树;
5的左子树是空,返回;
此时5的左子树访问完了,访问5的右子树;
5的右子树是空,返回;
此时5的左右子树都访问完了,返回;
此时4的左子树访问完了,访问4这棵树的右子树;
4这棵树的右子树以6为根,先访问6,再访问6这棵树的左子树;
6的左子树是空,返回;
此时6的左子树访问完了,访问6的右子树;
6的右子树是空,返回;
此时6的左右子树都访问完了,返回;
此时4的左右子树都访问完了,返回;
此时1的左右子树都访问完了,返回。
前序遍历的结果是:1 2 3 NULL NULL NULL 4 5 NULL NULL 6 NULL NULL
前序遍历递归图解:
二叉树的中序遍历
中序遍历又称为中根遍历,中序遍历的访问顺序是:左子树 根 右子树。
假设我们要对一棵二叉树进行中序遍历,则对于这棵二叉树及其所有的子树,我们都要按左子树、根、右子树的顺序依次访问。
void InOrder(BTNode* root)
{
if (root == NULL)
{
printf("NULL ");
return;
}
InOrder(root->left);
printf("%d ",root->data);
InOrder(root->right);
}
例如:我们要对下面这棵二叉树进行中序遍历。
遇到根节点1,先不访问,先访问1的左子树;
1的左子树以2为根,先不访问2,先访问2的左子树;
2的左子树以3为根,先不访问3,先访问3的左子树;
3的左子树是空,返回;
此时3的左子树访问完成,接着访问3,然后访问3的右子树;
3的右子树是空,返回;
此时3这棵树的左子树、根、右子树都依次访问完成了,返回;
此时2的左子树访问完成了,接着访问2,然后访问2的右子树;
2的右子树是空,返回;
此时2这棵树的左子树、根、右子树都依次访问完成了,返回;
此时1这棵树的左子树访问完成了,接着访问1,然后访问1的右子树;
1的右子树以4为根,先不访问4,先访问4的左子树;
4的左子树以5为根,先不访问5,先访问5的左子树;
5的左子树是空,返回;
此时5的左子树访问完成了,接着访问5,然后访问5的右子树;
5的右子树是空,返回;
此时5这棵树的左子树、根、右子树都依次访问完成了,返回;
此时4这棵树的左子树访问完成了,接着访问4,然后访问4的右子树;
4的右子树以6为根,先不访问6,先访问6的左子树;
6的左子树是空,返回;
此时6的左子树访问完成,接着访问6,然后访问6的右子树;
6的右子树是空,返回;
此时6这棵树的左子树、根、右子树都依次访问完成了,返回;
此时4这棵树的左子树、根、右子树都依次访问完成了,返回;
此时1这棵树的左子树、根、右子树都依次访问完成了,返回。
中序遍历的结果是:NULL 3 NULL 2 NULL 1 NULL 5 NULL 4 NULL 6 NULL
中序遍历递归图解:
二叉树的后序遍历
后序遍历又称为后根遍历,后序遍历的访问顺序是:左子树 右子树 根。
假设我们要对一棵二叉树进行后序遍历,则对于这棵二叉树及其所有的子树,我们都要按左子树、右子树、根的顺序依次访问。
void PostOrder(BTNode* root)
{
if (root == NULL)
{
printf("NULL ");
return;
}
PostOrder(root->left);
PostOrder(root->right);
printf("%d ",root->data);
}
例如:我们要对下面这棵二叉树进行后序遍历。
遇到根节点1,先不访问1,先访问1的左子树;
1的左子树以2为根,先不访问2,先访问2的左子树;
2的左子树以3为根,先不访问3,先访问3的左子树;
3的左子树是空,返回;
此时3的左子树访问完成了,接着访问3的右子树;
3的右子树是空,返回;
此时3的右子树访问完成了,接着访问3;
访问3之后,3这棵树的左子树、右子树、根都依次访问完成了,返回;
此时2的左子树访问完成了,接着访问2的右子树;
2的右子树是空,返回;
此时2的右子树访问完成了,接着访问2;
访问2之后,2这棵树的左子树、右子树、根都依次访问完成了,返回;
此时1这棵树的左子树访问完成了,接着访问1的右子树;
1的右子树以4为根,先不访问4,先访问4的左子树;
4的左子树以5为根,先不访问5,先访问5的左子树;
5的左子树是空,返回;
此时5的左子树访问完成了,接着访问5的右子树;
5的右子树是空,返回;
此时5的右子树访问完成了,接着访问5;
访问5之后,5这棵树的左子树、右子树、根都依次访问完成了,返回;
此时4这棵树的左子树访问完成了,接着访问4的右子树;
4的右子树以6为根,先不访问6,先访问6的左子树;
6的左子树是空,返回;
此时6的左子树访问完成了,接着访问6的右子树;
6的右子树是空,返回;
此时6的右子树访问完成了,接着访问6;
访问6之后,6这棵树的左子树、右子树、根都依次访问完成了,返回;
此时4这棵树的右子树访问完成了,接着访问4;
访问4之后,4这棵树的左子树、右子树、根都依次访问完成了,返回;
此时1这棵树的右子树访问完成了,接着访问1;
访问1之后,1这棵树的左子树、右子树、根都访问完成了,返回。
后序遍历的结果是:NULL NULL 3 NULL 2 NULL NULL 5 NULL NULL 6 4 1
后序遍历递归图解:
二叉树的层序遍历
设二叉树的根节点所在层数为1,层序遍历就是从所在二叉树的根节点出发,首先访问第一层的树根节点,然后从左到右访问第2层上的节点,接着是第三层的节点,以此类推,自上而下,自左至右逐层访问树的结点的过程就是层序遍历。
如下图二叉树层序遍历的结果是:1 2 4 3 5 6
层序遍历需要用到队列。层序遍历方法如下:
1.根节点先进队列;
2.保存队头数据,然后出队头数据;
3.依次进队头结点的左右孩子。
根节点进队列后,循环进行步骤二和步骤三,直到队列为空。若队列为空,则循环结束,层序遍历也就完成了。
void LevelOrder(BTNode* root)
{
Queue q;//创建队列
QueueInit(&q);//初始化队列
if (root)//根节点进队列
QueuePush(&q, root);
while (!QueueEmpty(&q))//若队列不为空,就进入循环
{
BTNode* front = QueueFront(&q);//保存队头数据
printf("%d ",front->data);
QueuePop(&q);//删除队头数据
if(front->left)//若队头左孩子不为空,则入队列
QueuePush(&q, front->left);
if (front->right)//若队头右孩子不为空,则入队列
QueuePush(&q, front->right);
}
QueueDestroy(&q);//销毁队列
}
二叉树节点个数
求二叉树的结点个数,要求出根节点的左子树的节点个数、根节点的右子树的结点个数,再把根节点的左右子树的结点个数相加再加上根节点自己,就可以求出二叉树的节点个数。
int BTSize(BTNode* root)
{
if (root == NULL)
{
return 0;
}
return BTSize(root->left) + BTSize(root->right) + 1;
}
二叉树的高度
求二叉树的高度,只要求出根节点的左右子树的高度,然后将根节点的左右子树的高度进行比较,返回高度高的那个+1就可以了。(还要+1是因为根节点自己还有一层)。
int BTHeight(BTNode* root)
{
if (root == NULL)
{
return 0;
}
int left = BTHeight(root->left);//求根节点的左子树的高度
int right = BTHeight(root->right);//求根节点的右子树的高度
//比较左右子树的高度,返回高的那个高度+1
if (left > right)
{
return left + 1;
}
else
{
return right + 1;
}
}
二叉树第k层节点个数
二叉树第k层结点个数 = 左子树的第k-1层节点个数+右子树的第k-1层结点个数
随着函数递归的调用,k会递减到1,当k等于1的时候,就到了二叉树的第k层。当k等于1且结点不为空时,返回1。
int BTKLevel(BTNode* root, int k)
{
assert(k > 0);//k不能小于等于0
if (root == NULL)
{
return 0;
}
if (k == 1)
{
return 1;
}
//求左子树的第k-1层节点个数
int left = BTKLevel(root->left, k - 1);
//求右子树的第k-1层结点个数
int right = BTKLevel(root->right, k - 1);
return left + right;
}
二叉树查找值为x的节点
二叉树查找值为x的节点,若找到,则返回该节点的地址,若有多个值为x的节点,则返回第一个结点的地址。
二叉树查找值为x的节点的方法如下:
1.先查找根节点,若根节点符合,就返回根节点;
2.若根节点不符合,就去根节点的左子树找,找到则返回;
3.若根节点和根节点的左子树都找不到,就去根节点的右子树中找,找到则返回。若找不到,则说明这棵树(或子树)没有值为x的结点,返回NULL。
BTNode* BTFind(BTNode* root, BTDataType x)
{
if (root == NULL)
return NULL;
if (root->data == x)
return root;
BTNode* lret = BTFind(root->left, x);
if (lret)
return lret;
BTNode* rret = BTFind(root->right, x);
if (rret)
return rret;
return NULL;
}
二叉树叶子节点个数
根据前面我们所学的概念,可以知道,叶子结点是既没有左孩子,也没有右孩子的结点。求二叉树的叶子结点个数,需要递归遍历整棵树。若结点为NULL,则返回0。若结点满足是叶子结点,则返回1。若结点不为空也不是叶子结点,则继续去左右子树中找叶子结点,并将左右子树中的叶子结点个数相加。
int BTLeafSize(BTNode* root)
{
if (root == NULL)//结点为空,返回0
{
return 0;
}
//满足条件,说明当前节点是叶子结点
if (root->left == NULL && root->right == NULL)
{
return 1;
}
//找不到叶子结点就继续去左子树和右子树中找
return BTLeafSize(root->left) + BTLeafSize(root->right);
}
判断二叉树是否是完全二叉树
判断二叉树是否是完全二叉树,需要用到完全二叉树的一个性质:完全二叉树的结点是连续的。
若一棵二叉树是完全二叉树,则这棵二叉树满足以下两点:
1.该二叉树除最后一层外,其他层必须是满的;
2.该二叉树的最后一层从左到右必须是连续的。
根据以上两点可知,完全二叉树的节点是连续的。若一棵二叉树的空节点后面还有结点,则说明不是完全二叉树;若一棵二叉树的空节点后面全是空节点,则说明是完全二叉树。
判断二叉树是否是完全二叉树,也需要用到队列,方法如下:
1.按层序遍历走;
2.若队头数据是空节点,则跳出循环;
3.遍历队列中剩下的数据,若有非空结点,则返回false;若都是空节点,则返回true。
bool BTComplete(BTNode* root)
{
Queue q;//创建队列
QueueInit(&q);//初始化队列
if (root)//根节点入队列
QueuePush(&q, root);
while (!QueueEmpty(&q))
{
BTNode* front = QueueFront(&q);//取队头数据
QueuePop(&q);//出队头数据
if (front == NULL)//队头数据是空节点,则跳出循环
{
break;
}
else//若队头数据是非空结点,则依次入队头的左右孩子
{
QueuePush(&q,front->left);
QueuePush(&q,front->right);
}
}
//判断是否是完全二叉树
while (!QueueEmpty(&q))
{
BTNode* front = QueueFront(&q);//取队头数据
QueuePop(&q);//出队头数据
//若有一个不是空节点,则该二叉树不是完全二叉树,返回false
if (front != NULL)
{
QueueDestroy(&q);
return false;
}
}
QueueDestroy(&q);
//若程序可以走到这一步,说明该二叉树是完全二叉树,返回true
return true;
}
二叉树的销毁
销毁二叉树需要用后序遍历,因为如果用前序遍历或者中序遍历销毁二叉树,那么根结点的销毁就会在销毁其孩子结点之前,根节点销毁后,就找不到它的孩子节点,就无法销毁它的孩子结点,因此用后序遍历来销毁二叉树是最合适的。
void BTDestory(BTNode* root)
{
if (root == NULL)
return;
BTDestory(root->left);
BTDestory(root->right);
free(root);
}