0

0

二叉树原地扁平化为双向链表结构:原理与优化实践

聖光之護

聖光之護

发布时间:2025-12-07 18:37:24

|

645人浏览过

|

来源于php中文网

原创

二叉树原地扁平化为双向链表结构:原理与优化实践

本教程详细探讨了如何将二叉树原地(in-place)扁平化为一种类似双向链表的结构。文章首先阐述了扁平化的核心目标和节点连接顺序,接着分析了递归实现中常见的指针初始化误区,特别是避免创建不必要的循环引用。最后,提供了一种经过优化的递归解决方案,通过直接更新父节点指针并返回子树的边界节点,实现了更简洁高效的扁平化操作,并附带了完整的代码示例和注意事项。

1. 二叉树扁平化概述

二叉树扁平化是将一个二叉树结构转换为一种线性结构,使其节点按照特定的遍历顺序(通常是左-根-右,即中序遍历的逻辑顺序)排列,形成一个类似双向链表的结构。这里的“类似双向链表”意味着树节点的 left 和 right 指针将分别充当链表的 prev 和 next 指针。扁平化操作通常要求是“原地”(in-place)进行,即直接修改原树节点的指针,不创建新的节点,并且最终返回扁平化后链表的“最左侧”节点(即原树中序遍历的第一个节点)。

扁平化目标:

  • 将二叉树转换为一个线性结构。
  • 节点顺序遵循原树的左-根-右(中序)逻辑顺序。
  • left 指针指向前一个节点,right 指针指向后一个节点。
  • 操作必须是原地修改。

2. 递归扁平化策略

实现二叉树原地扁平化的一种有效方法是采用递归策略。核心思想是为每个节点设计一个辅助函数,该函数负责扁平化当前节点的左右子树,然后将这些扁平化的子树与当前节点连接起来,最终返回当前子树的扁平化结果的“最左侧”和“最右侧”节点。

假设我们的辅助函数 helper(node) 能够返回一个元组 (leftmost, rightmost),分别代表以 node 为根的子树扁平化后的链表的头节点和尾节点。

2.1 初始实现分析与常见误区

考虑以下一种递归辅助函数的初始实现思路:

class BinaryTree:
    def __init__(self, value, left=None, right=None):
        self.value = value
        self.left = left
        self.right = right

def flattenBinaryTree(root):
    if not root:
        return None
    leftmost, _ = helper(root)
    return leftmost

def helper(node):
    if node is None:
        return None, None

    # 初始默认值
    leftmost_of_current_subtree = node
    rightmost_of_current_subtree = node

    # 用于存储左右子树扁平化后的边界节点
    leftmost_of_right_child_subtree = None
    rightmost_of_left_child_subtree = None

    # 处理左子树
    if node.left:
        leftmost_of_current_subtree, rightmost_of_left_child_subtree = helper(node.left)

    # 处理右子树
    if node.right:
        leftmost_of_right_child_subtree, rightmost_of_current_subtree = helper(node.right)

    # 连接当前节点与左右子树
    # 将当前节点的右指针指向其右子树扁平化后的最左节点
    node.right = leftmost_of_right_child_subtree
    if leftmost_of_right_child_subtree:
        leftmost_of_right_child_subtree.left = node # 右子树的最左节点指回当前节点

    # 将当前节点的左指针指向其左子树扁平化后的最右节点
    node.left = rightmost_of_left_child_subtree
    if rightmost_of_left_child_subtree:
        rightmost_of_left_child_subtree.right = node # 左子树的最右节点指回当前节点

    return leftmost_of_current_subtree, rightmost_of_current_subtree

误区分析:为什么 leftmost_of_right_child_subtree 和 rightmost_of_left_child_subtree 不能默认初始化为 node?

在上述代码中,leftmost_of_right_child_subtree 和 rightmost_of_left_child_subtree 被初始化为 None。如果尝试将它们也初始化为 node,例如:

GentleAI
GentleAI

