数据结构 --- 二叉树的相关面试题

这里只介绍二叉树的以下几个面试题:
1.用非递归版本实现先序、中序和后序遍历
2.求二叉树的镜像
3.判断一棵树是否是完全二叉树
4.给一个前序遍历和中序遍历的结果重建二叉树
上一篇博客中我们详解了用非递归实现二叉树的先序、中序和后序遍历,在此不再赘述,有需要的参考非递归实现数的先序、中序和后序遍历
下面我们直接来看后面这三个问题。

1.求二叉树的镜像
镜像类似于我们生活中照镜子一样,除过左右发生了交换以外,其他都没变。而对于二叉树的镜像,我们只用将其左右子树进行交换即可。
这里写图片描述

代码如下:
用递归和非递归两种方法进行实现。

void Swap(TreeNode** a,TreeNode** b)
{
    TreeNode* tmp = *a;
    *a = *b;
    *b = tmp;
    return;
}
//递归版本
//二叉树的镜像将以前访问某个节点的动作变成了交换其左右子树的位置
void TreeMirror(TreeNode* root)
{
    if(root == NULL)
    {
        return;
    }
    Swap(&root->lchild,&root->rchild);
    TreeMirror(root->lchild);
    TreeMirror(root->rchild);
}

//借助层序实现非递归版本的镜像
void TreeMirrorByLoop(TreeNode* root)
{
    if(root == NULL)
    {
        return;//空树
    }
    SeqQueue queue;
    SeqQueueInit(&queue);
    SeqQueuePush(&queue,root);
    //循环的取队首元素
    TreeNode* front = NULL;
    while(SeqQueueFront(&queue,&front))
    {
        //printf("ret = %d",ret);
        //交换其左右子树的位置
        Swap(&front->lchild,&front->rchild);
        SeqQueuePop(&queue);
        if(front->lchild != NULL)
        {
            SeqQueuePush(&queue,front->lchild);
        }
        if(front->rchild != NULL)
        {
            SeqQueuePush(&queue,front->rchild);
        }

    }
}

2.判断一棵树是否是完全二叉树
我们先来看以下几个例子:
这里写图片描述

上图中, a , b , c 都是完全二叉树,d和e 不是。为什么呢?

按照自己的理解将完全二叉树的特点归纳为:
(1)叶子结点只可能在层次最大的两层上出现。
(2)若二叉树最下面一层有节点出现,那么这个节点一定是是从左到右依次排列,若只有一个孩子,那么这个孩子一定是左孩子而不是右孩子(特殊情况,空树和只有根节点的树都是完全二叉树)。
(3)要么最后一层全为叶子节点,否则以它为根节点的孩子一定是从左依次排列的)。

解决思路:我们使用层序遍历,将遍历过程分为两个阶段:
阶段一:任何一个节点都同时具有左右子树,一旦发现某个节点不是同时具备
a)当前节点只有右子树,一定不是完全二叉树;
b)当前节点只有左子树,进入第二阶段;
c)当前节点没有子树,进入第二阶段;
阶段二:任何一个节点都必须没有子树。
当遍历结束后,所有条件都满足,那么这个树就是完全二叉树。否则,就不是。

具体实现代码如下:

int IsCompleteTree(TreeNode* root)
{
    if(root == NULL)
    {
        return 0;
    }
    SeqQueue queue;
    SeqQueueInit(&queue);
    SeqQueuePush(&queue,root);
    //两个阶段分别进行判定

    int if_start_two_step_flag = 0;//进入第二阶段的标志位
    TreeNode* cur = NULL;
    while(SeqQueueFront(&queue,&cur))
    {
        SeqQueuePop(&queue);
        //首先判断在哪个阶段
        if(if_start_two_step_flag == 0)
        {
            //阶段一
            if(cur->lchild != NULL && cur->rchild != NULL)
            {
                //当前节点同时具备左右子树
                SeqQueuePush(&queue,cur->lchild);
                SeqQueuePush(&queue,cur->rchild);
            }
            else if(cur->lchild != NULL && cur->rchild == NULL)
            {
                // 当前节点只有左子树,没有右子树
                if_start_two_step_flag = 1;
                //进入第二阶段
            }
            else if(cur->lchild == NULL && cur->rchild != NULL)
            {
                //当前节点只有右子树,没有左子树
                return 0;
                //直接退出
            }
            else
            {
                //没有左右子树,进入第二阶段
                if_start_two_step_flag = 1;
            }
        }
        else
        {
            //进入第二阶段
            if(cur->lchild == NULL && cur->rchild == NULL)
                ;
            else
            {
                return 0;
            }
        }
    }
    //循环结束,所有节点都判定过了,此时又没有return 0的情况发生
    return 1;

}

3.给一个前序遍历和中序遍历的结果重建二叉树
这里写图片描述

思路:我们定义一个指针pre_order_index指向pre_order数组,用于遍历这个数组,在in_order数组中定义两个指针,一个用于标记左子树,另一个用来标记右子树,区间为左闭右开[in_order_left,in_order_right)。
首先在pre_order数组拿到当前元素,然后查找当前元素在in_order数组中的位置;这个位置将in_order数组分为两部分,左边即[in_order_lerf,cur_root_in_order_index)就是当前元素的左子树,右边[cur_root_in_order_index+1,in_order_right)即为右子树。然后重复上述过程,直到所有元素都遍历过了。

具体实现代码如下:

size_t Find(TreeNodeType to_find,TreeNodeType array[],size_t *left,size_t *right)
{
    size_t i = *left ;
    for( ;i < *right;i++)
    {
        if(array[i] == to_find)
        {
            return i;
        }
    }
    return (size_t)0;
}

//递归的调用下面这个函数完成数的重建
TreeNode* _TreeReBuild(TreeNodeType pre_order[],size_t size,size_t* pre_order_index,TreeNodeType in_order[],size_t* in_order_left,size_t* in_order_right)
{
    if(pre_order_index == NULL)
    {
        return NULL;//非法输入
    }
    if(*pre_order_index >= size)
    {
        return NULL;//遍历完了
    }
    if(*in_order_left >= *in_order_right)
    {
        return NULL;//无效区间
    }
    //先拿当前点,即先序遍历的第一个值创建一个节点
    //此时这个节点就是根节点
    TreeNode* new_node = CreateTreeNode(pre_order[*pre_order_index]);
    //查找一下当前节点在中序遍历中的位置
    size_t cur_root_in_order_index = Find(new_node->data,in_order,in_order_left,in_order_right);
    ++(*pre_order_index);
    new_node->lchild = _TreeReBuild(pre_order,size,pre_order_index,in_order,in_order_left,&cur_root_in_order_index);
    ++cur_root_in_order_index;
    new_node->rchild = _TreeReBuild(pre_order,size,pre_order_index,in_order,&cur_root_in_order_index,in_order_right);
    return new_node;
}

TreeNode* TreeReBuild(TreeNodeType pre_order[],TreeNodeType in_order[],size_t size)
{
    size_t pre_order_index = 0;//定义一个指向pre_order的指针
    //区间为左闭右开[in_order_left,in_order_right)
    size_t in_order_left = 0;
    size_t in_order_right = size;
    return _TreeReBuild(pre_order,size,&pre_order_index,in_order,&in_order_left,&in_order_right);

猜你喜欢

转载自blog.csdn.net/y6_xiamo/article/details/80387011