目录
补充上一篇笔记
一、二叉树遍历(牛客网)
编一个程序,读入用户输入的一串先序遍历字符串,根据此字符串建立一个二叉树(以指针方式存储)。 例如如下的先序遍历字符串: ABC##DE#G##F### 其中“#”表示的是空格,空格字符代表空树。建立起此二叉树以后,再对二叉树进行中序遍历,输出遍历结果。
输入描述:
输入包括1行字符串,长度不超过100。
输出描述:
可能有多组测试数据,对于每组数据, 输出将输入字符串建立二叉树后中序遍历的序列,每个字符后面都有一个空格。 每个输出结果占一行。
按照题中给的例子可以画出整个树的图。字符串是前序遍历的结果,题目让我们最终打印中序结果。思路能想到,把原本的树建立出来然后中序遍历即可,所以重点在于如何建立。
#代表NULL,也就是没有节点。所以说遇到#就return NULL。如果不是#,那就需要建立一个指针,开辟一块空间,然后给值。接下去,如何递归?我们肯定还要判断是不是空,每次也要建立一个指针,递归过程不能丢掉任何一个指针,所以最一开始就定义左右指针,来接受递归后形成的树。当然还有一个用来走下标的i。
TreeS* rebuildTree(char* str, int* i)
{
if (str[*i] == '#')
{
(*i)++;
return NULL;
}
TreeS* root = (TreeS*)malloc(sizeof(TreeS));
root->val = str[(*i)++];
root->left = rebuildTree(str, i);
root->right = rebuildTree(str, i);
return root;
}
为何要用指针i?如果是一个只有两层的二叉树,1 2 3节点,1访问完后,来到2,2没有左右子树,访问完2后,i应当指向下一个数字3,但是如果是传值调用,那么访问2的左子树时,遇到空,i++,返回NULL,这时候的i不会影响到访问2节点的函数里的i,下一行的访问右子树时传入的i还是原先的i,还是在访问2的左子树的空,等到2这里的程序结束后,本应当访问3,但此时 str[i] 并不是3,而是空,就会导致程序出错,所以应当传址调用,这点画递归展开的图就能够理解。
#include <stdio.h>
#include <stdlib.h>
typedef struct TreeString
{
char val;
struct TreeString* left;
struct TreeString* right;
}TreeS;
TreeS* rebuildTree(char* str, int* i)
{
if (str[*i] == '#')
{
(*i)++;
return NULL;
}
TreeS* root = (TreeS*)malloc(sizeof(TreeS));
root->val = str[(*i)++];
root->left = rebuildTree(str, i);
root->right = rebuildTree(str, i);
return root;
}
void Inorder(TreeS* root)
{
if (root == NULL)
{
return;
}
Inorder(root->left);
printf("%c ", root->val);
Inorder(root->right);
}
int main()
{
char str[100];
scanf("%s", str);
int i = 0;
TreeS* root = rebuildTree(str, &i);
Inorder(root);
return 0;
}
二、判断是否为完全二叉树
层序遍历
层序遍历是要用到队列的。取到根节点,插入,如果队列不为空,那么就拿出1,放入2和4,队列不为空,那就拿出2,插入3,此时队列有4和3,循环一遍后拿出4,插入5和6,一步步往下,层序就打印出来了。 也就是说,每一层不是一起,全部都放进去,随着上一层的数据拿走一个,就插入进它的左右子树,上一层的数据都拿出来后,下一层的数据也都放进去了。
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);
}
}
printf("\n");
QueueDestroy(&q);
}
解决层序问题再来看如何判断完全二叉树。完全二叉树的最后一层是从左面开始有连续的节点,上一层挨着的两个节点应当是都有左右子树,或者左面那个有2个子树,而右面那个只有左子树,这就是连续的,但如果右面那个只有右子树,就不连续,就不是完全二叉树。如果最后一层全是节点,那是满二叉树,不是完全二叉树。
完全二叉树会在最后一层有变化,也就是会遇到空,遇到空还要考虑连续问题,从空开始,之后的每个节点都是空,那么就是完全二叉树,反之则不是。利用层序遍历,每次拿走队顶元素后,判断是否为空,为空就去判断后面的节点。在之前的层序遍历中可以发现,每到新的一层,这一层的所有节点都已经进入了队列中,所以只需要遍历一遍看看是否为空即可。
bool TreeComplete(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);
if (front != NULL)//碰到第一个空出来后,再次取队顶,就是第一个空后面的那个元素,所以就可以开始判断了
{
QueueDestroy(&q);
return false;
}
}
QueueDestroy(&q);
return true;
}
三、二叉树的销毁
最优解是用后序遍历。因为要防止根部被先砍掉。
void TreeDestroy(BTNode* root)
{
if (root == NULL)
{
return;
}
TreeDestroy(root->left);
TreeDestroy(root->right);
free(root);
}
结束,下一篇写排序。