【数据结构】解锁二叉树遍历:从理论到实战的深度探索

引言:数据结构世界的二叉树奥秘

在计算机科学的广袤领域中,数据结构犹如基石,支撑着众多复杂算法与系统的构建。从简单的数组、链表,到复杂的树、图结构,每一种数据结构都有着独特的设计思想与适用场景,它们为数据的高效存储、检索、处理提供了多样化的解决方案。数据结构的合理选择,往往能使算法的效率产生质的飞跃,将原本复杂、耗时的计算任务变得简洁、快速。

二叉树,作为树型数据结构的一种特殊且重要的形式,在数据结构的大家庭中占据着举足轻重的地位。它的每个节点最多包含两个子节点,分别称为左子节点和右子节点,这种简洁而规整的结构设计,赋予了二叉树诸多优良特性。二叉树广泛应用于搜索算法、排序算法、编译器设计、文件系统管理等诸多领域,为解决各类实际问题提供了强大的支持。例如,在搜索场景中,二叉搜索树能够实现高效的查找操作,其时间复杂度相较于线性搜索大幅降低;在编译器中,语法树作为一种特殊的二叉树,用于分析和处理程序代码的语法结构,是编译过程中不可或缺的关键环节。

而二叉树遍历,作为研究和操作二叉树的核心技术,更是开启二叉树应用大门的钥匙。通过遍历,我们可以按照特定的顺序访问二叉树中的每一个节点,从而实现对树中数据的全面处理。无论是查找特定元素、统计节点数量,还是对树进行深度优先或广度优先的搜索,二叉树遍历都发挥着至关重要的作用。不同的遍历方式,如前序遍历、中序遍历、后序遍历和层序遍历,从不同角度揭示了二叉树的结构信息,为解决各种实际问题提供了灵活多样的思路和方法。接下来,让我们深入探索二叉树及其遍历的神秘世界,领略它们的独特魅力与强大功能。

一、二叉树基础概念大揭秘

1.1 二叉树的严格定义

二叉树是一种树形数据结构,它的每个结点最多有两个子树,分别称为左子树和右子树 。从递归的角度来看,二叉树要么为空,要么由一个根结点以及两棵互不相交的、分别称为左子树和右子树的二叉树组成。这意味着,二叉树的定义是递归的,每一棵子树本身也都是一棵二叉树。例如,一个简单的二叉树可能只有一个根结点,此时它的左子树和右子树均为空;稍微复杂一些的二叉树,根结点下可能有左子树或右子树,或者两者都有,而这些子树又各自遵循二叉树的定义,可能包含自己的子树。

二叉树与普通树的主要区别在于:普通树的结点可以有任意多个子树,而二叉树每个结点最多只能有两个子树;并且二叉树的子树有明确的左右之分,顺序不可颠倒,而普通树的子树之间没有这种有序性。例如,在普通树中,一个结点的子树顺序交换后,树的逻辑结构不变,但在二叉树中,交换左子树和右子树会得到不同的二叉树 。这种结构上的差异,使得二叉树在算法设计和应用上具有独特的优势和特点,为其在各种领域的广泛应用奠定了基础。

1.2 二叉树的独特特点

二叉树具有一些独特的特点,这些特点使其在数据结构中占据重要地位。首先,每个结点最多有两棵子树,这是二叉树最显著的特征之一。这一限制使得二叉树的结构相对简单且规整,便于进行各种操作和算法设计。例如,在实现二叉树的遍历算法时,由于每个结点最多只有两个分支,我们可以清晰地定义遍历的路径和顺序,从而高效地访问树中的每一个结点。

其次,二叉树的子树有左右之分,次序不能颠倒。这意味着即使一个结点只有一棵子树,也必须明确指出它是左子树还是右子树。这种有序性为二叉树赋予了更多的信息和结构特性。以二叉搜索树为例,它利用了二叉树的有序性,左子树的所有结点值小于根结点值,右子树的所有结点值大于根结点值,通过这种规则可以快速地进行查找、插入和删除操作。如果子树的次序不固定,那么二叉搜索树的这些高效操作将无法实现。为了更直观地理解这一点,我们来看一个简单的图示:

1

/

2 3

在这个二叉树中,结点 1 是根结点,2 是左子树,3 是右子树。如果我们将 2 和 3 的位置交换,得到的二叉树:

1

/

3 2

虽然结点和结构看起来相似,但由于子树的顺序发生了变化,这是两棵不同的二叉树,它们在一些算法和应用中的表现也会截然不同。

1.3 特殊二叉树全解析

满二叉树:满二叉树是一种特殊的二叉树,它的每一层上的结点数都达到了最大。也就是说,除了叶结点外,每一个结点都有左右子叶,并且所有叶子结点都处在最底层。例如,深度为 3 的满二叉树,其结点数为(2^3 - 1 = 7),具体结构如下:

1

/

2 3

/ \ /

4 5 6 7

满二叉树的特点非常明显,它的结构完全对称,具有高度的规律性。在同样深度的二叉树中,满二叉树的结点个数最多,叶子数也最多。这使得满二叉树在一些需要均匀分布数据或进行高效查找的场景中具有优势。例如,在构建某些数据索引结构时,如果数据量能够匹配满二叉树的结点数量,那么可以利用满二叉树的特性快速定位到目标数据。

  1. 完全二叉树:对于深度为 k 的,有 n 个结点的二叉树,当且仅当其每一个结点都与深度为 k 的满二叉树中编号从 1 至 n 的结点一一对应时,称为完全二叉树。简单来说,完全二叉树除了最后一层外,其他各层的结点数都达到了最大个数,并且最后一层的结点都集中在左边。例如:

1

/

2 3

/ \ /

4 5 6

完全二叉树的叶子结点只可能出现在最下面两层,且对任一结点,若其右分支下的子孙的最大层次为 i,则其左分支下的子孙的最大层次必为 i 或 i + 1。在实际应用中,完全二叉树常用于堆数据结构的实现,因为它可以利用数组进行高效的存储和操作。通过数组下标可以快速定位到每个结点的父结点、左孩子和右孩子,大大提高了堆操作的效率。

  1. 平衡二叉树:平衡二叉树又被称为 AVL 树,它是一棵二叉排序树,具有以下性质:它是一棵空树或它的左右两个子树的高度差的绝对值不超过 1,并且左右两个子树都是一棵平衡二叉树。例如:

    4

/

2 6

/ \ /

1 3 5 7

在这个平衡二叉树中,根结点 4 的左子树高度为 2,右子树高度也为 2,高度差为 0,满足平衡二叉树的条件。平衡二叉树的主要作用是保持树的平衡性,避免树在插入或删除结点时出现严重的不平衡,导致树的高度过高,从而影响查找、插入和删除等操作的效率。在大规模数据的存储和查找场景中,平衡二叉树能够保证操作的时间复杂度始终维持在较低的水平,为高效的数据处理提供了有力支持。

二、二叉树遍历方式深度剖析

2.1 前序遍历(NLR)

前序遍历是二叉树遍历的一种重要方式,其遍历顺序为:先访问根结点,再遍历左子树,最后遍历右子树。这种遍历方式就像是一场从根节点出发的深度优先探索,优先访问根节点,然后深入左子树进行探索,直到左子树的最底层,再回溯到根节点,转向右子树继续探索。

为了更清晰地理解前序遍历,我们来看一个具体的例子。假设有如下二叉树:

1

/

2 3

/

4 5

前序遍历的过程如下:

首先访问根结点 1。

然后对根结点 1 的左子树进行前序遍历,即访问结点 2。

接着对结点 2 的左子树进行前序遍历,访问结点 4。

由于结点 4 没有左子树和右子树,回溯到结点 2,对结点 2 的右子树进行前序遍历,访问结点 5。

至此,根结点 1 的左子树遍历完毕,最后对根结点 1 的右子树进行前序遍历,访问结点 3。

所以,该二叉树的前序遍历结果为:1, 2, 4, 5, 3。

接下来,我们通过代码来实现前序遍历。以 Python 语言为例:

javascript 复制代码
class TreeNode:
    def __init__(self, val=0, left=None, right=None):
        self.val = val
        self.left = left
        self.right = right


def preorderTraversal(root):
    result = []
    if root:
        result.append(root.val)
        result.extend(preorderTraversal(root.left))
        result.extend(preorderTraversal(root.right))
    return result

构建示例二叉树

javascript 复制代码
root = TreeNode(1)
root.right = TreeNode(3)
root.left = TreeNode(2)
root.left.left = TreeNode(4)
root.left.right = TreeNode(5)

print(preorderTraversal(root))  # 输出: [1, 2, 4, 5, 3]

在这段代码中,我们首先定义了一个TreeNode类来表示二叉树的节点。然后,preorderTraversal函数实现了前序遍历的逻辑。如果根节点不为空,首先将根节点的值添加到结果列表中,然后递归地对左子树和右子树进行前序遍历,并将遍历结果扩展到结果列表中。

同样地,使用 Java 实现前序遍历的代码如下:

javascript 复制代码
import java.util.ArrayList;
import java.util.List;

class TreeNode {
    int val;
    TreeNode left;
    TreeNode right;

    TreeNode(int x) {
        val = x;
    }
}

class Solution {
    public List<Integer> preorderTraversal(TreeNode root) {
        List<Integer> result = new ArrayList<>();
        if (root != null) {
            result.add(root.val);
            result.addAll(preorderTraversal(root.left));
            result.addAll(preorderTraversal(root.right));
        }
        return result;
    }
}

在 Java 代码中,通过ArrayList来存储遍历结果,利用递归实现前序遍历的步骤,逻辑与 Python 实现类似。

再来看 C 语言实现前序遍历的代码:

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

// 定义二叉树节点结构
struct TreeNode {
    int val;
    struct TreeNode *left;
    struct TreeNode *right;
};

// 前序遍历函数
void preorderTraversal(struct TreeNode* root) {
    if (root != NULL) {
        printf("%d ", root->val);
        preorderTraversal(root->left);
        preorderTraversal(root->right);
    }
}

// 创建新节点函数
struct TreeNode* createNode(int val) {
    struct TreeNode* newNode = (struct TreeNode*)malloc(sizeof(struct TreeNode));
    newNode->val = val;
    newNode->left = NULL;
    newNode->right = NULL;
    return newNode;
}

int main() {
    // 构建示例二叉树
    struct TreeNode* root = createNode(1);
    root->right = createNode(3);
    root->left = createNode(2);
    root->left->left = createNode(4);
    root->left->right = createNode(5);

    preorderTraversal(root);  // 输出: 1 2 4 5 3
    return 0;
}

C 语言通过定义结构体表示二叉树节点,preorderTraversal函数利用递归实现前序遍历,在遍历过程中直接打印节点的值。 前序遍历在许多实际应用中都有重要作用,比如在文件系统的目录遍历中,如果将目录结构看作一棵二叉树,根节点是根目录,子节点是子目录和文件,那么前序遍历可以按照从根目录开始,依次访问子目录和文件的顺序,完整地遍历整个文件系统,方便对文件和目录进行管理和操作。

2.2 中序遍历(LNR)

中序遍历是二叉树遍历的另一种重要方式,其遍历规则为先遍历左子树,再访问根结点,最后遍历右子树。这种遍历顺序就像是在二叉树中进行一次 "左 - 中 - 右" 的有序探索,先深入到左子树的最底层,然后逐步回溯到根节点,最后再探索右子树。中序遍历在二叉搜索树中有着极为重要的应用,因为二叉搜索树的特性使得中序遍历的结果是一个有序序列,这为很多算法和应用提供了便利。

以如下二叉树为例:

5

/

3 7

/ \ /

2 4 6 8

中序遍历的过程如下:

首先对根结点 5 的左子树进行中序遍历。

进入左子树,对结点 3 的左子树进行中序遍历,访问结点 2。

由于结点 2 没有左子树,所以访问结点 2 本身。

接着回溯到结点 3,访问结点 3。

