实验五 根据先序和中序构造二叉树、二叉树的层序遍历

一、实验描述

  • 给出一棵二叉树的先序(或后序)遍历结果,以及中序遍历结果,如何构造这棵树?假定遍历结果以数组方式输入,请写出相应函数,判断是否存在生成同样遍历结果的树,如果存在,构造这棵树。
  • 二叉树的层序遍历。使用队列作为辅助存储,按树的结点的深度,从根开始依次访问所有结点。

二、问题分析与算法设计

1、根据先序和中序构造二叉树

1.1、分析

二叉树前序遍历的顺序为:

  • 先遍历根节点;
  • 随后递归地遍历左子树;
  • 最后递归地遍历右子树。

二叉树中序遍历的顺序为:

  • 先递归地遍历左子树;
  • 随后遍历根节点;
  • 最后递归地遍历右子树。

在「递归」地遍历某个子树的过程中,我们也是将这颗子树看成一颗全新的树,按照上述的顺序进行遍历。挖掘「前序遍历」和「中序遍历」的性质,我们就可以得出根据前序和中序遍历构造二叉树的做法。

1.2、思路

对于任意一颗树而言,前序遍历的形式总是

[ 根节点, [左子树的前序遍历结果], [右子树的前序遍历结果] ]

即根节点总是前序遍历中的第一个节点。而中序遍历的形式总是

[ [左子树的中序遍历结果], 根节点, [右子树的中序遍历结果] ]

只要我们在中序遍历中定位到根节点,那么我们就可以分别知道左子树和右子树中的节点数目。由于同一颗子树的前序遍历和中序遍历的长度显然是相同的,因此我们就可以对应到前序遍历的结果中,对上述形式中的所有左右括号进行定位。

这样一来,我们就知道了左子树的前序遍历和中序遍历结果,以及右子树的前序遍历和中序遍历结果,我们就可以递归地对构造出左子树和右子树,再将这两颗子树接到根节点的左右位置。

2、二叉树的层序遍历

2.1、分析

二叉树的遍历的核心问题:二维结构的线性化

  • 从节点访问器左右儿子节点
  • 访问左儿子后,右儿子节点怎么办?
    • 使用队列作为辅助存储,按树的结点的深度,从根开始依次访问所有结点。

2.2、思路

遍历从根节点开始,首先将根节点入队,然后开始执行循环:节点出队、访问该节点、其左右儿子入队。

层序基本过程:先根节点入队,然后:

Ⅰ从队列中取出一个元素;
Ⅱ访问该元素所指节点
Ⅲ若该元素所指节点的左、右孩子节点非空,则将其左、右孩子的指针顺序入队

三、算法实现

1、构造二叉树

struct TreeNode {
    int val;
    struct TreeNode *left;
    struct TreeNode *right;
};

typedef struct TreeNode *BinTree;


struct TreeNode* buildTree(int* preorder, int preorderSize, int* inorder, int inorderSize)
{
    struct TreeNode *newNode;
    int p = 0;
    int i = 0;

    // 去除输入不合理的情况
    if (preorder == NULL || inorder == NULL)
    {
        return NULL;
    }
    if (preorderSize <= 0 || inorderSize <= 0)
    {
        return NULL;
    }

    newNode = (struct TreeNode *) malloc(sizeof(struct TreeNode));
    newNode->val = preorder[p]; // 根据前序取出根节点
    newNode->left = NULL;
    newNode->right = NULL;

    for (i = 0; i < inorderSize; i++)
    {
        // 在中序中找到根节点,然后递归
        if (inorder[i] == newNode->val)
        {
            newNode->left = buildTree(&preorder[p + 1], i, inorder, i); // 递归构造左子树
            newNode->right = buildTree(&preorder[p + i + 1], preorderSize - i - 1, &inorder[i + 1],
                                       inorderSize - i - 1); // 递归构造右子树
            break;
        }
    }

    return newNode;
}

2、层序遍历

