目录
一、二叉树与递归
关于二叉树的考题有很多,难度有高有低,但大多数都是考递归,学会了二叉树的递归就可以拿捏这棵树。
递归是二叉树最独特的特点,如图:
每个节点都可以当作一个根节点,并和他的孩子节点构成一棵树
而这个节点的孩子节点也可以当作一个根,和他的孩子节点构成一棵树
如此反复,孩子越来越少,直到最后只剩下一个叶子节点。
这就是递归分治的思想:大事归小,小事返回
常见的题目如下:
在二叉树中,不管何种遍历方式(层序除外),都是递归的思想
细分为以下三种遍历:前序遍历、中序遍历、后序遍历
二、前中后序遍历
遍历顺序:
前序:自己、左节点、右节点
中序:左节点、自己、右节点
后序:左节点、右节点、自己
前序遍历: (绿色是到达该节点,橙色是遍历该节点)
中序遍历:
后序遍历:
写递归一定不能缺少的
1、返回条件:从上往下找左右子树,找到叶子节点就不再往下找了。也就是当此节点的左右子树为NULL时,就返回
2、参数的改变:由于每棵树的左右孩子节点都可以构成一棵新树,所以我们就递归左右孩子节点(root->left 、root->right)直到左右孩子都满足返回条件时递归停止。
所以我们每次递归的时候,做递归左右子树操作(绝大多数情况下采用先左后右,但先右后左也行)
可以形象的比作把任务交给孩子去做,然后孩子又交给他的孩子去做,直到最后的孩子没有孩子了,就自己做了(递归开始返回)
而下述的遍历我们采用了打印的方式,来清晰直观的展示遍历的顺序
切记!绝大多数情况下遍历并非采取打印的方式
//前序遍历
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);//遍历自己
}
三、例题
1、二叉树的销毁
如何销毁?
很简单:遍历一遍,把每个节点都遍历到,每次遍历到的节点,我们都把他删掉,就实现了。
销毁采用后序遍历
如果采用先序遍历,
当你free掉当前节点时,会找不到他的孩子节点
如果采用中序遍历,
你会成功free掉他的左孩子,但是当你想找右孩子时,会发现节点已经被free掉了,找不到右孩子
所以采用后序遍历删除
代码如下:第一个是传二级指针,第二个是传一级指针
// 二叉树销毁
void BinaryTreeDestory(BTNode** root)//二级指针目的是每次删除完一个节点时,都使指向该节点的指针转为指向NULL,防止产生野指针
{
if (*root == NULL)//为空返回
return;
BinaryTreeDestory(&(*root)->_left);//找左节点
BinaryTreeDestory(&(*root)->_right);//找右节点
free(*root);
*root=NULL;//每一步释放完root所指向空间的内存时,都把root指向的内容置空,防止产生野指针
return;
}
//当然,也可以传一级指针,不过需要自己手动置空一下
void BinaryTreeDestory(BTNode* root)
{
if (root == NULL)//为空返回
return;
BinaryTreeDestory(root->_left);//找左节点
BinaryTreeDestory(root->_right);//找右节点
free(root);
return;
}
2、二叉树节点个数
只需要遍历全部节点,每遍历到一个节点,总个数就+1
无论采用何种遍历方式,均能实现
思路有了,然后就是如何写递归了
1:我们需要的是节点个数,返回值肯定是 int 型的,所以函数的返回类型是 int 型
2:采用哪种递归方式,
是单独递归,return返回?
还是在return后面递归?
其实很好抉择,如果我们选择左图,则无论递归多深,返回值都是1
所以对于计数类型,我们通常都会选择右图,其每次递归都会使得数值+n
3:然后我们大事归小,小事返回。我们不想做的就交给我们的孩子做:只要我们自己不为空,就给节点个数+1,再加上左右子树的节点个数就行了,至于左右子树右多少节点,就递归给他们自己解决(这就是分治的思想)
int BinaryTreeSize(BTNode* root)//这里的代码是后序遍历,把+1更改位置就可以更改前中序遍历
{
if (root == NULL)//如果为空就返回0
return 0;
return BinaryTreeSize(root->_left)+BinaryTreeSize(root->_right)+1;//只要我不为空,我就给个数+1,然后左右子树就递归给他们去算
}
3、二叉树第k层节点个数
只需要找到第k层,然后把第k层的节点个数相加,就实现了
思路有了,开始写递归
1:我们需要的是节点个数,返回值肯定是 int 型的,所以函数的返回类型是 int 型
2:我们要找第k层,所以传进来的参数除了root外,还要有一个k
3:每次递归时,逻辑上都是往深走了一层,当走到第k层时,就到达了我们想要的那一层。那么我们需要改变的参数就是k的值
4:当我们递归到我们想要的层数时,此时k恒等于一个常数。当k等于这个常数的时候,我们就返回+1,代表找到了一个符合规定的节点,并且停止往深递归
5:万一有些点的深处不到k,则我们到NULL时就要返回+0了,代表我们找不到第k层
6:然后我们大事归小,小事返回。我们不想做的就交给我们的孩子做:只要我们自己不到第k层,就把任务交给孩子去找,至于孩子有没有满足到达第k层,就递归给他们自己解决(这就是分治的思想)
int BinaryTreeLevelKSize(BTNode* root, int k)
{
if (root == NULL)//走到底了,还没到第k层,就返回0
return 0;
if (k == 1)//走到了第k层,就返回1
return 1;
return BinaryTreeLevelKSize(root->_left, k - 1)+BinaryTreeLevelKSize(root->_right, k - 1);//每次递归都是往深一层递归,所以剩余层数-1(即k-1),然后就把问题交给孩子去做就行了
}
四、更多题解
如果还需要更多例题和题解:可以进入仓库查看