然后对结点 3 的右子树进行中序遍历,访问结点 4。

至此,根结点 5 的左子树遍历完毕,访问根结点 5。

最后对根结点 5 的右子树进行中序遍历。进入右子树,对结点 7 的左子树进行中序遍历,访问结点 6。

访问结点 7,再对结点 7 的右子树进行中序遍历,访问结点 8。

所以,该二叉树的中序遍历结果为:2, 3, 4, 5, 6, 7, 8。可以看到,对于这个二叉搜索树,中序遍历的结果是一个从小到大的有序序列。

下面是 Python 实现中序遍历的代码:

javascript 复制代码
class TreeNode:
    def __init__(self, val=0, left=None, right=None):
        self.val = val
        self.left = left
        self.right = right


def inorderTraversal(root):
    result = []
    if root:
        result.extend(inorderTraversal(root.left))
        result.append(root.val)
        result.extend(inorderTraversal(root.right))
    return result

构建示例二叉树

javascript 复制代码
root = TreeNode(5)
root.right = TreeNode(7)
root.left = TreeNode(3)
root.left.left = TreeNode(2)
root.left.right = TreeNode(4)
root.right.left = TreeNode(6)
root.right.right = TreeNode(8)

print(inorderTraversal(root))  # 输出: [2, 3, 4, 5, 6, 7, 8]

在这段 Python 代码中,inorderTraversal函数通过递归实现中序遍历。首先递归地遍历左子树,将左子树的遍历结果添加到result列表中,然后将根节点的值添加到列表,最后递归地遍历右子树并将结果添加到列表。

使用 Java 实现中序遍历的代码如下:

javascript 复制代码
import java.util.ArrayList;
import java.util.List;

class TreeNode {
    int val;
    TreeNode left;
    TreeNode right;

    TreeNode(int x) {
        val = x;
    }
}

class Solution {
    public List<Integer> inorderTraversal(TreeNode root) {
        List<Integer> result = new ArrayList<>();
        if (root != null) {
            result.addAll(inorderTraversal(root.left));
            result.add(root.val);
            result.addAll(inorderTraversal(root.right));
        }
        return result;
    }
}

Java 代码同样利用递归实现中序遍历,通过ArrayList存储遍历结果,逻辑与 Python 实现一致。

再看 C 语言实现中序遍历的代码:

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

// 定义二叉树节点结构
struct TreeNode {
    int val;
    struct TreeNode *left;
    struct TreeNode *right;
};

// 中序遍历函数
void inorderTraversal(struct TreeNode* root) {
    if (root != NULL) {
        inorderTraversal(root->left);
        printf("%d ", root->val);
        inorderTraversal(root->right);
    }
}

// 创建新节点函数
struct TreeNode* createNode(int val) {
    struct TreeNode* newNode = (struct TreeNode*)malloc(sizeof(struct TreeNode));
    newNode->val = val;
    newNode->left = NULL;
    newNode->right = NULL;
    return newNode;
}

int main() {
    // 构建示例二叉树
    struct TreeNode* root = createNode(5);
    root->right = createNode(7);
    root->left = createNode(3);
    root->left->left = createNode(2);
    root->left->right = createNode(4);
    root->right->left = createNode(6);
    root->right->right = createNode(8);

    inorderTraversal(root);  // 输出: 2 3 4 5 6 7 8
    return 0;
}

C 语言通过结构体定义二叉树节点,inorderTraversal函数利用递归实现中序遍历,在遍历过程中直接打印节点的值。 中序遍历在实际应用中非常广泛,比如在数据库索引中,如果索引结构采用二叉搜索树,那么通过中序遍历可以快速获取有序的数据,方便进行范围查询、排序等操作,大大提高了数据处理的效率。

2.3 后序遍历(LRN)

后序遍历是二叉树遍历的一种方式,其遍历规则为先遍历左子树,再遍历右子树,最后访问根结点。这种遍历顺序就像是在二叉树中进行一次 "自底向上" 的探索,先深入到左子树的最底层,然后逐步回溯到右子树的最底层,最后才访问根节点。后序遍历在一些场景中有着独特的应用,比如在计算目录内文件大小的场景中,如果将目录结构看作一棵二叉树,文件是叶子节点,目录是分支节点,通过后序遍历可以先计算出每个子目录和文件的大小,然后再汇总计算出整个目录的大小。

