二叉树展开为链表的算法解析与实现

2025/3/12
本文详细介绍了如何将二叉树按照前序遍历的顺序展开为一个单链表,包括递归、迭代和原地修改三种解决方案,并提供了相应的代码示例和复杂度分析。
二叉树结构图,前序遍历过程图,链表结构图

将二叉树展开为链表是一个常见的算法问题,通常要求将二叉树按照前序遍历的顺序展开为一个单链表。展开后的链表应该使用二叉树的右指针来连接节点,左指针应该全部置为 null

问题描述

给定一个二叉树,将其展开为一个单链表。展开后的链表应该使用二叉树的右指针来连接节点,左指针应该全部置为 null

示例

输入:
    1
   / \
  2   5
 / \   \
3   4   6

输出:
1
 \
  2
   \
    3
     \
      4
       \
        5
         \
          6

解决方案

我们可以使用递归或迭代的方法来解决这个问题。以下是两种常见的解决方案:

方法一:递归

  1. 前序遍历:首先对二叉树进行前序遍历,将节点按顺序存储在一个列表中。
  2. 重构链表:遍历列表,将每个节点的右指针指向下一个节点,左指针置为 null
function flatten(root) {
    if (!root) return;

    // 前序遍历,将节点存储在列表中
    const nodes = [];
    preOrder(root, nodes);

    // 重构链表
    for (let i = 0; i < nodes.length - 1; i++) {
        nodes[i].left = null;
        nodes[i].right = nodes[i + 1];
    }
}

function preOrder(node, nodes) {
    if (!node) return;
    nodes.push(node);
    preOrder(node.left, nodes);
    preOrder(node.right, nodes);
}

方法二:迭代(使用栈)

  1. 使用栈模拟前序遍历:通过栈来模拟前序遍历的过程。
  2. 重构链表:在遍历过程中,将当前节点的右指针指向栈顶节点,左指针置为 null
function flatten(root) {
    if (!root) return;

    const stack = [root];
    let prev = null;

    while (stack.length) {
        const curr = stack.pop();

        if (prev) {
            prev.left = null;
            prev.right = curr;
        }

        if (curr.right) stack.push(curr.right);
        if (curr.left) stack.push(curr.left);

        prev = curr;
    }
}

方法三:原地修改(O(1) 空间复杂度)

  1. 找到左子树的最右节点:对于每个节点,找到其左子树的最右节点。
  2. 将右子树接到最右节点:将当前节点的右子树接到左子树的最右节点上。
  3. 将左子树移到右子树:将左子树移到右子树的位置,左指针置为 null
function flatten(root) {
    let curr = root;
    while (curr) {
        if (curr.left) {
            let prev = curr.left;
            while (prev.right) {
                prev = prev.right;
            }
            prev.right = curr.right;
            curr.right = curr.left;
            curr.left = null;
        }
        curr = curr.right;
    }
}

复杂度分析

  • 时间复杂度:O(N),其中 N 是二叉树的节点数。每个节点被访问一次。
  • 空间复杂度
    • 方法一和方法二:O(N),需要存储所有节点。
    • 方法三:O(1),只使用了常数级别的额外空间。

总结

将二叉树展开为链表可以通过多种方法实现,具体选择哪种方法取决于对空间复杂度的要求。如果允许使用额外的空间,递归和迭代的方法都比较直观。如果要求原地修改,方法三是最优的选择。

标签:算法
上次更新:

相关文章

npx完全指南:前端开发必备工具详解 | 20年架构师深度解析

本文由20年前端架构师深入解析npx工具,涵盖其核心功能、优势、高级用法、最佳实践及与npm/yarn的区别比较,帮助开发者掌握这一现代前端开发利器。

·前端开发

<处理关联数据的最佳实践:Article 与 Tags 的关系 | 开发指南>

<本文详细介绍了在开发中处理关联数据(如 Article 和 Tags 的多对多关系)的最佳实践,包括拆分业务逻辑、使用事务保证数据一致性、合理设计关联表结构、批量操作、幂等性和乐观锁等关键要点,并提供了基于 mysql2 和 Sequelize 的代码示例。>

·后端开发

Astro 静态站点生成器:构建高性能网站的最佳选择

Astro 是一个专注于构建快速、轻量级网站的静态站点生成器,支持多种前端框架,采用岛屿架构减少 JavaScript 加载,提升性能。

·前端开发

MySQL外键约束详解:维护数据一致性与完整性

本文详细介绍了MySQL中的外键约束(Foreign Key Constraint),包括其基本概念、创建方法、作用、级联操作、限制、修改与删除方法、查看方式以及最佳实践。通过合理使用外键约束,可以有效管理数据库中的数据关系,确保数据的准确性和可靠性。

·后端开发

MySQL JSON数据类型支持与使用指南 | 详细解析与示例

本文详细解析了MySQL从5.7版本开始支持的JSON数据类型,包括版本支持、创建JSON字段、插入与查询JSON数据、修改JSON数据、生成JSON、索引优化、性能与应用场景、注意事项及示例全流程。

·后端开发

SQL JOIN、LEFT JOIN 和 RIGHT JOIN 的区别与应用场景详解

本文详细介绍了 SQL 中 JOIN、LEFT JOIN 和 RIGHT JOIN 的区别,包括它们的作用、语法、示例以及实际应用场景,帮助读者更好地理解和使用这些连接方式。

·后端开发

Weex 跨平台移动开发框架:核心特性与使用指南

Weex 是由阿里巴巴开源的跨平台移动开发框架,支持使用 Vue.js 或 Rax 构建高性能的 iOS、Android 和 Web 应用。本文详细解析了 Weex 的核心特性、架构、工作流程、组件和模块、开发工具、优缺点、应用场景及未来发展。

·前端开发

ECharts 与 DataV 数据可视化工具对比分析 | 选择指南

本文详细对比了 ECharts 和 DataV 两个常用的数据可视化工具,包括它们的设计目标、优缺点、使用场景和技术栈,帮助读者根据具体需求选择合适的工具。

·前端开发

前端部署后通知用户刷新页面的常见方案 | 单页应用更新提示

本文介绍了在前端部署后通知用户刷新页面的几种常见方案,包括WebSocket实时通知、轮询检查版本、Service Worker版本控制、版本号对比、自动刷新、使用框架内置功能以及第三方库。每种方案的优缺点和示例代码均有详细说明。

·前端开发

TypeScript 映射类型常见问题与解决方案 | 提升代码维护性

本文探讨了在使用 TypeScript 时,映射类型的不当使用可能导致的问题,如代码难以维护、类型推断不准确或性能问题,并提供了相应的解决方案和最佳实践。

·编程语言