// 层序
void LevelOrderTraversal(SearchTree T) {
    Queue Q;
    SearchTree ST;
    if (!T) return; /* 如果是空树就直接返回 */
    Q = CreateQueue();
    Enqueue(T, Q); /* 将根节点入队 */
    while (!IsEmpty(Q)) {
        ST = FrontAndDequeue(Q);
        printf("%d ", ST->Element); /* 访问取出队列的节点 */
        if (ST->Left) Enqueue(ST->Left, Q);
        if (ST->Right) Enqueue(ST->Right, Q);
    }
}

四、实验结果

1、构造二叉树

测试代码:

// 测试
int main()
{
    int preorder[5] = {3, 9, 20, 15, 7};
    int inorder[5] = {9, 3, 15, 20, 7};

    BinTree buildedTree = buildTree(preorder, 5, inorder, 5);

    printf("前序遍历:");
    PreOrderTraversal(buildedTree);
    printf("\n");
    printf("中序遍历:");
    InOrderTraversal(buildedTree);
    printf("\n");
    printf("后序遍历:");
    PostOrderTraversal(buildedTree);

    return 0;
}

输出结果:

20201031143818

即:

     3
    / \
   9  20
     /  \
    15   7

2、层序遍历

测试代码:

int main( )
{
    SearchTree T;
    Position P;


    int i;
    int j = 15;

    T = MakeEmpty( NULL ); // 创建一棵空树
    for( i = 0; i < 50; i++, j = ( j + 7 ) % 50 ) // 将 50 个数插入树中
        T = Insert( j, T );
    for( i = 0; i < 50; i++ )
        if( ( P = Find( i, T ) ) == NULL || Retrieve( P ) != i ) // 测试查找函数
            printf( "Error at %d\n", i );

    /* 测试遍历 */
    printf("先序遍历 \n");
    PreOrderTraversal(T);
    printf("\n");
    printf("中序遍历 \n");
    InOrderTraversal(T);
    printf("\n");
    printf("后序遍历 \n");
    PostOrderTraversal(T);
    printf("\n");
    printf("层序遍历 \n");
    LevelOrderTraversal(T);
    printf("\n");
    /* 测试遍历 */

    return 0;
}

输出结果:

20201031144653

五、实验结论

1、构造二叉树

由于上面的算法使用的是线性查找,所以在二叉树平衡的情况下,其时间复杂度为 \(O(NlogN)\),如果二叉树不平衡,则最坏情况下的时间复杂度为 \(O(N^2)\)。我们这里为了方便,就采取了简单的线性扫描。

如果想提高查找的效率,我们可以考虑使用哈希映射(HashMap)来帮助我们快速地定位根节点。对于哈希映射中的每个键值对,键表示一个元素(节点的值),值表示其在中序遍历中的出现位置。在构造二叉树的过程之前,我们可以对中序遍历的列表进行一遍扫描,就可以构造出这个哈希映射。在此后构造二叉树的过程中,我们就只需要 \(O(1)\) 的时间对根节点进行定位了。

2、层序遍历

显然,对于二叉树中的每一个节点,我们仅访问了一次,因此,时间复杂度为 \(O(N)\)

六、附:完整代码

1、构造二叉树

#include <stdio.h>
#include <malloc.h>

// Definition for a binary tree node.
struct TreeNode {
    int val;
    struct TreeNode *left;
    struct TreeNode *right;
};

typedef struct TreeNode *BinTree;


struct TreeNode* buildTree(int* preorder, int preorderSize, int* inorder, int inorderSize)
{
    struct TreeNode *newNode;
    int p = 0;
    int i = 0;

    // 去除输入不合理的情况
    if (preorder == NULL || inorder == NULL)
    {
        return NULL;
    }
    if (preorderSize <= 0 || inorderSize <= 0)
    {
        return NULL;
    }

    newNode = (struct TreeNode *) malloc(sizeof(struct TreeNode));
    newNode->val = preorder[p]; // 根据前序取出根节点
    newNode->left = NULL;
    newNode->right = NULL;