以如下二叉树为例:

1

/

2 3

/ \ /

4 5 6 7

后序遍历的过程如下:

首先对根结点 1 的左子树进行后序遍历。

进入左子树,对结点 2 的左子树进行后序遍历,访问结点 4。

由于结点 4 没有左子树和右子树,回溯到结点 2,对结点 2 的右子树进行后序遍历,访问结点 5。

访问结点 2,至此根结点 1 的左子树遍历完毕。

接着对根结点 1 的右子树进行后序遍历。进入右子树,对结点 3 的左子树进行后序遍历,访问结点 6。

对结点 3 的右子树进行后序遍历,访问结点 7。

最后访问根结点 1。

所以,该二叉树的后序遍历结果为:4, 5, 2, 6, 7, 3, 1。

下面是 Python 实现后序遍历的代码:

javascript 复制代码
class TreeNode:
    def __init__(self, val=0, left=None, right=None):
        self.val = val
        self.left = left
        self.right = right


def postorderTraversal(root):
    result = []
    if root:
        result.extend(postorderTraversal(root.left))
        result.extend(postorderTraversal(root.right))
        result.append(root.val)
    return result

构建示例二叉树

javascript 复制代码
root = TreeNode(1)
root.right = TreeNode(3)
root.left = TreeNode(2)
root.left.left = TreeNode(4)
root.left.right = TreeNode(5)
root.right.left = TreeNode(6)
root.right.right = TreeNode(7)

print(postorderTraversal(root))  # 输出: [4, 5, 2, 6, 7, 3, 1]

在这段 Python 代码中,postorderTraversal函数通过递归实现后序遍历。首先递归地遍历左子树,将左子树的遍历结果添加到result列表中,然后递归地遍历右子树并将结果添加到列表,最后将根节点的值添加到列表。

使用 Java 实现后序遍历的代码如下:

javascript 复制代码
import java.util.ArrayList;
import java.util.List;

class TreeNode {
    int val;
    TreeNode left;
    TreeNode right;

    TreeNode(int x) {
        val = x;
    }
}

class Solution {
    public List<Integer> postorderTraversal(TreeNode root) {
        List<Integer> result = new ArrayList<>();
        if (root != null) {
            result.addAll(postorderTraversal(root.left));
            result.addAll(postorderTraversal(root.right));
            result.add(root.val);
        }
        return result;
    }

}

Java 代码同样利用递归实现后序遍历,通过ArrayList存储遍历结果,逻辑与 Python 实现一致。

再看 C 语言实现后序遍历的代码:

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

// 定义二叉树节点结构
struct TreeNode {
    int val;
    struct TreeNode *left;
    struct TreeNode *right;
};

// 后序遍历函数
void postorderTraversal(struct TreeNode* root) {
    if (root != NULL) {
        postorderTraversal(root.left);
        postorderTraversal(root.right);
        printf("%d ", root->val);
    }
}

// 创建新节点函数
struct TreeNode* createNode(int val) {
    struct TreeNode* newNode = (struct TreeNode*)malloc(sizeof(struct TreeNode));
    newNode->val = val;
    newNode->left = NULL;
    newNode->right = NULL;
    return newNode;
}

int main() {
    // 构建示例二叉树
    struct TreeNode* root = createNode(1);
    root->right = createNode(3);
    root->left = createNode(2);
    root->left->left = createNode(4);
    root->left->right = createNode(5);
    root->right->left = createNode(6);
    root->right->right = createNode(7);

    postorderTraversal(root);  // 输出: 4 5 2 6 7 3 1
    return 0;
}

