递归包含队规体和递归出口两个重要条件
递归就是把一个问题分成若干个小问题,且这些问题是有限的,即我们可以确定出它的结束条件,我们把一个大问题分解成了n个小问题,把其中的n-1个问题看成一个整体,先解决第n个问题然后找到大问题和小问题之间的关系,再解决剩下的n-1个问题。
例如:求1—n的和,可分解成n个数相加,且他们操作步骤相同,把除了1外的剩下n-1个数看成一个整体,r然后让1和剩下n-1个数相加再让2和剩下的n-2个数相加,当最后只剩下一个数时,递归结束。
二叉树最多有两个孩子节点,且除根节点外所有孩子只有一个父节点
1.在调试时我们可以选择手动创建二叉树
2.另一种是我们可以根据我们输入的值进行创建二叉树
用#表示空节点
牛客题目
// 通过前序遍历的数组"ABD##E#H##CF##G##"构建二叉树
//这里为什么要传i且是它的地址呢?
BTNode* BinaryTreeCreate(BTDataType* a,int* pi)
{
//为什么这样写不行?
// if (str[(*pi)++] == '#') {//等于#,pi也要++
// return NULL;
// }
if (a[*pi] == '#') {//等于#,pi也要++
(*pi)++;
return NULL;
}
//if (str[*pi] != '#') {
//BT* root = (BT*)malloc(sizeof(BT));
BTNode* tmp = (BTNode*)malloc(sizeof(BTNode));
if (tmp == NULL)
{
perror("malloc fail");
return NULL;
}
BTNode* root = tmp;
root->data = a[(*pi)++];
root->left = BinaryTreeCreate(a, pi);
root->right = BinaryTreeCreate(a, pi);
return root;
//}
}
参数a传的是数组首地址,那么pi代表什么?为什么还要传地址呢?因为我们要把数组中的值逐个添加到数中,而*pi就是数组中元素的下标,当我们每次添加w完一个元素之后,对应的下标就要加1,而在递归调用的过程中每一次调用之后如果传的不是地址,那么他所对应的下标就不会被改变。
BTNode* root = tmp;
root->data = a[(*pi)++];
root->left = BinaryTreeCreate(a, pi);
root->right = BinaryTreeCreate(a, pi);
这段语句就是递归体,我们在对树进行赋值的时候,小问题就是对每个根节点和它的左右孩子节点赋值。也就是先对第一个小问题进行判断求解,然后再递归调用剩下的小问题,二叉树创建属于二路递归。
前序遍历的顺序,先遍历根节点再遍历左孩子最后遍历右孩子,所以前序遍历就被分成了n个小问题,且每个小问题都是相同的,就是先遍历根节点再遍历左孩子最后遍历右孩子。
//先序遍历
void BinaryTreePrevOrder(BTNode* root)
{
if (root == NULL)
{
printf("NULL ");
return;
}
printf("%c ", root->data);
BinaryTreePrevOrder(root->left);
BinaryTreePrevOrder(root->right);
}
//中序遍历
void BinaryTreeInOrder(BTNode* root)
{
if (root == NULL)
{
printf("NULL ");
return;
}
BinaryTreeInOrder(root->left);
printf("%d ", root->data);
BinaryTreeInOrder(root->right);
}
//后序遍历
void BinaryTreePostOrder(BTNode* root)
{
if (root == NULL)
{
printf("NULL ");
return;
}
BinaryTreePostOrder(root->left);
BinaryTreePostOrder(root->right);
printf("%d ", root->data);
}
思想:分治法把问题分解成多个小问题,大问题就是求解所有节点个数,每个小问题为左右子树节点个数和根节点之和。
//统计树的节点个数
int BinaryTreeSize(BTNode* root)
{
if (root == NULL)
{
return 0;
}
int LeftTreeSize = BinaryTreeSize(root->left);
int RightTreeSize = BinaryTreeSize(root->right);
return LeftTreeSize + RightTreeSize + 1;
}
第K层节点个数就是这一层左树的节点个数加上这一层右树的节点个数
进入递归就是先解决根节点问题然后小问题中剩下的子问题就是分别对左右子树求解。
先明确小问题的求解方法,然后再找递归出口。
//思路:求根节点的第k层的节点个数,就是相当于分别求它的左右子树的k - 1层个数之和
int BinaryTreeLevelKSize(BTNode* root, int k)
{
//k一定大于0
assert(k > 0);
//如果此时的跟为NULL,就不需要向下走了
if (root == NULL)
{
return 0;
}
//如果只有第一层了,就返回1
if (k == 1)
{
return 1;
}
/*int LeftLevel = TreeKLevel(root->left, k - 1);
int RightLevel = TreeKLevel(root->right, k - 1);
return LeftLevel + RightLevel;*/
return BinaryTreeLevelKSize(root->left, k - 1) + BinaryTreeLevelKSize(root->right, k - 1);
}
二叉树高度为左右子树高度+1(根节点)中大的那个
int BinaryTreeHigh(BTNode* root)
{
if (root == NULL)
{
return 0;
}
int LeftHigh = BinaryTreeHigh(root->left);
int RightHigh = BinaryTreeHigh(root->right);
return LeftHigh > RightHigh ? LeftHigh + 1 : RightHigh + 1;
}
叶子结点:左右孩子都为空的节点
//叶子节点棵树 ---左右孩子为空的节点
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);
}
层序遍历顺序从上向下,从左至右
在这里我们充分利用了队列的先进先出的性质,我们选择把节点的地址放到队列中,注意不是节点的值,因为我们还要让它的子孩子进队列,每出一个节点,就要让它的孩子进去,如果为空就不进,遍历结束的判断条件是队列为空。
// 层序遍历
//用队列,每出一个,就把它的孩子带进来
void BinaryTreeLevelOrder(BTNode* root)
{
if (root == NULL)
{
return;
}
Queue Q;
QueueInit(&Q);
//为什么要把队列的成员类型定义为二叉树的指针?重要
//如果是把值放到队列中那么它的孩子就找不到了,不好在放进去
QueuePush(&Q, root);
while (!QueueEmpty(&Q))
{
/*Print(root, &Q);*/
BTNode* front = QueueFront(&Q);
QueuePop(&Q);
printf("%c ", front->data);
if (front->left != NULL)
{
//QueuePush(&Q, root->left->data);
QueuePush(&Q, front->left);
}
if (front->right != NULL)
{
//QueuePush(&Q, root->right->data);
QueuePush(&Q, front->right);
}
}
printf("\n");
QueueDestroy(&Q);
}
完全二叉树就是从左至右节点是连续的。
所以我们可以充分利用完全二叉树的性质,因此在层序遍历中,当有一个节点为空时,那么它后面的也都为空。所以我们选择层序遍历,当有一个节点为空时,就对队列里剩下的节点进行判断如果全为空就是完全二叉树,如果不是就不是完全二叉树。注意此时我们的层序遍历,空节点也要入队列,用来找到第一个出队列的空节点。
// 判断二叉树是否是完全二叉树
//充分利用完全二叉树的定义 如果完全二叉树当它第一次出现NULL时后面的就全为NULL
bool BinaryTreeComplete(BTNode* root)
{
//还是使用队列把指向值的指针放进去,只不过现在NULL也要放进去
if (root == NULL)
{
return false;
}
Queue Q;
QueueInit(&Q);
QueuePush(&Q, root);
while (!QueueEmpty(&Q))
{
/*if (front->left)
{
QueuePush(&Q, front->left);
}
if (front->right)
{
QueuePush(&Q, front->right);
}*/
QDataType front = QueueFront(&Q);
QueuePop(&Q);
//如果是空就入空
if (front == NULL)
{
break;
}
if (front->left == NULL)
{
QueuePush(&Q, NULL);
}
else
{
QueuePush(&Q, front->left);
}
if (front->right == NULL)
{
QueuePush(&Q, NULL);
}
else
{
QueuePush(&Q, front->right);
}
}
while (!QueueEmpty(&Q))
{
//此时队头为空,后面全为空就为完全二叉树
QDataType front = QueueFront(&Q);
if (front != NULL)
{
return false;
}
QueuePop(&Q);
}
QueueDestroy(&Q);
return true;
}