二叉树常见面试题

二叉树常见面试题

二叉树的基本操作实现见博客 点击打开链接
本文将每个函数的测试代码也附在函数后面

1. 非递归实现先序遍历

实现思想:
实现代码:
void TreePreOrderByLoop(TreeNode* root)//1.非递归先序遍历
{//利用栈辅助实现
    if(root == NULL)
        return;
    //1.先将根结点入栈
    SeqStack stack;                                                                                                                                   
    SeqStackInit(&stack);
    SeqStackPush(&stack,root);
    //2.循环开始
    while(1)
    {
        //取栈顶元素为当前元素
        TreeNode* cur = NULL;
        int ret = SeqStackGetTop(&stack,&cur);
        //栈空代表遍历结束
        if(ret == 0)
            break;
        //出栈当前元素,并访问当前元素
        SeqStackPop(&stack);
        printf("[%c] ",cur->data);
        //把当前元素的右子树入栈,再将当前元素的左子树入栈(为空不入栈)
        if(cur->rchild != NULL)
            SeqStackPush(&stack,cur->rchild);
        if(cur->lchild != NULL)
            SeqStackPush(&stack,cur->lchild);
    }
    printf("\n");
}

void TestPreOrderByLoop()
{
    SHOW_NAME;
    TreeNode* root;
    TreeInit(&root);
    TreeNodeType data[] = "abd##eg###c#f##";
    size_t size = sizeof(data) - 1;//这里用sizeof比strlen效率高一点
    TreeNodeType null_node = '#';
    TreeNode* new_node = TreeCreate(data,size,null_node);
    TreePreOrderByLoop(new_node);
}

2. 非递归实现中序遍历
实现思想:

实现代码:
void TreeInOrderByLoop(TreeNode* root)//2.非递归中序遍历
{//利用栈辅助实现
    if(root == NULL)
        return;
    SeqStack stack;
    SeqStackInit(&stack);
    //1.定义cur指针,指向根节点,根节点不为空时,将根节点入栈,且cur=cur->lchild
    TreeNode* cur = root;
    //2.循环开始
    while(1)
    {
        //直至左子树为空时才结束循环,因为中序遍历是先访问左子树
        while(cur != NULL)
        {
            SeqStackPush(&stack,cur);
            cur = cur->lchild;
        }
        //取栈顶元素为当前元素
        TreeNode* top = NULL;
        int ret = SeqStackGetTop(&stack,&top);
        //栈空表示访问结束
        if(ret == 0)
            break;
        //访问当前元素,并出栈
        printf("[%c] ",top->data);
        SeqStackPop(&stack);
        //3.让cur指向栈顶元素的右子树,重复2的判断过程
        cur = top->rchild;                                                                                                                            
    }
    printf("\n");
}

void TestInOrderByLoop()
{
    SHOW_NAME;
    TreeNode* root;
    TreeInit(&root);
    TreeNodeType data[] = "abd##eg###c#f##";
    size_t size = sizeof(data) - 1;//这里用sizeof比strlen效率高一点
    TreeNodeType null_node = '#';
    TreeNode* new_node = TreeCreate(data,size,null_node);
    TreeInOrderByLoop(new_node);
}

3. 非递归实现后续遍历
实现思想:

实现代码:
void TreePostOrderByLoop(TreeNode* root)//3.非递归后序遍历
{//利用栈辅助实现
    if(root == NULL)
        return;
    SeqStack stack;
    SeqStackInit(&stack);
    //1.定义cur指针,指向根节点,根节点不为空时,将根节点入栈,且cur=cur->lchild
    TreeNode* cur = root;
    TreeNode* pre = NULL;//用来保存上一个访问的元素
    //2.循环开始
    while(1)
    {
        //直至左子树为空时才结束循环,因为中序遍历是先访问左子树
        while(cur != NULL)
        {
            SeqStackPush(&stack,cur);
            cur = cur->lchild;
        }
        //取栈顶元素为当前元素
        TreeNode* top = NULL;
        int ret = SeqStackGetTop(&stack,&top);
        //栈空表示访问结束
        if(ret == 0)
            break;
        //判断当前元素,若当前元素的右子树为空或刚刚已访问过,则可以访问当前元素并出栈
        if(top->rchild==NULL || top->rchild==pre)
        {
            printf("[%c] ",top->data);
            SeqStackPop(&stack);                                                                                                                      
            //要注意更新pre指针
            pre = top;
        }
        //3.当前元素不符合刚刚判断当前元素的规则,则让cur指向栈顶元素的右子树,重复2的判断过程
        else
            cur = top->rchild;
    }
    printf("\n");
}

void TestPostOrderByLoop()
{
    SHOW_NAME;
    TreeNode* root;
    TreeInit(&root);
    TreeNodeType data[] = "abd##eg###c#f##";
    size_t size = sizeof(data) - 1;//这里用sizeof比strlen效率高一点
    TreeNodeType null_node = '#';
    TreeNode* new_node = TreeCreate(data,size,null_node);
    TreePostOrderByLoop(new_node);
}
                         