    for (i = 0; i < inorderSize; i++)
    {
        // 在中序中找到根节点,然后递归
        if (inorder[i] == newNode->val)
        {
            newNode->left = buildTree(&preorder[p + 1], i, inorder, i); // 递归构造左子树
            newNode->right = buildTree(&preorder[p + i + 1], preorderSize - i - 1, &inorder[i + 1],
                                       inorderSize - i - 1); // 递归构造右子树
            break;
        }
    }

    return newNode;
}

void freeTree(struct TreeNode *T)
{
    if (NULL == T)
        return;

    freeTree(T->left);
    freeTree(T->right);
    freeTree(T);
}

// 先序
void PreOrderTraversal(struct TreeNode *T)
{
    if (T) {
        printf("%d ", T->val);
        PreOrderTraversal(T->left);
        PreOrderTraversal(T->right);
    }
}

// 中序
void InOrderTraversal(struct TreeNode *T) {
    if (T) {
        InOrderTraversal(T->left);
        printf("%d ", T->val);
        InOrderTraversal(T->right);
    }
}

// 后序
void PostOrderTraversal(struct TreeNode *T) {
    if (T) {
        PostOrderTraversal(T->left);
        PostOrderTraversal(T->right);
        printf("%d ", T->val);
    }
}

// 测试
int main()
{
    int preorder[5] = {3, 9, 20, 15, 7};
    int inorder[5] = {9, 3, 15, 20, 7};

    BinTree buildedTree = buildTree(preorder, 5, inorder, 5);

    printf("前序遍历:");
    PreOrderTraversal(buildedTree);
    printf("\n");
    printf("中序遍历:");
    InOrderTraversal(buildedTree);
    printf("\n");
    printf("后序遍历:");
    PostOrderTraversal(buildedTree);

    return 0;
}

2、层序遍历

tree.h

typedef int ElementType;

#ifndef _Tree_H
#define _Tree_H

struct TreeNode; // 定义结构体节点
typedef struct TreeNode *Position; // 指向节点的指针
typedef struct TreeNode *SearchTree; // 搜索树的根节点

SearchTree MakeEmpty( SearchTree T );
Position Find( ElementType X, SearchTree T );
Position FindMin( SearchTree T );
Position FindMax( SearchTree T );
SearchTree Insert( ElementType X, SearchTree T );
SearchTree Delete( ElementType X, SearchTree T );
ElementType Retrieve( Position P );

/* 树的遍历方法 */
void PreOrderTraversal(SearchTree T);
void InOrderTraversal(SearchTree T);
void PostOrderTraversal(SearchTree T);
void LevelOrderTraversal(SearchTree T);
/* 树的遍历方法 */

#endif

tree.c

#include "tree.h"
#include <stdlib.h>
#include "fatal.h"
#include "queueli.h"

struct TreeNode
{
    ElementType Element; // 树节点存储的元素
    SearchTree Left; // 左子树
    SearchTree Right; // 右子树
};

// 建立一棵空树
SearchTree
MakeEmpty(SearchTree T)
{
    if (T != NULL)
    {
        MakeEmpty(T->Left); // 递归删除左子树
        MakeEmpty(T->Right); // 递归删除右子树
        free(T); // 释放该节点
    }
    return NULL;
}

// 二叉搜索树的查找操作
Position
Find(ElementType X, SearchTree T)
{
    if (T == NULL)
        return NULL;
    if (X < T->Element) // 如果待查找元素比根节点小,那么递归查找左子树
        return Find(X, T->Left);
    else if (X > T->Element) // 如果待查找元素比根节点大,那么递归查找右子树
        return Find(X, T->Right);
    else
        return T;
}

// 查找最小元素,即找出最左边的叶子节点
Position
FindMin(SearchTree T)
{
    if (T == NULL)
        return NULL;
    else if (T->Left == NULL)
        return T;
    else
        return FindMin(T->Left);
}

// 查找最大值
Position
FindMax(SearchTree T)
{
    if (T != NULL)
        while (T->Right != NULL)
            T = T->Right;

    return T;
}

