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


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

本教程详细探讨了如何将二叉树原地(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,例如:

堆友 堆友

Alibaba Design打造的设计师全成长周期服务平台,旨在成为设计师的好朋友

堆友 759 查看详情 堆友
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_tr*ersal(node, result):
        if node:
            inorder_tr*ersal(node.left, result)
            result.append(node.value)
            inorder_tr*ersal(node.right, result)

    original_inorder = []
    inorder_tr*ersal(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. 注意事项与总结

  • 原地修改: 扁

以上就是二叉树原地扁平化为双向链表结构:原理与优化实践的详细内容,更多请关注其它相关文章!


# 如何用  # 教你如何面试seo  # 广州社群营销推广首选  # 网页seo排名优化方案  # 京东品牌营销推广方案  # 旅游网站建设团队  # 营销推广剪辑工作内容  # 大润发软文营销推广策略  # 惠州财税seo怎么做  # 武汉seo推广和营销哪个好  # 海南seo公司多少钱  # 再到  # 默认值  # node  # 连接到  # 二叉树  # 遍历  # 链表  # 扁平化  # 递归  # 子树  # 为什么  # 排列  # 优化实践  # ai  # app 


相关栏目: 【 Google疑问12 】 【 Facebook疑问10 】 【 优化推广96088 】 【 技术知识133117 】 【 IDC资讯59369 】 【 网络运营7196 】 【 IT资讯61894 】


相关推荐: 店铺如何做视频号推广?做视频号推广有用吗?  《蓝色星原:旅谣》坐骑获取攻略  WooCommerce 新客户订单自动添加管理员备注教程  《健康大兴》注册方法介绍  魔法祈幻界兑换码礼包大全  在Peewee中处理PostgreSQL记录重复:一站式数据摄取教程  sublime怎么在文件中显示代码结构大纲_sublime符号列表功能  第五人格PC版怎么避免被封号_第五人格PC版防封号注意事项  J*aScript模块加载器_RequireJS原理分析  Win10截图远程协助 Win10远程桌面截屏法【场景应用】  《暗黑破坏神4》国服回归送狂欢礼包 价值6916元  《金山词霸》语音翻译方法  悟空浏览器网页版链接 悟空浏览器网页版最新有效地址  word表格如何按某一列内容进行排序_Word表格按列排序方法  iSpring三分屏制作教程  《植物大战僵尸3》火龙草作用介绍  快手网页版官方访问 快手网页版页面在线打开  126邮箱网页在线登录2025_126邮箱网页版入口官方地址  《土豆雅思》修改密码方法  快递优选如何查优选物流_快递优选专属物流渠道查询与配送时效  PHP中获取HTTP响应状态消息:方法与限制  嘴唇干裂起皮怎么办 唇部护理与预防干裂的方法【详解】  微信客户端如何找回密码_微信客户端忘记密码找回方法  Golang如何实现HTTP请求重试机制_Golang HTTP请求错误处理策略  《梦想世界:长风问剑录》药师一图流分享  WooCommerce购物车:强制显示所有交叉销售商品教程  VB表达式书写规则解析  TikTok收藏夹无法删除视频如何解决 TikTok收藏管理优化方法  阿里旺旺电脑网页版入口 阿里旺旺电脑版网页登录入口  sublime如何配置PHP开发环境_在sublime中运行与调试PHP代码  抖音网页版官方链接 抖音网页版官网链接入口  猫眼app抢票快还是小程序快  《图怪兽》退出登录方法  Firefox OS应用开发:解决XMLHttpRequest跨域请求阻塞问题  J*a中逻辑运算符如何使用_逻辑与或非的基础用法讲解  《oppo商城》维修服务位置  B站怎么开|直播| B站|直播|申请需要什么条件【新手必看】  火柴人战争网页版在线玩  优化Leaflet弹出层图片显示:条件渲染策略  使用Python和GBGB API高效抓取指定日期范围和赛道比赛结果教程  windows10怎么设置电源按钮_windows10按下电源键功能修改  易车网官网直达入口 易车网在线登录入口  盲鳗善于分泌黏液猜猜主要用来做什么  Leaflet地图弹出窗口图片动态显示:避免缺失图标的专业指南  Highcharts雷达图轴线交点数值标注指南  圆通快递包裹轨迹查询 圆通速递快件实时位置跟踪  研招网官方网站招生平台入口_中国研究生招生信息网官网登录  word文档行距怎么调?word文档调行距的操作步骤  PHP中动态类名访问的类实例类型提示与静态分析实践  MacBook Pro词典使用指南 

 2025-12-07

了解您产品搜索量及市场趋势,制定营销计划

同行竞争及网站分析保障您的广告效果

点击免费数据支持

提交您的需求,1小时内享受我们的专业解答。

运城市盐湖区信雨科技有限公司


运城市盐湖区信雨科技有限公司

运城市盐湖区信雨科技有限公司是一家深耕海外推广领域十年的专业服务商,作为谷歌推广与Facebook广告全球合作伙伴,聚焦外贸企业出海痛点,以数字化营销为核心,提供一站式海外营销解决方案。公司凭借十年行业沉淀与平台官方资源加持,打破传统外贸获客壁垒,助力企业高效开拓全球市场,成为中小企业出海的可靠合作伙伴。

 8156699

 13765294890

 8156699@qq.com

Notice

We and selected third parties use cookies or similar technologies for technical purposes and, with your consent, for other purposes as specified in the cookie policy.
You can consent to the use of such technologies by closing this notice, by interacting with any link or button outside of this notice or by continuing to browse otherwise.