4. 树的镜像
实现思想:
    将树的每个结点的左右子树交换,生成的新树即是实现了树的镜像的新树。这里写了两种方法,
实现代码:
void Swap(TreeNode** a, TreeNode** b)//4.树的镜像实现的交换函数
{
    TreeNode* tmp = *a;
    *a = *b;
    *b = tmp;                                                                                                                                         
}

void TreeMirror1(TreeNode* root)//树的镜像(方法1)
{//树的镜像即将树对称的画出来,即左右子树都交换
    if(root == NULL)
        return;
    //访问动作即交换左右子树
    Swap(&root->lchild,&root->rchild);
    TreeMirror1(root->lchild);
    TreeMirror1(root->rchild);
    return;
}

void TreeMirror2(TreeNode* root)//树的镜像(方法2)
{//层序遍历、利用队列实现
    if(root == NULL)
        return;
    SeqQueue q;
    SeqQueueInit(&q);
    //1.将根节点入队
    SeqQueuePush(&q,root);
    TreeNode* front = NULL;
    //2.循环开始,若当前队首元素不为空,交换当前元素的左右子树并出队
    while(SeqQueueGetFront(&q,&front))
    {
        //此处的访问即交换左右子树
        Swap(&front->lchild,&front->rchild);
        SeqQueuePop(&q);
        //3.若当前元素的左右子树不为空,则入队,继续循环判断
        if(front->lchild != NULL)
            SeqQueuePush(&q,front->lchild);
        if(front->rchild != NULL)
            SeqQueuePush(&q,front->rchild);
    }
    return;
}

void TestMirror()
{
    SHOW_NAME;
    TreeNode* root;
    TreeInit(&root);
    TreeNodeType data[] = "abd##eg###c#f##";
    size_t size = sizeof(data) - 1;//这里用sizeof比strlen效率高一点
    TreeNodeType null_node = '#';
    TreeNode* new_node = TreeCreate(data,size,null_node);
    TreePreOrderByLoop(new_node);
    TreeMirror1(new_node);
    TreePreOrderByLoop(new_node);
    TreeMirror2(new_node);
    TreePreOrderByLoop(new_node);
}

5.判断一棵树是否为完全二叉树
实现思想:

    利用队列通过层序遍历的思想,判断,主要分为两个阶段:
1.任何一个结点应该同时具有左右两个子树,若哪个结点不是同时具备,则:

(1)若当前结点只有右子树,不是完全二叉树;
(2)若当前结点只有左子树,进入第二阶段;
(3)若当前结点无子树,进入第二阶段;

2.进入第二阶段的结点,均必须无子树,若有子树说明不是完全二叉树
当以上全部判断过,函数还未返回,说明该树是完全二叉树。

实现代码:
int IsCompleteTree(TreeNode* root)//5.判断当前树是否为完全二叉树
{
    if(root == NULL)//空树
        return 0;
    SeqQueue q;
    SeqQueueInit(&q);
    SeqQueuePush(&q,root);
    int if_start_step_two_flag = 0;//是否开始第二阶段的标志
    TreeNode* cur = NULL;
    while(SeqQueueGetFront(&q,&cur))
    {
        SeqQueuePop(&q);
        //一.第一阶段
        if(if_start_step_two_flag == 0)
        {
            //1.当前结点同时具有左右子树,将左右子树入队
            if(cur->lchild!=NULL && cur->rchild!=NULL)
            {
                SeqQueuePush(&q,cur->lchild);
                SeqQueuePush(&q,cur->rchild);
            }
            //2.当前结点只有右子树,但无左子树,说明不是完全二叉树,直接返回
            else if(cur->lchild==NULL && cur->rchild!=NULL)
            {
                return 0;
            }
            //3.当前结点只有左子树无右子树,将左子树入队,进入第二阶段
            else if(cur->lchild!=NULL && cur->rchild==NULL)
            {                                                                                                                                         
                if_start_step_two_flag = 1;
                SeqQueuePush(&q,cur->lchild);
            }
            //4.当前结点无左右子树,进入第二阶段
            else
            {
                if_start_step_two_flag = 1;
            }
        }

        //二.第二阶段
        else
        {
            //任何一个结点都应该没有子树,否则不是完全二叉树
            if(cur->lchild==NULL && cur->rchild==NULL)
                ;
            else
                return 0;
        }
    }
    //所有条件都满足,时一个完全二叉树
    return 1;
}