// 插入操作
SearchTree
Insert(ElementType X, SearchTree T)
{
    if (T == NULL)
    {
    /* Create and return a one-node tree 创建并返回一个单节点树 */
    T = malloc(sizeof(struct TreeNode));
    if (T == NULL)
        FatalError("Out of space!!!"); // 空间用尽的情况
        else
        {
        T->Element = X; // 赋值
        T->Left = T->Right = NULL; // 左右子树置空
        }
    } else
    if (X < T->Element)
        T->Left = Insert(X, T->Left); // 递归寻找合适的插入位置
        else
    if (X > T->Element)
        T->Right = Insert(X, T->Right);
        /* Else X is in the tree already; we'll do nothing */
    return T;  /* Do not forget this line!! */
}

// 删除操作
SearchTree
Delete(ElementType X, SearchTree T)
{
    Position TmpCell;

    // 寻找节点
    if (T == NULL)
        Error("Element not found");
    else if (X < T->Element)  /* Go left */
        T->Left = Delete(X, T->Left);
    else if (X > T->Element)  /* Go right */
        T->Right = Delete(X, T->Right);
    else  /* Found element to be deleted 找到了该删除的节点 */
    {
        if (T->Left && T->Right)  /* Two children 有两个孩子 */
        {
            /* Replace with smallest in right subtree 用右子树中最小的节点进行替换 */
            TmpCell = FindMin(T->Right); // 找出右子树中最小的节点
            T->Element = TmpCell->Element; // 替换
            T->Right = Delete(T->Element, T->Right); // 删除刚刚的那个在右子树中最小的节点
        } else  /* One or zero children 有 1 个或者 0 个孩子 */
        {
            TmpCell = T;
            if (T->Left == NULL) /* Also handles 0 children */
                T = T->Right; // 如果左子树为空,那么将 T 更新为右子树,下同
            else if (T->Right == NULL)
                T = T->Left;
            free(TmpCell); // 释放原来的 T 节点
        }
    }


    return T;
}

// 取出 Position P 中的元素
ElementType
Retrieve(Position P)
{
    return P->Element;
}

// 先序
void PreOrderTraversal(SearchTree T)
{
    if (T) {
        printf("%d ", T->Element);
        PreOrderTraversal(T->Left);
        PreOrderTraversal(T->Right);
    }
}

// 中序
void InOrderTraversal(SearchTree T) {
    if (T) {
        InOrderTraversal(T->Left);
        printf("%d ", T->Element);
        InOrderTraversal(T->Right);
    }
}

// 后序
void PostOrderTraversal(SearchTree T) {
    if (T) {
        PostOrderTraversal(T->Left);
        PostOrderTraversal(T->Right);
        printf("%d ", T->Element);
    }
}

// 层序
void LevelOrderTraversal(SearchTree T) {
    Queue Q;
    SearchTree ST;
    if (!T) return; /* 如果是空树就直接返回 */
    Q = CreateQueue();
    Enqueue(T, Q); /* 将根节点入队 */
    while (!IsEmpty(Q)) {
        ST = FrontAndDequeue(Q);
        printf("%d ", ST->Element); /* 访问取出队列的节点 */
        if (ST->Left) Enqueue(ST->Left, Q);
        if (ST->Right) Enqueue(ST->Right, Q);
    }
}

queueli.h

#include "tree.h"

typedef Position ElementType1;

#ifndef _Queueli_h
#define _Queueli_h

struct Node;
struct QNode;
typedef struct Node *PtrToNode; // 指向Node节点的指针
typedef struct QNode *Queue; // 队列头,也是指向QNode节点的指针

int IsEmpty(Queue Q);

Queue CreateQueue(void);

void DisposeQueue(Queue Q);

void MakeEmpty1(Queue Q);

void Enqueue(ElementType1 X, Queue Q);

ElementType1 Front(Queue Q);

void Dequeue(Queue Q);

ElementType1 FrontAndDequeue(Queue Q);

#endif

queueli.c

#include "queueli.h"
#include "fatal.h"
#include <stdio.h>
// #include "tree.h"

// 节点
struct Node
{
    ElementType1 Element;
    PtrToNode Next;
};

struct QNode
{
    PtrToNode rear; // 指向队尾节点
    PtrToNode front; // 指向对头节点
};

