新闻中心
深入理解二叉树扁平化:原地转换为双向链表结构

本文详细阐述如何将二叉树原地扁平化为一种类似双向链表的结构,其中节点的左右指针分别模拟链表的prev和next指针。通过递归辅助函数,文章深入解析了在遍历过程中如何正确地调整节点指针,以实现左-中-右的顺序连接,并避免常见的循环引用问题。此教程旨在提供一个清晰、高效的二叉树扁平化解决方案。
二叉树扁平化概述
二叉树扁平化是将一个标准的二叉树结构转换为一个类似双向链表的结构。在这个新的结构中,原二叉树的节点仍然存在,但它们的 left 和 right 指针被重新定义,分别扮演了双向链表中的 prev 和 next 角色。扁平化后的节点应遵循原二叉树的左-中-右(in-order)遍历顺序。此过程必须是“原地”(in-place)操作,即直接修改现有节点的指针,而不是创建新的节点或复制数据。最终,函数需要返回扁平化结构的最左侧节点(即原二叉树中in-order遍历的第一个节点)。
节点结构定义
首先,我们定义二叉树的节点结构,它包含一个值、一个左子节点和一个右子节点。
class BinaryTree:
def __init__(self, value, left=None, right=None):
self.value = value
self.left = left
self.right = right扁平化核心思想:递归与指针重定向
实现原地扁平化的关键在于使用递归辅助函数,它能够处理当前节点及其左右子树的扁平化,并将它们正确地连接起来。辅助函数通常需要返回扁平化子树的最左侧节点和最右侧节点,以便父节点能够将它们连接到自身。
初始尝试与常见陷阱
在尝试实现时,一个常见的误区是对辅助函数返回值的初始化。例如,考虑以下初始化方式:
# 假设这是某个辅助函数内部 leftmostofleft = node rightmostofright = node leftmostofright = node # 潜在问题 rightmostofleft = node # 潜在问题
这种初始化方式在某些情况下会导致错误。具体来说,如果一个节点没有右子节点,而我们将其 leftmostofright 初始化为 node,那么当执行 node.right = leftmostofright 时,就会导致 node.right = node,从而形成一个循环引用。这显然不是我们期望的双向链表结构,因为链表不应该有节点指向自身作为其下一个节点。因此,在没有实际子树的情况下,对应的“最左侧”或“最右侧”节点指针应该保持为 None,直到它们被实际的子树扁平化结果填充。
优化后的递归辅助函数
为了避免上述问题并简化逻辑,我们可以采用一个更精炼的递归策略。核心思想是:
Health AI健康云开放平台
专注于健康医疗垂直领域的AI技术开放平台
113
查看详情
- 递归处理左子树:扁平化左子树,获取其扁平化后的最左侧和最右侧节点。
- 连接当前节点与左子树:将当前节点的 left 指针指向左子树扁平化后的最右侧节点,并建立从该最右侧节点到当前节点的反向连接。
- 递归处理右子树:扁平化右子树,获取其扁平化后的最左侧和最右侧节点。
- 连接当前节点与右子树:将当前节点的 right 指针指向右子树扁平化后的最左侧节点,并建立从该最左侧节点到当前节点的反向连接。
- 返回扁平化子树的整体最左侧和最右侧节点。
以下是优化后的 helper 函数实现:
def flattenBinaryTree(root):
# 如果根节点为空,则直接返回None
if root is None:
return None
# 调用辅助函数进行扁平化,并返回扁平化结构的最左侧节点
leftmost, _ = helper(root)
return leftmost
def helper(node):
# 递归终止条件:如果节点为空,则返回None, None
if node is None:
return None, None
# 初始化当前节点为扁平化结构的最左和最右节点,
# 假设它目前是独立的,没有左右子树连接
leftmost_of_flattened_subtree = node
rightmost_of_flattened_subtree = node
# 1. 扁平化左子树
# 如果存在左子节点
if node.left:
# 递归调用helper,获取左子树扁平化后的最左侧和最右侧节点
# 注意:这里将返回的最左侧节点赋给 leftmost_of_flattened_subtree,
# 因为整个扁平化结构的最左侧节点将来自最左侧的子树。
# 同时,将左子树扁平化后的最右侧节点赋给 node.left,
# 这样 node.left 现在指向了它在双向链表中的“前一个”节点。
leftmost_of_flattened_subtree, node.left = helper(node.left)
# 建立从左子树扁平化后的最右侧节点到当前节点的连接
# 即:前一个节点的 right 指针指向当前节点
node.left.right = node
# 2. 扁平化右子树
# 如果存在右子节点
if node.right:
# 递归调用helper,获取右子树扁平化后的最左侧和最右侧节点
# 注意:这里将右子树扁平化后的最左侧节点赋给 node.right,
# 这样 node.right 现在指向了它在双向链表中的“后一个”节点。
# 同时,将返回的最右侧节点赋给 rightmost_of_flattened_subtree,
# 因为整个扁平化结构的最右侧节点将来自最右侧的子树。
node.right, rightmost_of_flattened_subtree = helper(node.right)
# 建立从右子树扁平化后的最左侧节点到当前节点的反向连接
# 即:后一个节点的 left 指针指向当前节点
node.right.left = node
# 返回当前节点所代表的扁平化子树的最左侧和最右侧节点
return leftmost_of_flattened_subtree, rightmost_of_flattened_subtree
代码解析与注意事项
-
flattenBinaryTree(root) 函数:这是对外暴露的接口。它首先处理根节点为空的情况,然后调用 helper 函数。helper 函数返回的是扁平化结构的最左侧节点和最右侧节点,但
flattenBinaryTree 只需要返回最左侧节点,因为它是链表的起点。 -
helper(node) 函数:
- 基线条件:if node is None: return None, None。当遇到空节点时,没有可扁平化的内容,返回 None。
-
初始化 leftmost_of_flattened_subtree 和 rightmost_of_flattened_subtree:
- 它们最初都指向 node 本身。这意味着如果 node 是一个叶子节点,那么它就是自身扁平化后的最左和最右节点。
- 如果 node 有左子树,leftmost_of_flattened_subtree 会被更新为左子树扁平化后的最左节点。
- 如果 node 有右子树,rightmost_of_flattened_subtree 会被更新为右子树扁平化后的最右节点。
-
处理左子树 (if node.left:):
- leftmost_of_flattened_subtree, node.left = helper(node.left):
- 递归地扁平化左子树。
- leftmost_of_flattened_subtree 被更新为左子树扁平化后的最左节点。这是因为整个扁平化序列的起点必然在当前节点的左子树中(如果存在)。
- node.left 被赋值为 helper(node.left) 返回的第二个值,即左子树扁平化后的最右节点。这意味着 node.left 现在指向了它在扁平化链表中的“前一个”节点。
- node.left.right = node:建立从左子树扁平化后的最右节点到当前节点的 right 指针连接。
- leftmost_of_flattened_subtree, node.left = helper(node.left):
-
处理右子树 (if node.right:):
- node.right, rightmost_of_flattened_subtree = helper(node.right):
- 递归地扁平化右子树。
- node.right 被赋值为 helper(node.right) 返回的第一个值,即右子树扁平化后的最左节点。这意味着 node.right 现在指向了它在扁平化链表中的“后一个”节点。
- rightmost_of_flattened_subtree 被更新为右子树扁平化后的最右节点。这是因为整个扁平化序列的终点必然在当前节点的右子树中(如果存在)。
- node.right.left = node:建立从右子树扁平化后的最左节点到当前节点的 left 指针连接。
- node.right, rightmost_of_flattened_subtree = helper(node.right):
- 返回:return leftmost_of_flattened_subtree, rightmost_of_flattened_subtree。返回当前节点所代表的扁平化子树的整体最左侧和最右侧节点,供上一层递归调用使用。
这种方法巧妙地利用了Python的多重赋值特性,直接将递归调用返回的最左/最右节点赋值给对应的变量和当前节点的 left/right 指针,从而避免了中间变量的复杂性,并确保了指针的正确连接。
示例与测试
为了验证扁平化过程,我们需要一个辅助函数来插入节点和遍历扁平化后的链表。
# 假设 BinaryTree 类定义如上
# 扁平化主函数和辅助函数定义如上
# 用于测试的 BinaryTree 辅助方法
class BinaryTree(BinaryTree): # 继承以添加辅助方法
def insert(self, values, i=0):
if i >= len(values):
return
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
# 测试用例
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("原始树结构(in-order 遍历):")
# 模拟in-order遍历
def in_order_tr*ersal(node, result):
if node is None:
return
in_order_tr*ersal(node.left, result)
result.append(node.value)
in_order_tr*ersal(node.right, result)
in_order_result = []
in_order_tr*ersal(root, in_order_result)
print(in_order_result) # 期望扁平化后的顺序:[4, 2, 7, 5, 8, 1, 6, 3]
# 扁平化二叉树
leftMostNode = flattenBinaryTree(root)
# 验证扁平化后的链表
flattened_values = leftMostNode.leftToRightToLeft()
print("扁平化后从左到右再到左遍历结果:")
print(flattened_values)
# 期望结果 (leftToRightToLeft): [4, 2, 7, 5, 8, 1, 6, 3, 3, 6, 1, 8, 5, 7, 2, 4]
expected = [4, 2, 7, 5, 8, 1, 6, 3, 3, 6, 1, 8, 5, 7, 2, 4]
assert flattened_values == expected
print("测试通过!")总结
二叉树原地扁平化为双向链表结构是一个经典的递归问题,它要求我们深入理解二叉树的遍历顺序以及如何精确地操纵节点指针。通过一个返回子树最左和最右节点的辅助函数,我们可以高效地在in-order遍历过程中建立起前后节点间的双向连接。关键在于正确处理递归返回值的赋值,确保 node.left 和 node.right 最终指向其在扁平化链表中的 prev 和 next 节点,同时避免循环引用。这种方法不仅实现了原地操作,而且逻辑清晰,易于理解和维护。
以上就是深入理解二叉树扁平化:原地转换为双向链表结构的详细内容,更多请关注其它相关文章!
# node
# 这是
# 是一个
# 转换为
# 它在
# 二叉树
# 链表
# 递归
# 扁平化
# 子树
# ai
# app
# python
# 遍历
# 盘锦双语网站建设
# 郑州公司网站平台建设
# 辽宁矩阵seo商家
# 新乡网站优化哪家合适做
# 辽宁手机网站推广哪里有
# 保健品网站seo运营
# 老年人活动营销推广方案
# 广州龙岗网站建设推广
# 小区推广营销团队
# 首页关键词排名怎么弄
相关栏目:
【
科技资讯46185 】
【
网络学院92790 】
相关推荐:
Golang如何使用bytes.Split分割字节切片_Golang bytes切片分割方法
Win11 USB传输速度慢怎么解决 Win11 USB驱动更新与设置
Golang指针如何与map组合使用_Golang map指针组合实践
微信网页版官方快速登录入口 微信网页版网页版账号直达
Vue.js 图片显示异常排查:理解应用挂载范围与DOM ID唯一性
《明末:渊虚之羽》设计师谈设计角色:那会刚毕业 充满激情
如何在J*a中实现统一对象行为接口_项目大型化时的接口规范化
C++如何比较两个字符串_C++ string compare函数与操作符对比
三星ZFold5多任务卡顿_Samsung ZFold5流畅度提升
在J*a中如何开发简易仓库管理与库存统计_仓库管理库存统计项目实战解析
Windows10怎么开启夜间模式 Windows10系统设置调整色温与亮度缓解夜间用眼疲劳【教程】
mc.js官网登录入口 mc.js官方登录入口最新版
Sublime Text怎么显示空格和制表符_Sublime显示不可见字符设置
拷贝漫画电脑版官网入口 拷贝漫画(PC版)在线直达
HTML长属性值处理:表单action路径优化与代码规范应对
CSS条件样式无法按设备触发怎么排查_media条件语句正确设置解决触发问题
响应式CSS Grid布局:优化网格项在小屏幕下的堆叠与宽度适配
Win10磁盘清理工具在哪 Win10打开并使用磁盘清理【教程】
c++如何使用std::memory_order控制原子操作顺序_c++ C++11内存模型详解
在哪找SublimeJ远程工具_SFTP插件配置教程
手机CPU怎么影响游戏体验_手机CPU对游戏性能的影响分析
俄罗斯Yandex搜索引擎入口_Yandex官网免登录一键访问
如何在网页中实现特定地点的随机图片展示
高德地图总提示网络异常怎么办 高德地图离线导航设置与网络排查方法
虫虫漫画精品漫画官网_虫虫漫画精品漫画官网进入精品漫画
uc手机浏览器网页版入口 uc浏览器手机版便捷登录首页
QQ邮箱稳定登录入口_QQ邮箱官方网站网页版使用
Composer的 "check-platform-reqs" 命令有什么用_在部署前检查生产环境是否满足Composer依赖需求
天眼查怎么看公司融资情况 天眼查企业融资历史查询步骤【攻略】
初次安装JDK时环境变量如何正确配置_J*A_HOME与PATH设置规则讲解
离线运行Go语言之旅:本地部署与GOPATH配置指南
html两个JS只运行一个怎么办_让双JS在html中都运行方法【技巧】
HTML5原生日期选择器与jQuery UI:实现日期选择器的联动与程序化控制
天猫2025双十一0点秒杀攻略 天猫爆款抢购时间
微信怎么把收藏的内容分类管理 微信收藏内容标签分类方法
探索高级语言到C/C++的转译路径:以Go为例及内存管理策略
我的世界官方游戏入口 我的世界官网平台直达链接
最新韩小圈网页版登录入口_官网在线观看官方链接
抖音未来赚钱的新趋势 2025年值得关注的变现风口分析
J*aScript中正确使用querySelectorAll与复杂CSS选择器
J*aScript map 方法中处理循环元素为空数组的策略
J*a 递归快速排序中静态变量的状态管理与陷阱
品牌机怎么重装系统 联想/戴尔/惠普笔记本恢复出厂系统教程
Animex动漫社网入口地址 Animex动漫社网正版在线入口
LocoySpider如何部署到云服务器_LocoySpider云部署的远程配置
Golang切片为何属于引用类型_Golang slice底层结构与引用语义说明
C++如何操作大型数据集_使用C++流式处理(Streaming)技术避免一次性加载大文件
Lar*el的路由模型绑定怎么用_Lar*el Route Model Binding简化控制器逻辑
必由学网页版入口 必由学官方平台直接访问
Win10文件资源管理器“此电脑”分组怎么关 Win10恢复经典视图【技巧】


2025-12-04
浏览次数:次
返回列表
flattenBinaryTree 只需要返回最左侧节点,因为它是链表的起点。