GentleAI是一个高效的AI工作平台,为普通人提供智能计算、简单易用的界面和专业技术支持。让人工智能服务每一个人。

下载
leftmost_of_current_subtree = node
rightmost_of_current_subtree = node
leftmost_of_right_child_subtree = node # 错误尝试
rightmost_of_left_child_subtree = node # 错误尝试

这将导致问题。考虑一个只有左子树而没有右子树的节点(例如,node.right 为 None)。在这种情况下,if node.right: 条件不会被满足,leftmost_of_right_child_subtree 将保持其默认值 node。

随后,执行到连接逻辑时:

node.right = leftmost_of_right_child_subtree # 此时,node.right 将被赋值为 node

这将导致 node.right 指向 node 自身,形成一个循环引用。这不仅破坏了链表结构,也与我们期望的 node.right 为 None 的情况(当没有右子树时)相悖。

因此,当一个节点没有某个子树时,其对应的连接指针(例如 node.right 或 node.left)应该指向 None。将 leftmost_of_right_child_subtree 和 rightmost_of_left_child_subtree 初始化为 None,并在子树存在时才更新它们,可以有效避免这种不必要的自循环或错误连接。

2.2 优化后的递归实现

上述实现虽然能够工作,但存在一些冗余和可以简化的地方。例如,叶子节点不需要特殊处理,并且可以通过更直接的方式进行指针赋值。以下是经过优化的 helper 函数:

def helper_optimized(node):
    # 如果节点为空,则没有扁平化结果,返回 None, None
    if node is None:
        return None, None

    # 初始化当前子树的扁平化后的最左和最右节点为当前节点本身
    # 这是在没有子节点情况下的默认值
    leftmost_in_subtree = node
    rightmost_in_subtree = node

    # 递归处理左子树
    if node.left:
        # 扁平化左子树,获取其最左和最右节点
        # leftmost_of_left_child: 左子树扁平化后的最左节点
        # rightmost_of_left_child: 左子树扁平化后的最右节点
        leftmost_of_left_child, rightmost_of_left_child = helper_optimized(node.left)

        # 更新当前子树的最左节点为左子树的最左节点
        leftmost_in_subtree = leftmost_of_left_child

        # 将左子树扁平化后的最右节点连接到当前节点
        rightmost_of_left_child.right = node
        node.left = rightmost_of_left_child # 当前节点的left指针指向左子树的rightmost

    # 递归处理右子树
    if node.right:
        # 扁平化右子树,获取其最左和最右节点
        # leftmost_of_right_child: 右子树扁平化后的最左节点
        # rightmost_of_right_child: 右子树扁平化后的最右节点
        leftmost_of_right_child, rightmost_of_right_child = helper_optimized(node.right)

        # 更新当前子树的最右节点为右子树的最右节点
        rightmost_in_subtree = rightmost_of_right_child

        # 将当前节点连接到右子树扁平化后的最左节点
        leftmost_of_right_child.left = node
        node.right = leftmost_of_right_child # 当前节点的right指针指向右子树的leftmost

    # 返回当前子树扁平化后的最左和最右节点
    return leftmost_in_subtree, rightmost_in_subtree

优化版 helper_optimized 的核心逻辑:

  1. 基准情况: 如果 node 为 None,直接返回 (None, None)。
  2. 初始化边界: leftmost_in_subtree 和 rightmost_in_subtree 默认设置为当前 node。这意味着如果 node 是一个叶子节点,它就是其自身扁平化后的最左和最右节点。
  3. 处理左子树:
    • 如果 node.left 存在,递归调用 helper_optimized(node.left) 获取左子树扁平化后的最左节点 leftmost_of_left_child 和最右节点 rightmost_of_left_child。
    • 更新当前子树的整体最左节点:leftmost_in_subtree = leftmost_of_left_child。
    • 建立双向链接:rightmost_of_left_child.right = node (左子树的最右节点指向当前节点),node.left = rightmost_of_left_child (当前节点指向左子树的最右节点)。
  4. 处理右子树:
    • 如果 node.right 存在,递归调用 helper_optimized(node.right) 获取右子树扁平化后的最左节点 leftmost_of_right_child 和最右节点 rightmost_of_right_child。
    • 更新当前子树的整体最右节点:rightmost_in_subtree = rightmost_of_right_child。
    • 建立双向链接:leftmost_of_right_child.left = node (右子树的最左节点指回当前节点),node.right = leftmost_of_right_child (当前节点指向右子树的最左节点)。
  5. 返回结果: 返回 (leftmost_in_subtree, rightmost_in_subtree)。