// 判断队列是否为空
int IsEmpty(Queue Q)
{
    return (Q->front == NULL);
}

Queue CreateQueue(void)
{
    Queue Q;
    Q = malloc(sizeof(struct QNode));
    if (Q == NULL)
        FatalError("Out of space!!!"); // 空间用尽警告
    Q->front = NULL;
    Q->rear = NULL;
    MakeEmpty1(Q); // 还是感觉有些多此一举
    return Q;
}

// 创建一个空队列
void MakeEmpty1(Queue Q)
{
    if (Q == NULL)
        Error("Must use CreateQueue first");
    else
        while (!IsEmpty(Q))
            Dequeue(Q);
}

// 清除队列
void DisposeQueue(Queue Q)
{
    if (Q != NULL)
    {
        MakeEmpty1(Q);
        free(Q);
    }
}

// 入队操作
void Enqueue(ElementType1 X, Queue Q)
{
    PtrToNode TmpCell;

    TmpCell = malloc(sizeof(struct Node));
    if (TmpCell == NULL)
        FatalError("Out of space!!!");
    TmpCell->Element = X;
    TmpCell->Next = NULL;
    if (Q->rear == NULL)
    { // 此时队列为空
        Q->rear = TmpCell;
        Q->front = TmpCell;
    } else { // 不为空
        Q->rear->Next = TmpCell; // 将节点入队
        Q->rear = TmpCell; // rear 仍然保持最后
    }
}

// 取出队首元素
ElementType1 Front(Queue Q)
{
    if (!IsEmpty(Q))
        return Q->front->Element;
    Error("Empty queue");
    return NULL; // Return value used to avoid warning
}

// 出队操作
void Dequeue(Queue Q)
{
    PtrToNode FrontCell;
    if (IsEmpty(Q))
        Error("Empty queue");
    else
    {
        FrontCell = Q->front;
        if (Q->front == Q->rear) { // 只有一个元素
            Q->front = Q->rear = NULL;
        } else { // 有多个元素时
            Q->front = Q->front -> Next; // 先将front指向队首元素的下一个元素
        }
        free(FrontCell); // 不要忘了释放出对的节点
    }
}

// 在出队的同时返回该元素,即队首元素
ElementType1 FrontAndDequeue(Queue Q)
{
    ElementType1 X = NULL;

    if (IsEmpty(Q))
        Error("Empty queue");
    else
    {
        X = Front(Q);
        Dequeue(Q);
    }
    return X;
}

main.c

#include "tree.h"
#include <stdio.h>

int main( )
{
    SearchTree T;
    Position P;


    int i;
    int j = 15;

    T = MakeEmpty( NULL ); // 创建一棵空树
    for( i = 0; i < 50; i++, j = ( j + 7 ) % 50 ) // 将 50 个数插入树中
        T = Insert( j, T );
    for( i = 0; i < 50; i++ )
        if( ( P = Find( i, T ) ) == NULL || Retrieve( P ) != i ) // 测试查找函数
            printf( "Error at %d\n", i );

    /* 测试遍历 */
    printf("先序遍历 \n");
    PreOrderTraversal(T);
    printf("\n");
    printf("中序遍历 \n");
    InOrderTraversal(T);
    printf("\n");
    printf("后序遍历 \n");
    PostOrderTraversal(T);
    printf("\n");
    printf("层序遍历 \n");
    LevelOrderTraversal(T);
    printf("\n");
    /* 测试遍历 */

    return 0;
}

fatal.h

#include <stdio.h>
#include <stdlib.h>

#define Error(Str)        FatalError( Str )
#define FatalError(Str)   fprintf( stderr, "%s\n", Str ), exit( 1 )

参考:

  1. https://leetcode-cn.com/problems/construct-binary-tree-from-preorder-and-inorder-traversal/solution/cong-qian-xu-yu-zhong-xu-bian-li-xu-lie-gou-zao-9/
  2. 浙江大学数据结构 MOOC
posted @ 2020-10-31 15:56  模糊计算士  阅读(1242)  评论(0编辑  收藏  举报