C 语言通过结构体定义二叉树节点,postorderTraversal函数利用递归实现后序遍历,在遍历过程中直接打印节点的值。后序遍历在实际应用中还有很多场景,比如在表达式求值中,如果将表达式表示为一棵二叉树,操作数是叶子节点,运算符是分支节点,通过后序遍历可以按照正确的顺序计算表达式的值,这在编译器的表达式解析和计算中有着重要的应用。

2.4 层序遍历

层序遍历是二叉树遍历的一种方式,它从根结点开始,自上而下、自左至右逐层访问二叉树的结点。这种遍历方式就像是在二叉树中进行一次 "逐层扫描",从根节点所在的第一层开始,依次访问每一层的节点,直到所有节点都被访问完。层序遍历通常利用队列来实现,队列的先进先出特性使得它能够很好地满足逐层访问的需求。

以如下二叉树为例:

1

/

2 3

/ \ /

4 5 6 7

层序遍历的过程如下:

首先将根结点 1 入队列。

从队列中取出结点 1 并访问它,然后将结点 1 的左子结点 2 和右子结点 3 入队列。

从队列中取出结点 2 并访问它,然后将结点 2 的左子结点 4 和右子结点 5 入队列。

从队列中取出结点 3 并访问它,然后将结点 3 的左子结点 6 和右子结点 7 入队列。

从队列中取出结点 4 并访问它,由于结点 4 没有子结点,继续下一个。

从队列中取出结点 5 并访问它,由于结点 5 没有子结点,继续下一个。

从队列中取出结点 6 并访问它,由于结点 6 没有子结点,继续下一个。

从队列中

三、二叉树遍历的代码实战

3.1 基于递归的遍历实现

前序遍历递归实现:前序遍历的递归实现遵循 "根 - 左 - 右" 的顺序。以 Python 代码为例:

javascript 复制代码
class TreeNode:
    def __init__(self, val=0, left=None, right=None):
        self.val = val
        self.left = left
        self.right = right


def preorderTraversal(root):
    result = []
    if root:
        result.append(root.val)
        result.extend(preorderTraversal(root.left))
        result.extend(preorderTraversal(root.right))
    return result

构建示例二叉树

javascript 复制代码
root = TreeNode(1)
root.right = TreeNode(3)
root.left = TreeNode(2)
root.left.left = TreeNode(4)
root.left.right = TreeNode(5)

print(preorderTraversal(root))  # 输出: [1, 2, 4, 5, 3]

在这段代码中,首先定义了TreeNode类来表示二叉树的节点。preorderTraversal函数实现了前序遍历的逻辑。如果根节点不为空,首先将根节点的值添加到结果列表result中,然后递归地调用preorderTraversal函数对左子树和右子树进行前序遍历,并将遍历结果扩展到result列表中。

递归的原理是函数自身调用自身,每次调用时传入不同的参数,将大问题分解为小问题来解决。在二叉树前序遍历中,对于每一个节点,都将其看作一个独立的子问题,先处理根节点,然后递归地处理左子树和右子树。递归的执行过程就像一个层层深入的过程,先深入到最左边的节点,然后逐步回溯,处理右子树。

通过单步调试可以更直观地看到递归调用栈的变化。假设我们对上述示例二叉树进行单步调试,当执行到preorderTraversal(root)时,首先将根节点 1 的值添加到result中,然后进入preorderTraversal(root.left),此时preorderTraversal函数再次被调用,这个新的调用被压入调用栈中,继续处理左子树的节点 2。当处理完节点 2 的左子树节点 4 后,由于节点 4 没有子节点,开始回溯,此时preorderTraversal(root.left)这个调用从调用栈中弹出,继续处理节点 2 的右子树节点 5。处理完节点 5 后,再次回溯,preorderTraversal(root.left)的调用完全结束,接着处理preorderTraversal(root.right),将节点 3 的值添加到result中,最终完成整个前序遍历,此时调用栈为空。

中序遍历递归实现:中序遍历递归实现遵循 "左 - 根 - 右" 的顺序。Python 代码如下:

javascript 复制代码
class TreeNode:
    def __init__(self, val=0, left=None, right=None):
        self.val = val
        self.left = left
        self.right = right


