【数据结构】树与二叉树(八):二叉树的中序遍历(非递归算法NIO)

文章目录

  • [5.2.1 二叉树](#5.2.1 二叉树)
    • 二叉树性质
      • [引理5.1:二叉树中层数为i的结点至多有 2 i 2^i 2i个,其中 i ≥ 0 i \geq 0 i≥0。](#引理5.1:二叉树中层数为i的结点至多有 2 i 2^i 2i个,其中 i ≥ 0 i \geq 0 i≥0。)
      • [引理5.2:高度为k的二叉树中至多有 2 k + 1 − 1 2^{k+1}-1 2k+1−1个结点,其中 k ≥ 0 k \geq 0 k≥0。](#引理5.2:高度为k的二叉树中至多有 2 k + 1 − 1 2^{k+1}-1 2k+1−1个结点,其中 k ≥ 0 k \geq 0 k≥0。)
      • [引理5.3:设T是由n个结点构成的二叉树,其中叶结点个数为 n 0 n_0 n0,度数为2的结点个数为 n 2 n_2 n2,则有 n 0 = n 2 + 1 n_0 = n_2 + 1 n0=n2+1。](#引理5.3:设T是由n个结点构成的二叉树,其中叶结点个数为 n 0 n_0 n0,度数为2的结点个数为 n 2 n_2 n2,则有 n 0 = n 2 + 1 n_0 = n_2 + 1 n0=n2+1。)
    • 满二叉树、完全二叉树定义、特点及相关证明
  • [5.2.2 二叉树顺序存储](#5.2.2 二叉树顺序存储)
  • [5.2.3 二叉树链接存储](#5.2.3 二叉树链接存储)
  • [5.2.4 二叉树的遍历](#5.2.4 二叉树的遍历)
    • [1-3 先序、中序、后序遍历递归实现及相关练习](#1-3 先序、中序、后序遍历递归实现及相关练习)
    • [4. 中序遍历非递归](#4. 中序遍历非递归)
      • [a. 算法NIO](#a. 算法NIO)
      • [b. 算法解读](#b. 算法解读)
      • [c. 典例剖析](#c. 典例剖析)
      • d.代码实现
    • [5. 代码整合](#5. 代码整合)

5.2.1 二叉树

二叉树是一种常见的树状数据结构,它由结点的有限集合组成。一个二叉树要么是空集 ,被称为空二叉树 ,要么由一个根结点和两棵不相交的子树组成,分别称为左子树右子树 。每个结点最多有两个子结点,分别称为左子结点和右子结点。

二叉树性质

引理5.1:二叉树中层数为i的结点至多有 2 i 2^i 2i个,其中 i ≥ 0 i \geq 0 i≥0。

引理5.2:高度为k的二叉树中至多有 2 k + 1 − 1 2^{k+1}-1 2k+1−1个结点,其中 k ≥ 0 k \geq 0 k≥0。

引理5.3:设T是由n个结点构成的二叉树,其中叶结点个数为 n 0 n_0 n0,度数为2的结点个数为 n 2 n_2 n2,则有 n 0 = n 2 + 1 n_0 = n_2 + 1 n0=n2+1。

满二叉树、完全二叉树定义、特点及相关证明

5.2.2 二叉树顺序存储

二叉树的顺序存储是指将二叉树中所有结点按层次顺序存放在一块地址连续的存储空间中,详见:
【数据结构】树与二叉树(五):二叉树的顺序存储(初始化,插入结点,获取父节点、左右子节点等)

5.2.3 二叉树链接存储

二叉树的链接存储系指二叉树诸结点被随机存放在内存空间中,结点之间的关系用指针说明。在链式存储中,每个二叉树结点都包含三个域:数据域(Data)、左指针域(Left)和右指针域(Right),用于存储结点的信息和指向子结点的指针,详见:
【数据结构】树与二叉树(六):二叉树的链式存储

5.2.4 二叉树的遍历

  • 遍历(Traversal)是对二叉树中所有节点按照一定顺序进行访问的过程。
  • 通过遍历,可以访问树中的每个节点,并按照特定的顺序对它们进行处理。
  • 对二叉树的一次完整遍历,可给出树中结点的一种线性排序。
    • 在二叉树中,常用的遍历方式有三种:先序遍历中序遍历后序遍历
    • 这三种遍历方式都可以递归地进行,它们的区别在于节点的访问顺序。
      • 在实现遍历算法时,需要考虑递归终止条件和递归调用的顺序。
    • 还可以使用迭代的方式来实现遍历算法,使用栈或队列等数据结构来辅助实现。
  • 遍历是二叉树中基础而重要的操作,它为其他许多操作提供了基础,如搜索、插入、删除等。

1-3 先序、中序、后序遍历递归实现及相关练习

【数据结构】树与二叉树(七):二叉树的遍历(先序、中序、后序及其C语言实现)

中序遍历递归实现

c 复制代码
void inOrderTraversal(struct Node* root) {
    if (root == NULL) {
        return;
    }
    // 递归遍历左子树
    inOrderTraversal(root->left);
    // 访问根节点
    printf("%c ", root->data);
    // 递归遍历右子树
    inOrderTraversal(root->right);
}

4. 中序遍历非递归

a. 算法NIO

b. 算法解读

NIO算法利用了一个辅助堆栈S来模拟递归过程中的函数调用栈。通过在循环中不断将左子节点入栈,然后处理栈顶节点,并将指针移动到右子节点,实现了中序遍历的非递归算法。

  1. 创建一个空堆栈S,并将指针p指向树的根节点t。
  2. 进入循环,只要p不为空,执行以下步骤:
    a. 将p入栈S。
    b. 将p指向其左子节点(p = Left( p ))。
  3. 如果堆栈S为空,则结束算法。
  4. 从堆栈S中弹出栈顶元素,并将p指向弹出的节点。
  5. 打印p节点的值。
  6. 将p指向p的右子节点(p = Right( p ))。
  7. 跳转到步骤2,继续循环。

该算法的时间复杂度为O(n),其中n是二叉树中节点的数量。因为每个节点都会被访问一次且入栈一次,所以算法的时间复杂度与节点数量成正比。

这个非递归中序遍历算法可以应用于需要遍历二叉树并按照中序顺序访问节点的场景,例如在构建二叉树的线索化结构时,或者需要按照中序顺序遍历二叉搜索树等情况下。

c. 典例剖析








d.代码实现

c 复制代码
void nonRecursiveInOrder(struct Node* root) {
    struct Node* stack[100];  // 辅助堆栈,用于模拟递归调用栈
    int top = -1;  // 栈顶指针

    struct Node* current = root;

    while (current != NULL || top != -1) {
        // 将当前结点的左子结点入栈
        while (current != NULL) {
            stack[++top] = current;
            current = current->left;
        }

        // 弹出栈顶结点,并访问
        current = stack[top--];
        printf("%c ", current->data);

        // 处理右子结点
        current = current->right;
    }
}

5. 代码整合

c 复制代码
#include <stdio.h>
#include <stdlib.h>

// 二叉树结点的定义
struct Node {
    char data;
    struct Node* left;
    struct Node* right;
};

// 创建新结点
struct Node* createNode(int data) {
    struct Node* newNode = (struct Node*)malloc(sizeof(struct Node));
    if (newNode == NULL) {
        printf("Memory allocation failed!\n");
        exit(1);
    }
    newNode->data = data;
    newNode->left = NULL;
    newNode->right = NULL;
    return newNode;
}
// 递归中序遍历
void inOrderTraversal(struct Node* root) {
    if (root == NULL) {
        return;
    }
    // 递归遍历左子树
    inOrderTraversal(root->left);
    // 访问根节点
    printf("%c ", root->data);
    // 递归遍历右子树
    inOrderTraversal(root->right);
}

// 非递归中序遍历
void nonRecursiveInOrder(struct Node* root) {
    struct Node* stack[100];  // 辅助堆栈,用于模拟递归调用栈
    int top = -1;  // 栈顶指针

    struct Node* current = root;

    while (current != NULL || top != -1) {
        // 将当前结点的左子结点入栈
        while (current != NULL) {
            stack[++top] = current;
            current = current->left;
        }

        // 弹出栈顶结点,并访问
        current = stack[top--];
        printf("%c ", current->data);

        // 处理右子结点
        current = current->right;
    }
}

int main() {
    // 创建一棵二叉树
    struct Node* root = createNode('a');
    root->left = createNode('b');
    root->right = createNode('c');
    root->left->left = createNode('d');
    root->left->right = createNode('e');
    root->left->right->left = createNode('f');
    root->left->right->right = createNode('g');

    // 递归中序遍历二叉树
    printf("Recursive In-order traversal: \n");
    inOrderTraversal(root);
    printf("\n");

    // 非递归中序遍历二叉树
    printf("Non-recursive In-order traversal:\n");
    nonRecursiveInOrder(root);
    printf("\n");
    return 0;
}
相关推荐
戊子仲秋17 分钟前
【LeetCode】每日一题 2024_9_19 最长的字母序连续子字符串的长度(字符串,双指针)
算法·leetcode·职场和发展
小林熬夜学编程35 分钟前
C++第五十一弹---IO流实战:高效文件读写与格式化输出
c语言·开发语言·c++·算法
蠢蠢的打码40 分钟前
8584 循环队列的基本操作
数据结构·c++·算法·链表·图论
无问8172 小时前
数据结构-排序(冒泡,选择,插入,希尔,快排,归并,堆排)
java·数据结构·排序算法
Lenyiin3 小时前
《 C++ 修炼全景指南:十 》自平衡的艺术:深入了解 AVL 树的核心原理与实现
数据结构·c++·stl
程序猿进阶3 小时前
如何在 Visual Studio Code 中反编译具有正确行号的 Java 类?
java·ide·vscode·算法·面试·职场和发展·架构
Eloudy3 小时前
一个编写最快,运行很慢的 cuda gemm kernel, 占位 kernel
算法
slandarer3 小时前
MATLAB | R2024b更新了哪些好玩的东西?
java·数据结构·matlab
king_machine design3 小时前
matlab中如何进行强制类型转换
数据结构·算法·matlab
西北大程序猿3 小时前
C++ (进阶) ─── 多态
算法