今天我们来继续谈一谈二叉树~~~
目录
一、关于递归
在谈二叉树之前,我们先谈一谈递归。
递归就是函数自己调用自己。但特殊的是,函数每被调用一次,系统就会为它开启一个栈帧,直到函数返回时,系统才会把栈帧收回。因此,对于递归,还有另一层理解:递归的栈帧可以用来保存一些数据和这个栈帧的地址,直到栈帧销毁。
相关的例子会在下文讲解~~~
二、二叉树遍历
1、前序遍历(PreOrder)
前序遍历的访问顺序是:根 -> 左子树 -> 右子树
因此,代码应该是这样的:
void PreOrder(BTNode* root) /* 根 左子树 右子树 */
{
if (root == NULL)
return;
printf("%d ", root->data);
PreOrder(root->left);
PreOrder(root->right);
}
2、中序遍历(InOrder)
中序遍历的访问顺序是:左子树 -> 根 -> 右子树
代码:
void InOrder(BTNode* root) /* 左子树 根 右子树 */
{
if (root == NULL)
return;
InOrder(root->left);
printf("%d ", root->data);
InOrder(root->right);
}
3、后序遍历(PostOrder)
后序遍历的访问顺序是:左子树 -> 右子树 -> 根
代码:
void PostOrder(BTNode* root) /* 左子树 右子树 根 */
{
if (root == NULL)
return;
PostOrder(root->left);
PostOrder(root->right);
printf("%d ", root->data);
}
三、统计二叉树的节点数
1、思路
为了解决这个问题,我在这里先引用一个比喻:
在大学里,有校长、院长、班主任等等。而他们的关系就像这张图:
这时候校长要统计全校的人数。显然,校长是不可能自己亲自去宿舍一个一个数人的,而是叫两个院长去统计他们学院里的人数,最后再加上自己就行了。而同理,院长也不会自己亲自去数人数,也是叫辅导员去统计系里的人数,然后再加上自己....到最后,通过班长数的人数再逐步汇总到辅导员,再到院长,最后到校长。
因此,面对这个问题,我们可以先统计左右子树的节点数,最后再加上根节点自己。
2、代码
int BinaryTreeSize(BTNode* root)
{
if (root == NULL)
return 0;
return BinaryTreeSize(root->_left) + BinaryTreeSize(root->_right) + 1;
}
四、计算二叉树的深(高)度
1、思路
和统计节点数一样,这里我也可以打个比喻:校长要选出学校里身高最高的人。那怎么选呢,校长总不会亲自逐个逐个量学生的身高然后比较吧,因此校长一般会叫院长去统计他们院里身高最高的同学,然后再在这些同学里面选出最高的那个。
因此,在代码上,我们可以先统计左右子树的高度,然后再比较它们的高度,最后j加上根节点自己的高度返回就行了。
2、代码
int BinaryTreeHeight(BTNode *root)
{
if (root == NULL)
return 0;
if(BinaryTreeHeight(root->left) > BinaryTreeHeight(root->right))
return BinaryTreeHeight(root->left) + 1;
else
return BinaryTreeHeight(root->right) + 1;
}
这段代码大家肯定很快就写出来了,但这样就对了吗?如果你是个细心的人,就不难发现,在比较高度时,我们的代码在比较和返回时各递归了一次,这无疑会增加机器的开销。因此,我们可以用两个变量分别储存这两棵子树高度的值,然后再比较,再返回,这样就可以只递归一次了。
所以,正确的代码应该这样写:
int BinaryTreeHeight(BTNode *root)
{
if (root == NULL)
return 0;
int leftHeight = BinaryTreeHeight(root->left);
int rightHeight = BinaryTreeHeight(root->right);
if (leftHeight > rightHeight)
return leftHeight + 1;
else
return rightHeight + 1;
}
五、二叉树叶子节点个数
1、思路
还是用校长那个例子来打比方,这个问题就等同于校长要统计学生人数。而怎么操作呢,其实不难想出,校长可以叫院长去统计他们自己学院的学生人数,最后再汇总起来就好了。
以二叉树的角度来说,就是通过从根节点开始向左右子树递归,当该节点为叶子节点时,就返回1,如果为空节点,返回0。
2、代码
int BinaryTreeLeafSize(BTNode* root)
{
if (root == NULL)
return 0;
if (root != NULL && root->_left == NULL && root->_right == NULL)
return 1;
return BinaryTreeLeafSize(root->_left) + BinaryTreeLeafSize(root->_right);
}
六、二叉树第k层节点个数
1、思路
对于这个问题,还是用校长那个例子来讲,就是等同于校长要统计辅导员的人数。而怎么解决呢?同样,校长应该找院长统计他们学院里辅导员的人数,最后再汇总就行了。但要注意的是,因为数的是辅导员的人数,因此就不关班长的事了。
从二叉树的角度来看,可以从根节点分别向左右子树递归,然后把它们加在一起就行了。但值得注意的是:递归的逻辑是第k层的节点数等于左右孩子的k-1层。
2、代码
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);
}
七、二叉树查找值为x的节点
1、思路
以校长的角度来看,就是在学校里找人。那么怎么找呢?同样,他一定会叫院长去找,然后院长叫辅导员去找...找到了就返回这个人所在的位置,找不到就返回“不在”。
从二叉树的角度看,先比较根节点的值,如果一样就返回它的地址,不一样的话就叫左子树找,如果左子树还找不到就去右子树找,如果再找不到就返回空。
2、代码
BTNode* BinaryTreeFind(BTNode* root, BTDataType x)
{
if (root == NULL)
return NULL;
if (root->_data == x)
return root;
BTNode* leftRet = BinaryTreeFind(root->_left, x);
if (leftRet != NULL)
{
return leftRet;
}
BTNode* rightRet = BinaryTreeFind(root->_right, x);
if (rightRet != NULL)
{
return rightRet;
}
return NULL;
}
3、递归展开图
八、二叉树的创建
(1)思路
这里,我们的大体思路是先malloc一个根节点,然后用递归的方式分别malloc一个左子树和右子树,最后让根节点的 left 和 right 指针分别指向左右子树就行了。
但要注意的是:我们需要 i 变量来遍历数组。但如果递归的话,对于每个栈帧来说 i 的值是无法被记住的,一旦这个栈帧被销毁,i 的值就会变成上一层栈帧中 i 的值。因此,我们在传 i 时要传 i 的地址,不要传值。
(2)代码
BTNode* BuyTreeNode(BTDataType a)
{
BTNode* newNode = (BTNode*)malloc(sizeof(BTNode));
assert(newNode != NULL);
newNode->_data = a;
newNode->_left = newNode->_right = NULL;
return newNode;
}
// 通过前序遍历的数组"ABD##E#H##CF##G##"构建二叉树
BTNode* BinaryTreeCreate(BTDataType* a, int n, int* pi)
{
if (*pi == n)
return NULL;
if (a[*pi] == '#')
{
(*pi)++;
return NULL;
}
BTNode* root = BuyTreeNode(a[(*pi)++]);
BTNode* leftTree = BinaryTreeCreate(a, n, pi);
BTNode* rightTree = BinaryTreeCreate(a, n, pi);
root->_left = leftTree;
root->_right = rightTree;
return root;
}
九、二叉树的销毁
(1)思路
从整体上看,我们可以先把左右子树销毁,最后再把根节点销毁。当然,如果递归是遇到空树,直接返回就行了。
(2)代码
void BinaryTreeDestory(BTNode** root)
{
if (*root == NULL)
return;
BinaryTreeDestory(&((*root)->_left));
BinaryTreeDestory(&((*root)->_right));
free(*root);
*root = NULL;
}
好了,这就是二叉树的基本操作了。