def inorderTraversal(root):
    result = []
    if root:
        result.extend(inorderTraversal(root.left))
        result.append(root.val)
        result.extend(inorderTraversal(root.right))
    return result

构建示例二叉树

javascript 复制代码
root = TreeNode(1)
root.right = TreeNode(3)
root.left = TreeNode(2)
root.left.left = TreeNode(4)
root.left.right = TreeNode(5)

print(inorderTraversal(root))  # 输出: [4, 2, 5, 1, 3]

在这个代码中,inorderTraversal函数首先递归地遍历左子树,将左子树的遍历结果添加到result列表中,然后将根节点的值添加到列表,最后递归地遍历右子树并将结果添加到列表。递归原理与前序遍历类似,都是将问题分解为子问题,通过函数自身调用来解决。在执行过程中,同样是先深入到最左边的节点,然后逐步回溯,处理根节点和右子树。通过单步调试,可以清晰地看到调用栈的变化,随着递归的深入和回溯,调用栈中的函数调用不断进出,最终完成中序遍历。

后序遍历递归实现:后序遍历递归实现遵循 "左 - 右 - 根" 的顺序。Python 代码如下:

javascript 复制代码
class TreeNode:
    def __init__(self, val=0, left=None, right=None):
        self.val = val
        self.left = left
        self.right = right


def postorderTraversal(root):
    result = []
    if root:
        result.extend(postorderTraversal(root.left))
        result.extend(postorderTraversal(root.right))
        result.append(root.val)
    return result

构建示例二叉树

javascript 复制代码
root = TreeNode(1)
root.right = TreeNode(3)
root.left = TreeNode(2)
root.left.left = TreeNode(4)
root.left.right = TreeNode(5)

print(postorderTraversal(root))  # 输出: [4, 5, 2, 3, 1]

postorderTraversal函数通过递归先遍历左子树,再遍历右子树,最后将根节点的值添加到结果列表。递归过程中,调用栈随着递归的深入而不断增加新的函数调用,每一层递归处理当前节点的子树,直到最底层的叶子节点。当处理完叶子节点后,开始回溯,依次处理右子树和根节点,调用栈中的函数调用也随之逐步弹出,最终完成后序遍历。通过单步调试,可以详细观察到每一步递归调用栈的变化,以及节点值是如何按照 "左 - 右 - 根" 的顺序被添加到结果列表中的。

3.2 非递归的遍历实现技巧

使用栈实现前序遍历:使用栈实现前序遍历的非递归方法,核心思想是模拟递归的调用栈。以 Python 代码为例:

javascript 复制代码
class TreeNode:
    def __init__(self, val=0, left=None, right=None):
        self.val = val
        self.left = left
        self.right = right


def preorderTraversal(root):
    result = []
    stack = []
    if root:
        stack.append(root)
    while stack:
        node = stack.pop()
        result.append(node.val)
        if node.right:
            stack.append(node.right)
        if node.left:
            stack.append(node.left)
    return result

构建示例二叉树

javascript 复制代码
root = TreeNode(1)
root.right = TreeNode(3)
root.left = TreeNode(2)
root.left.left = TreeNode(4)
root.left.right = TreeNode(5)

print(preorderTraversal(root))  # 输出: [1, 2, 4, 5, 3]

在这段代码中,首先将根节点入栈。然后在循环中,不断从栈中弹出节点,将节点的值添加到结果列表中。由于栈是后进先出的结构,所以先将右子节点入栈,再将左子节点入栈,这样就可以保证先处理左子树,再处理右子树,符合前序遍历 "根 - 左 - 右" 的顺序。

使用栈实现中序遍历:使用栈实现中序遍历的非递归方法,需要先将当前节点的所有左子节点入栈,然后再处理栈顶节点及其右子树。Python 代码如下:

javascript 复制代码
class TreeNode:
    def __init__(self, val=0, left=None, right=None):
        self.val = val
        self.left = left
        self.right = right


def inorderTraversal(root):
    result = []
    stack = []
    current = root
    while current or stack:
        while current:
            stack.append(current)
            current = current.left
        current = stack.pop()
        result.append(current.val)
        current = current.right
    return result

