要将二元查找树(BST)转换为排序的双向链表且不创建新节点,核心思路是利用 BST 的中序遍历特性(中序遍历结果为递增序列),在遍历过程中调整节点的指针指向。以下是具体步骤和实现逻辑:
一、核心思路
- 中序遍历 BST:BST 的中序遍历结果是 升序序列,这与目标双向链表的顺序一致。
- 调整指针关系:
- 遍历过程中,记录当前节点的 前驱节点(中序遍历的前一个节点)。
- 将前驱节点的
right
指针指向当前节点(作为双向链表的next
)。 - 将当前节点的
left
指针指向前驱节点(作为双向链表的prev
)。
- 递归实现:通过递归中序遍历,自底向上调整指针,避免使用额外空间(除递归栈外)。
二、步骤分解
1. 初始化前驱节点
- 使用一个全局变量(或引用传递的指针)记录中序遍历的前驱节点,初始化为
null
。
2. 中序遍历并调整指针
- 递归左子树:先处理左子树,确保左子树已转换为双向链表。
- 处理当前节点:
- 若前驱节点不为
null
,则将前驱节点的right
指向当前节点。 - 将当前节点的
left
指向前驱节点。 - 更新前驱节点为当前节点。
- 若前驱节点不为
- 递归右子树:处理右子树,继续构建双向链表。
3. 找到链表头节点
- 中序遍历的第一个节点(BST 最左节点)即为双向链表的头节点。
三、代码实现(Java 示例)
java
public class Solution { | |
private TreeNode prev = null; // 记录前驱节点 | |
private TreeNode head = null; // 链表头节点 | |
public TreeNode convert(TreeNode root) { | |
if (root == null) return null; | |
// 中序遍历左子树 | |
convert(root.left); | |
// 处理当前节点 | |
if (prev == null) { | |
head = root; // 第一个节点作为头节点 | |
} else { | |
prev.right = root; // 前驱节点的右指针指向当前节点 | |
root.left = prev; // 当前节点的左指针指向前驱节点 | |
} | |
prev = root; // 更新前驱节点 | |
// 中序遍历右子树 | |
convert(root.right); | |
return head; | |
} | |
} | |
// 二叉树节点定义 | |
class TreeNode { | |
int val; | |
TreeNode left; | |
TreeNode right; | |
TreeNode(int x) { val = x; } | |
} |
四、关键点说明
- 指针调整逻辑:通过
prev
记录上一个节点,实现双向链表的prev
和next
关系(即原树的left
和right
指针复用)。 - 头节点确定:中序遍历的第一个节点(最左叶子节点)是链表的头节点,此时
prev
为null
,故将head
设为当前节点。 - 时间复杂度:O(n),其中 n 为节点数,每个节点仅遍历一次。
- 空间复杂度:O(h),h 为树的高度,递归栈深度取决于树的高度(平衡树时 h=log n,最坏情况 h=n)。
五、示例演示
以如下 BST 为例:
4 | |
/ \ | |
2 5 | |
/ \ | |
1 3 |
中序遍历顺序:1 → 2 → 3 → 4 → 5
转换后双向链表:
1 <-> 2 <-> 3 <-> 4 <-> 5
(其中 <->
表示双向指针)
总结
通过中序遍历 BST,在遍历过程中调整节点的 left
和 right
指针,即可将 BST 原地转换为排序的双向链表。核心是利用 BST 中序遍历的有序性,以及递归实现时对前驱节点的跟踪。
在 Python 中实现二元查找树(BST)到排序双向链表的转换,核心思路与 Java 版本一致,即利用中序遍历的有序性,通过递归调整节点指针。以下是具体实现代码:
Python 实现代码
python
class TreeNode: | |
def __init__(self, val=0, left=None, right=None): | |
self.val = val | |
self.left = left # 原左子树指针,转换后作为链表的 prev | |
self.right = right # 原右子树指针,转换后作为链表的 next | |
class Solution: | |
def __init__(self): | |
self.prev = None # 记录中序遍历的前驱节点 | |
self.head = None # 双向链表的头节点 | |
def convert_bst_to_doubly_linked_list(self, root): | |
if not root: | |
return None | |
# 中序遍历左子树 | |
self.convert_bst_to_doubly_linked_list(root.left) | |
# 处理当前节点:连接前驱节点与当前节点 | |
if not self.prev: | |
self.head = root # 第一个节点作为头节点 | |
else: | |
self.prev.right = root # 前驱节点的 right 指向当前节点(next) | |
root.left = self.prev # 当前节点的 left 指向前驱节点(prev) | |
self.prev = root # 更新前驱节点为当前节点 | |
# 中序遍历右子树 | |
self.convert_bst_to_doubly_linked_list(root.right) | |
return self.head | |
# 测试示例 | |
def print_doubly_linked_list(head): | |
"""打印双向链表(正序和逆序验证)""" | |
print("正序遍历: ", end="") | |
current = head | |
while current: | |
print(current.val, end=" <-> ") | |
last = current # 记录尾节点用于逆序遍历 | |
current = current.right | |
print("None") | |
print("逆序遍历: ", end="") | |
current = last | |
while current: | |
print(current.val, end=" <-> ") | |
current = current.left | |
print("None") | |
# 构建测试 BST | |
root = TreeNode(4) | |
root.left = TreeNode(2, TreeNode(1), TreeNode(3)) | |
root.right = TreeNode(5) | |
# 转换并打印结果 | |
solution = Solution() | |
dll_head = solution.convert_bst_to_doubly_linked_list(root) | |
print_doubly_linked_list(dll_head) |
代码说明
-
TreeNode 类:定义二叉树节点,包含
val
(值)、left
(左子树指针)、right
(右子树指针)。转换后,left
作为双向链表的prev
,right
作为next
。 -
Solution 类:
prev
:跟踪中序遍历的前驱节点,初始为None
。head
:记录双向链表的头节点(中序遍历的第一个节点)。convert_bst_to_doubly_linked_list
方法:递归中序遍历 BST,调整指针关系。
-
核心逻辑:
- 中序遍历左子树:确保左子树先转换为双向链表。
- 处理当前节点:
- 若
prev
为None
,说明当前节点是中序遍历的第一个节点(BST 最左节点),设为链表头节点head
。 - 否则,将
prev.right
指向当前节点,当前节点left
指向prev
,完成双向连接。
- 若
- 更新前驱节点:将
prev
设为当前节点,继续遍历右子树。
-
测试与验证:
print_doubly_linked_list
函数通过正序(从head
开始)和逆序(从尾节点开始)遍历,验证链表的双向性。
输出结果
正序遍历: 1 <-> 2 <-> 3 <-> 4 <-> 5 <-> None | |
逆序遍历: 5 <-> 4 <-> 3 <-> 2 <-> 1 <-> None |
结果符合 BST 中序遍历的升序特性,且双向指针关系正确。
复杂度分析
- 时间复杂度:O(n),每个节点仅遍历一次(中序遍历)。
- 空间复杂度:O(h),h 为树的高度(递归栈深度),平衡树时 h=log n,最坏情况(斜树)h=n。
关键区别(与 Java 版本对比)
- Python 中没有显式的指针,但通过对象引用(
self.prev
、root.left
等)实现节点间的连接。 - 递归逻辑完全一致,核心是利用中序遍历的有序性和前驱节点跟踪。
通过以上代码,可在不创建新节点的情况下,将 BST 原地转换为排序的双向链表。