void TestIsCompleteTree()
{
    SHOW_NAME;
    TreeNode* root;
    TreeInit(&root);
    TreeNodeType data[] = "abd##eg###c#f##";
    size_t size = sizeof(data) - 1;//这里用sizeof比strlen效率高一点
    TreeNodeType null_node = '#';
    TreeNode* new_node = TreeCreate(data,size,null_node);
    int ret = IsCompleteTree(new_node);                                                                                                               
    printf("expected is 0, actual is %d\n",ret);
    TreeNode* root1;
    TreeInit(&root1);
    TreeNodeType data1[] = "abd##e##cf###";
    size_t size1 = sizeof(data1) - 1;//这里用sizeof比strlen效率高一点
    TreeNodeType null_node1 = '#';
    TreeNode* new_node1 = TreeCreate(data1,size1,null_node1);
    int ret1 = IsCompleteTree(new_node1);
    printf("expected is 1, actual is %d\n",ret1);
}

6. 给定先序遍历序列和中序遍历序列,还原一棵树
实现思想:

先序序列:ABDFGCH,中序序列:DBFGAHC。因为先序是:根节点->左子树->右子树,中序是:左子树->根节点->右子树。所以,在上述序列中:定义index为先序遍历序列中的下标,left为中序序列中某棵树所在范围左侧的下标,right为中序序列中某棵树所在范围右侧的下标。该范围是一左闭右开区间,如树A的范围为:[0,7)

(1)根据下标范围判断该树是否为空(为空:left >= right)

(2)不为空,则根据index处的元素创建结点,作为当前树的当前根节点

(3)找到当前根节点在中序序列中的下标,从而确定当前根节点的左右子树的范围。然后index++遍历到之后的结点。

(4)如果左子树不为空,则index处的元素必为左子树的根节点,此时可以根据index创建新的结点作为左子树的根结点(此处的操作与(1)~(3)相同,因此可以递归的创建左子树)。否则转至(5)

(5)如果右子树不为空,则index处的元素必为右子树的根节点,此时可以根据index处创建新的结点作为右子树的根节点(此处的操作与(1)~(3)相同,因此可以递归的创建右子树)。否则根节点的左右子树均创建完毕,直接返回根节点即可。

实现代码:
size_t Find(TreeNodeType arr[], size_t left, size_t right, TreeNodeType to_find)//6.还原一棵树的查找当前结点在中序序列中的位置
{
    size_t i = 0;
    for(i=left; i<right; i++)
    {
        if(arr[i] == to_find)
            return i;
    }
    return -1;
}

TreeNode* _TreeReBulid(TreeNodeType pre_order[], size_t pre_order_size, size_t* pre_order_index,\
        TreeNodeType in_order[], size_t in_order_left, size_t in_order_right)//真正还原一棵树的代码
{
    if(in_order_left >= in_order_right)//无效区间
        return NULL;
    if(pre_order_index == NULL)//非法输入
        return NULL;
    if(*pre_order_index >= pre_order_size)//遍历结束
        return NULL;
    //根据先序遍历结果取出当前值,基于该值构建一个结点
    TreeNode* new_node = CreateTreeNode(pre_order[*pre_order_index]);
    //查找当前结点在中序遍历序列中的位置
    size_t cur_root_in_order_index = Find(in_order,in_order_left,in_order_right,new_node->data);
    //避免找不到当前结点在中序序列中的位置
    assert(cur_root_in_order_index != (size_t)-1);
    //左子树区间 [in_order_left, cur_root_in_order_index);
    //右子树区间 [cur_root_in_order_index+1, in_order_right);
    ++(*pre_order_index);                                                                                                                             
    new_node->lchild = _TreeReBulid(pre_order, pre_order_size, pre_order_index, in_order, in_order_left,cur_root_in_order_index);
    new_node->rchild = _TreeReBulid(pre_order, pre_order_size, pre_order_index, in_order, cur_root_in_order_index+1, in_order_right);
    return new_node;
}
TreeNode* TreeReBulid(TreeNodeType pre_order[], TreeNodeType in_order[], size_t size)//给定先序和中序遍历结果,还原一棵树
{
    size_t pre_order_index = 0;
    //前闭后开区间,用来表示对当前子树中序遍历的结果
    size_t in_order_left = 0;
    size_t in_order_right = size;
    return _TreeReBulid(pre_order,size,&pre_order_index,in_order,in_order_left,in_order_right);
}

void TestReBuild()
{
    SHOW_NAME;
    TreeNode* root;
    TreeInit(&root);
    TreeNodeType pre_order[] = "abdegcf";
    TreeNodeType in_order[] = "dbgeacf";
    size_t size = sizeof(pre_order) - 1;//这里用sizeof比strlen效率高一点
    TreeNode* new_node = TreeReBulid(pre_order, in_order, size);
    printf("pre_order# %s\n", pre_order);
    TreePreOrderByLoop(new_node);
    printf("in_order# %s\n", in_order);
    TreeInOrderByLoop(new_node);
}

猜你喜欢

转载自blog.csdn.net/lycorisradiata__/article/details/80273661