这种优化后的方法避免了中间变量的过多使用,通过直接更新 node.left 和 node.right 指针,并巧妙地利用递归返回的边界节点来构建双向链表。

3. 完整的代码示例

class BinaryTree:
    def __init__(self, value, left=None, right=None):
        self.value = value
        self.left = left
        self.right = right

    # 为了测试方便,添加一个插入方法
    def insert(self, values, i=0):
        if i >= len(values):
            return self
        queue = [self]
        while len(queue) > 0:
            current = queue.pop(0)
            if current.left is None:
                current.left = BinaryTree(values[i])
                break
            queue.append(current.left)
            if current.right is None:
                current.right = BinaryTree(values[i])
                break
            queue.append(current.right)
        self.insert(values, i + 1)
        return self

    # 为了测试方便,添加一个从左到右再到左的遍历方法
    def leftToRightToLeft(self):
        nodes = []
        current = self
        # 从最左节点开始向右遍历
        while current.right is not None:
            nodes.append(current.value)
            current = current.right
        nodes.append(current.value) # 添加最右节点
        # 从最右节点开始向左遍历
        while current is not None:
            nodes.append(current.value)
            current = current.left
        return nodes

def flattenBinaryTree(root):
    """
    将二叉树原地扁平化为双向链表结构,并返回链表的头节点。
    """
    if not root:
        return None
    leftmost_node, _ = helper_optimized(root)
    return leftmost_node

def helper_optimized(node):
    """
    递归辅助函数,扁平化以当前节点为根的子树,并返回扁平化后链表的头尾节点。
    返回: (leftmost_in_subtree, rightmost_in_subtree)
    """
    if node is None:
        return None, None

    leftmost_in_subtree = node
    rightmost_in_subtree = node

    # 处理左子树
    if node.left:
        # 递归扁平化左子树
        leftmost_of_left_child, rightmost_of_left_child = helper_optimized(node.left)

        # 更新当前子树的最左节点
        leftmost_in_subtree = leftmost_of_left_child

        # 将左子树扁平化后的最右节点连接到当前节点
        rightmost_of_left_child.right = node
        node.left = rightmost_of_left_child 

    # 处理右子树
    if node.right:
        # 递归扁平化右子树
        leftmost_of_right_child, rightmost_of_right_child = helper_optimized(node.right)

        # 更新当前子树的最右节点
        rightmost_in_subtree = rightmost_of_right_child

        # 将当前节点连接到右子树扁平化后的最左节点
        leftmost_of_right_child.left = node
        node.right = leftmost_of_right_child

    return leftmost_in_subtree, rightmost_in_subtree

