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

一、实验描述

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

二、问题分析与算法设计

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;}

输出结果:

即:

     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;}

输出结果:

五、实验结论

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_Hstruct 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; // 右子树};// 建立一棵空树SearchTreeMakeEmpty(SearchTree T){    if (T != NULL)    {        MakeEmpty(T->Left); // 递归删除左子树        MakeEmpty(T->Right); // 递归删除右子树        free(T); // 释放该节点    }    return NULL;}// 二叉搜索树的查找操作PositionFind(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;}// 查找最小元素,即找出最左边的叶子节点PositionFindMin(SearchTree T){    if (T == NULL)        return NULL;    else if (T->Left == NULL)        return T;    else        return FindMin(T->Left);}// 查找最大值PositionFindMax(SearchTree T){    if (T != NULL)        while (T->Right != NULL)            T = T->Right;    return T;}// 插入操作SearchTreeInsert(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!! */}// 删除操作SearchTreeDelete(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 中的元素ElementTypeRetrieve(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_hstruct 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

来源:https://www.icode9.com/content-4-749201.html

(0)

相关推荐