一、前言:何为二叉树
1.1 二叉树
二叉树为树的一种,而树为一种非线性的数据结构,其只有一个根节点,然后有多种分支节点。注意,分支节点之间无交集。类似于下图(省略号表示各种分支节点):
而二叉树表示为其每个节点(包括根节点)的分支节点不超过两个。即像下图所示:
以上,为二叉树的简单认识,下面来了解一下二叉树中的特殊形式。
1.2 满二叉树与完全二叉树
在二叉树中有两种特殊的数据形式,一是满二叉树,二是完全二叉树。
满二叉树为每个节点都有两个分支节点,就是满满的。而完全二叉树为最后一层可以不满(方向是从左到右,也就是最后一层的右边如果要有节点,那你左边则必须要满了,否则就构不成完全二叉树),前面从根节点的第一层开始一直到倒数第二层都为满的。
如下面三种图:
图一为满二叉树,注意满二叉树也是完全二叉树的一种,算是一种特殊的完全二叉树。
图二为完全二叉树。其最后一层未满,但其满的方向是从左到右,符合定义。
图三为普通二叉树,虽然图二和图三比较相像,但注意完全二叉树的定义为其从左到右全为满的,但图三恰恰相反(从右到左为满的),因此不为完全二叉树。
二、二叉树的相关知识
2.1 前言
对于二叉树的大体知识来说,建立一个基本的二叉树是较为简单的,采取最笨的方法就只需要申请节点空间,然后将申请的节点进行链接即可。其次,在学习二叉树时,其中一个十分重要的方法就是递归方法,当然递归可以实现,非递归也可以,无论是递归还是非递归,其都蕴含了一个十分重要的思想那就是分治思想。分治思想可以说是整个二叉树的核心。
我们主要进行实现二叉树的其他操作。
大概可以分为这几个部分:
1. 二叉树的前序、中序、后序三大基本遍历,和一种特殊层序遍历。
2. 二叉树的总节点个数
3. 二叉树的叶子节点个数(叶子节点就是孤单单的一个节点,他没有向下的分支节点,只有它自己一个)
4. 返回值为 x 的节点
5. 第 k 层节点个数
6. 判断二叉树是不是完全二叉树
接下来,我们依次来进行实现。
2.2 前、中、后序遍历
首先,进行实现前,我们肯定要了解这些前中后序的意思。前中后序其实是根节点被遍历的次序。前序:先遍历根节点,之后遍历左节点,再遍历右节点。
中序:先遍历左节点,再遍历根节点,再遍历右节点。
后序:先遍历左节点,在遍历右节点,在遍历根节点。
我们直接来看代码,代码十分简单。
前序:
// 二叉树前序遍历
void BinaryTreePrevOrder(BTNode* root)
{
if (root == NULL)
return;
printf("%c ", root->_data);
BinaryTreePrevOrder(root->_left);
BinaryTreePrevOrder(root->_right);
}
方法还是进行递归,下面我们进行画递归展开图分析一下:
假设此时二叉树的结构为下图:
下面进行前序遍历的递归分析:
可能字体有点稍小,大家可以保存图片后观看。
以上,就为二叉树前序遍历的基本介绍和认识。
中序和后序遍历其思想和前序遍历相同,此时输出次序不同,大体结构并没有发生变化。
中序:
// 二叉树中序遍历
void BinaryTreeInOrder(BTNode* root)
{
if (root == NULL)
return;
BinaryTreeInOrder(root->_left);
printf("%c ", root->_data);
BinaryTreeInOrder(root->_right);
}
后序:
// 二叉树后序遍历
void BinaryTreePostOrder(BTNode* root)
{
if (root == NULL)
return;
BinaryTreePostOrder(root->_left);
BinaryTreePostOrder(root->_right);
printf("%c ", root->_data);
}
以上,就为二叉树结构的基本遍历操作介绍,本结尾的最后还会介绍层序遍历二叉树的结构。
2.3 二叉树总节点个数
在对上一节遍历的实现中,我们就可认识到,在对二叉树进行研究时,递归方法是十分必要的,因此在面对二叉树的题型时,我们也要考虑到递归方法。再看这道题,判断总节点个数,乍一想,思路其实有很多,我们可以直接利用上一节的所学的遍历,直接从头到尾遍历一遍二叉树,直接节点不为空,总节点数就加1。这也是最基本的思路。
那采用递归方法该如何操作呢,下面进行递归方法实现。
还是一样,首先来看代码:
// 二叉树节点个数
int BinaryTreeSize(BTNode* root)
{
if (root == NULL)
return 0;
return BinaryTreeSize(root->_left) + BinaryTreeSize(root->_right) + 1;
}
代码十分简洁,但其递归方法还是不太简单的,还是画递归展开图进行分析:
其递归方法核心为:总节点数 = 总左子树节点数 + 总右子树节点数。
这个图像只能这么大了,可以保存后再看,也可以私信我,我发给你。
2.4 二叉树的叶子节点个数
这道题和前文判断总节点较为相像,只是递归结束条件发生了改变。
其递归方法核心也为:总叶子节点个数 = 总左子树叶子节点个数 + 总右子树叶子节点个数。
老规矩,先看代码部分:
// 二叉树叶子节点个数
int BinaryTreeLeafSize(BTNode* root)
{
if (root == NULL)
return 0;
if (root->_left == NULL && root->_right == NULL)
return 1;
return BinaryTreeLeafSize(root->_left) + BinaryTreeLeafSize(root->_right);
}
递归展开图分析:
其方法:总叶子节点个数 = 总左子树叶子节点个数 + 总右子树叶子节点个数
把握住每个节点都是由它的左子树叶子节点和它的右子树叶子节点总和得到。往下一直递归即可。
2.5 返回值为 x 的节点
思路和前几问思路差不多,这里就不再画递归展开图进行分析。
思路为,先判断自己是不是值为 x 的节点,再查找左子树,如果左子树中有值为 x 的节点了,就直接返回,此时不需要查找右子树。如果左子树中没有查找到值为 x 的节点,那就继续查找右子树节点,如果在右子树中未查找到值为 x 的节点,就返回NULL。
直接看代码环节。
// 二叉树查找值为x的节点
BTNode* BinaryTreeFind(BTNode* root, BTDataType x)
{
if (root == NULL)
return NULL;
if (root->_data == x)
return root;
BTNode* node = BinaryTreeFind(root->_left, x);
if (node != NULL && node->_data == x)
return node;
node = BinaryTreeFind(root->_right, x);
if (node != NULL && node->_data == x)
return node;
return NULL;
}
其需要注意的点为,在每次查找左子树后,需要加一层判断,找到了就不需要再去查找右子树了。
找不到,再去查找右子树。
()
2.6 第 k 层节点个数
面对这一题,可能大家乍一想会没有用递归实现的思路。这里,我用画图进行分析一下。
通过分析,就可以直接实现问题的求解:
代码:
// 二叉树第k层节点个数
int BinaryTreeLevelKSize(BTNode* root, int k)
{
if (root == NULL)
return 0;
if (k == 1)
return 1;
return BinaryTreeLevelKSize(root->_left, k - 1) + BinaryTreeLevelKSize(root->_right, k - 1);
}
root 等于 NULL,是用来限制当到达了第 k 层后,节点为空的情况,只要到达了第 k 层节点不为空,那就返回1。
递归展开图分析:
因此,在做类似题目时,一定要想清楚关系性,之后就较为容易的实现了。
2.7 层序遍历
层序遍历:通俗来讲,就是一层一层遍历,像下图而言,层序遍历顺序为:1 2 3 4 5,先遍历第一层,再遍历第二层,最后遍历第三层。
其实现思路为:使用队列结构,先将根节点进入队列,当要将节点输出时,将其要输出节点的左和右节点不为空的进入队列,再将队列元素删除。重复这一过程,一直输出到队列为空。
代码:
// 层序遍历
void BinaryTreeLevelOrder(BTNode* root)
{
Queue q;
QueueInit(&q);
if (root != NULL)
QueuePush(&q, root);//注意,直接将整个节点放进去作为一个队列元素,而不是值
while (!QueueEmpty(&q))
{
BTNode* front = QueueFront(&q);//取出队头元素
printf("%c ", front->_data);
//每当释放一个队头元素时,就将它的左右节点不为空的入队列里
if (front->_left != NULL)
QueuePush(&q, front->_left);
if (front->_right != NULL)
QueuePush(&q, front->_right);
QueuePop(&q);
}
QueueDestroy(&q);
}
需要注意的是,进入队列的元素,不是节点的值,而是整个节点。对于队列不清楚的哥们,可以去看我队列的文章,很详细的进行了讲述。
2.8 判断二叉树是否为完全二叉树
要解决这一问题,需要用到上一问题的层序遍历,首先看完全二叉树和非完全二叉树的对比图:
当我们利用层序进行遍历时,
完全二叉树,其遍历顺序为:1--->2--->3--->4--->5--->NULL--->NULL
非完全二叉树,其遍历顺序为:1--->2--->3--->NULL--->NULL--->4--->5
这时,就可以发现区别了,当利用层序遍历时,当遍历到NULL时,其完全二叉树,后面全是NULL,而非完全二叉树,其后面还有非空节点。因此,可以根据这一区别进行判别两者。
代码:
// 判断二叉树是否是完全二叉树
int BinaryTreeComplete(BTNode* root)
{
Queue q;
QueueInit(&q);
int id = 0;
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);
}
//结束循环后,再去判断队列中NULL节点后面的节点
while (!QueueEmpty(&q))//如果是完全二叉树,则遇见第一个NULL后后面所有元素都为NULL,如果不是,则后面会有非空元素。
{
BTNode* front = QueueFront(&q);
QueuePop(&q);
if (front != NULL)
return 0;
}
return 1;
}
需要注意的点为,在入队列时,可能树的全部节点并未全部进入队列,但其分析过程相同。
完全二叉树进入队列后,遇见了第一个空节点后,其队列后面的节点全为空。
非完全二叉树进入队列后,遇见了第一个空节点后,其队列后面的节点有非空节点。
至此,二叉树暂且告一段落,不清楚的哥们可以私信我,尽可能给你解决!