# --- 测试用例 ---
if __name__ == "__main__":
    # 构建测试树:
    #         1
    #        / \
    #       2   3
    #      / \   \
    #     4   5   6
    #        / \
    #       7   8
    root = BinaryTree(1).insert([2, 3, 4, 5, 6])
    root.left.right.left = BinaryTree(7)
    root.left.right.right = BinaryTree(8)

    print("原始树结构 (中序遍历):")
    # 模拟中序遍历来验证节点顺序
    def inorder_traversal(node, result):
        if node:
            inorder_traversal(node.left, result)
            result.append(node.value)
            inorder_traversal(node.right, result)

    original_inorder = []
    inorder_traversal(root, original_inorder)
    print(original_inorder) # 预期: [4, 2, 7, 5, 8, 1, 6, 3]

    print("\n扁平化二叉树...")
    leftMostNode = flattenBinaryTree(root)

    print("扁平化后链表 (从左到右再到左遍历):")
    # 预期结果:[4, 2, 7, 5, 8, 1, 6, 3, 3, 6, 1, 8, 5, 7, 2, 4]
    # 第一次遍历 [4, 2, 7, 5, 8, 1, 6, 3]
    # 第二次遍历 [3, 6, 1, 8, 5, 7, 2, 4]
    print(leftMostNode.leftToRightToLeft()) 

    # 验证扁平化后的顺序
    current = leftMostNode
    flattened_values = []
    while current:
        flattened_values.append(current.value)
        current = current.right
    print("扁平化后从左到右顺序:", flattened_values) # 预期: [4, 2, 7, 5, 8, 1, 6, 3]

    # 验证双向链接
    if flattened_values:
        print("验证双向链接 (从右到左):")
        current = flattened_values[-1] # 从最右节点开始
        reversed_flattened_values = []
        while current:
            reversed_flattened_values.append(current.value)
            current = current.left
        print("扁平化后从右到左顺序:", reversed_flattened_values) # 预期: [3, 6, 1, 8, 5, 7, 2, 4]

4. 注意事项与总结

  • 原地修改:

热门AI工具

更多
DeepSeek
DeepSeek

幻方量化公司旗下的开源大模型平台

豆包大模型
豆包大模型

字节跳动自主研发的一系列大型语言模型

WorkBuddy
WorkBuddy

腾讯云推出的AI原生桌面智能体工作台

腾讯元宝
腾讯元宝

腾讯混元平台推出的AI助手

文心一言
文心一言

文心一言是百度开发的AI聊天机器人,通过对话可以生成各种形式的内容。

讯飞写作
讯飞写作

基于讯飞星火大模型的AI写作工具,可以快速生成新闻稿件、品宣文案、工作总结、心得体会等各种文文稿

即梦AI
即梦AI

一站式AI创作平台,免费AI图片和视频生成。

ChatGPT
ChatGPT

最最强大的AI聊天机器人程序,ChatGPT不单是聊天机器人,还能进行撰写邮件、视频脚本、文案、翻译、代码等任务。

相关专题

更多
if什么意思
if什么意思

if的意思是“如果”的条件。它是一个用于引导条件语句的关键词,用于根据特定条件的真假情况来执行不同的代码块。本专题提供if什么意思的相关文章,供大家免费阅读。

847

2023.08.22

TypeScript类型系统进阶与大型前端项目实践
TypeScript类型系统进阶与大型前端项目实践

本专题围绕 TypeScript 在大型前端项目中的应用展开,深入讲解类型系统设计与工程化开发方法。内容包括泛型与高级类型、类型推断机制、声明文件编写、模块化结构设计以及代码规范管理。通过真实项目案例分析,帮助开发者构建类型安全、结构清晰、易维护的前端工程体系,提高团队协作效率与代码质量。

25

2026.03.13

Python异步编程与Asyncio高并发应用实践
Python异步编程与Asyncio高并发应用实践

本专题围绕 Python 异步编程模型展开,深入讲解 Asyncio 框架的核心原理与应用实践。内容包括事件循环机制、协程任务调度、异步 IO 处理以及并发任务管理策略。通过构建高并发网络请求与异步数据处理案例,帮助开发者掌握 Python 在高并发场景中的高效开发方法,并提升系统资源利用率与整体运行性能。

44

2026.03.12

C# ASP.NET Core微服务架构与API网关实践
C# ASP.NET Core微服务架构与API网关实践