构建示例二叉树

javascript 复制代码
root = TreeNode(1)
root.right = TreeNode(3)
root.left = TreeNode(2)
root.left.left = TreeNode(4)
root.left.right = TreeNode(5)

print(inorderTraversal(root))  # 输出: [4, 2, 5, 1, 3]

代码中,首先将当前节点的所有左子节点入栈,当左子节点为空时,弹出栈顶节点,将其值添加到结果列表中,然后将当前节点指向右子节点,继续上述过程,直到栈为空且当前节点也为空,从而实现中序遍历 "左 - 根 - 右" 的顺序。

使用栈实现后序遍历:使用栈实现后序遍历的非递归方法相对复杂一些,有多种实现方式。一种常见的方法是使用两个栈,第一个栈用于存储节点,第二个栈用于存储后序遍历的结果。Python 代码如下:

javascript 复制代码
class TreeNode:
    def __init__(self, val=0, left=None, right=None):
        self.val = val
        self.left = left
        self.right = right


def postorderTraversal(root):
    result = []
    stack1 = []
    stack2 = []
    if root:
        stack1.append(root)
    while stack1:
        node = stack1.pop()
        stack2.append(node)
        if node.left:
            stack1.append(node.left)
        if node.right:
            stack1.append(node.right)
    while stack2:
        node = stack2.pop()
        result.append(node.val)
    return result

构建示例二叉树

javascript 复制代码
root = TreeNode(1)
root.right = TreeNode(3)
root.left = TreeNode(2)
root.left.left = TreeNode(4)
root.left.right = TreeNode(5)

print(postorderTraversal(root))  # 输出: [4, 5, 2, 3, 1]

在这个实现中,首先将根节点入栈stack1,然后不断将当前节点的左子节点和右子节点入栈stack1,在入栈时,先入右子节点,再入左子节点。接着从stack1中弹出节点并压入stack2,最后从stack2中依次弹出节点,将节点的值添加到结果列表中,这样就得到了后序遍历的结果。也可以使用一个栈来实现后序遍历,通过记录上一个访问的节点来判断是从左子树返回还是从右子树返回,从而确定是否访问根节点。

利用队列实现层序遍历:利用队列实现层序遍历的非递归方法,利用队列的先进先出特性,依次将每一层的节点入队,然后出队并处理其左右子节点。Python 代码如下:

javascript 复制代码
class TreeNode:
    def __init__(self, val=0, left=None, right=None):
        self.val = val
        self.left = left
        self.right = right


def levelOrderTraversal(root):
    result = []
    queue = []
    if root:
        queue.append(root)
    while queue:
        level = []
        size = len(queue)
        for _ in range(size):
            node = queue.pop(0)
            level.append(node.val)
            if node.left:
                queue.append(node.left)
            if node.right:
                queue.append(node.right)
        result.append(level)
    return result
相关推荐
shylyly_32 分钟前
list的模拟实现
数据结构·c++·链表·迭代器·list·list的模拟实现
ianozo1 小时前
数据结构--【栈与队列】笔记
数据结构·笔记
路飞雪吖~1 小时前
数据结构 && 常见的排序算法
数据结构·算法·排序算法
手握风云-1 小时前
Java数据结构第二十一期:解构排序算法的艺术与科学(三)
数据结构·算法·排序算法
wxr的理想之路2 小时前
list链表的使用
c语言·数据结构·链表·list
曦月逸霜3 小时前
第五次CCF-CSP认证(含C++源码)
数据结构·c++·算法·ccf-csp
Illusionna.3 小时前
KMP 算法的 C 语言实现
c语言·数据结构·算法
<但凡.4 小时前
题海拾贝:P9241 [蓝桥杯 2023 省 B] 飞机降落
数据结构·算法·蓝桥杯
Spring小子4 小时前
蓝桥杯[每日两题] 真题:好数 神奇闹钟 (java版)
java·数据结构·算法·蓝桥杯
记得早睡~4 小时前
leetcode654-最大二叉树
javascript·数据结构·算法·leetcode