本专题围绕 C# 在现代后端架构中的微服务实践展开,系统讲解基于 ASP.NET Core 构建可扩展服务体系的核心方法。内容涵盖服务拆分策略、RESTful API 设计、服务间通信、API 网关统一入口管理以及服务治理机制。通过真实项目案例,帮助开发者掌握构建高可用微服务系统的关键技术,提高系统的可扩展性与维护效率。

177

2026.03.11

Go高并发任务调度与Goroutine池化实践
Go高并发任务调度与Goroutine池化实践

本专题围绕 Go 语言在高并发任务处理场景中的实践展开,系统讲解 Goroutine 调度模型、Channel 通信机制以及并发控制策略。内容包括任务队列设计、Goroutine 池化管理、资源限制控制以及并发任务的性能优化方法。通过实际案例演示,帮助开发者构建稳定高效的 Go 并发任务处理系统,提高系统在高负载环境下的处理能力与稳定性。

50

2026.03.10

Kotlin Android模块化架构与组件化开发实践
Kotlin Android模块化架构与组件化开发实践

本专题围绕 Kotlin 在 Android 应用开发中的架构实践展开,重点讲解模块化设计与组件化开发的实现思路。内容包括项目模块拆分策略、公共组件封装、依赖管理优化、路由通信机制以及大型项目的工程化管理方法。通过真实项目案例分析,帮助开发者构建结构清晰、易扩展且维护成本低的 Android 应用架构体系,提升团队协作效率与项目迭代速度。

92

2026.03.09

JavaScript浏览器渲染机制与前端性能优化实践
JavaScript浏览器渲染机制与前端性能优化实践

本专题围绕 JavaScript 在浏览器中的执行与渲染机制展开,系统讲解 DOM 构建、CSSOM 解析、重排与重绘原理,以及关键渲染路径优化方法。内容涵盖事件循环机制、异步任务调度、资源加载优化、代码拆分与懒加载等性能优化策略。通过真实前端项目案例,帮助开发者理解浏览器底层工作原理,并掌握提升网页加载速度与交互体验的实用技巧。

102

2026.03.06

Rust内存安全机制与所有权模型深度实践
Rust内存安全机制与所有权模型深度实践

本专题围绕 Rust 语言核心特性展开,深入讲解所有权机制、借用规则、生命周期管理以及智能指针等关键概念。通过系统级开发案例,分析内存安全保障原理与零成本抽象优势,并结合并发场景讲解 Send 与 Sync 特性实现机制。帮助开发者真正理解 Rust 的设计哲学,掌握在高性能与安全性并重场景中的工程实践能力。

227

2026.03.05

PHP高性能API设计与Laravel服务架构实践
PHP高性能API设计与Laravel服务架构实践

本专题围绕 PHP 在现代 Web 后端开发中的高性能实践展开,重点讲解基于 Laravel 框架构建可扩展 API 服务的核心方法。内容涵盖路由与中间件机制、服务容器与依赖注入、接口版本管理、缓存策略设计以及队列异步处理方案。同时结合高并发场景,深入分析性能瓶颈定位与优化思路,帮助开发者构建稳定、高效、易维护的 PHP 后端服务体系。

530

2026.03.04

热门下载

更多
网站特效
/
网站源码
/
网站素材
/
前端模板

精品课程

更多
相关推荐
/
热门推荐
/
最新课程
HTML5/CSS3/JavaScript/ES6入门课程
HTML5/CSS3/JavaScript/ES6入门课程

共102课时 | 7.3万人学习

前端基础到实战(HTML5+CSS3+ES6+NPM)
前端基础到实战(HTML5+CSS3+ES6+NPM)

共162课时 | 21.8万人学习

第二十二期_前端开发
第二十二期_前端开发

共119课时 | 13.3万人学习

关于我们 免责申明 举报中心 意见反馈 讲师合作 广告合作 最新更新
php中文网:公益在线php培训,帮助PHP学习者快速成长!
关注服务号 技术交流群
PHP中文网订阅号
每天精选资源文章推送

Copyright 2014-2026 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号