A Binary Search Tree (BST) organizes data hierarchically, allowing for efficient search, insertion, and deletion operations with an average time complexity of O(log n). Even so, certain algorithms and system designs require linear, sequential access to sorted data. In practice, converting a BST into a sorted doubly linked list is a classic algorithmic problem that tests a developer's understanding of tree traversal, pointer manipulation, and recursion. This is where the doubly linked list shines, offering O(1) insertion and deletion at known positions and straightforward bidirectional traversal. This article provides a practical guide to mastering this conversion, covering the theoretical foundation, step-by-step implementation strategies, and complexity analysis.
Understanding the Core Concept
Before diving into code, it is crucial to visualize the structural relationship between the two data structures. In a BST, every node possesses a left pointer (to smaller values) and a right pointer (to larger values). In a sorted doubly linked list, every node possesses a prev pointer (to the previous/smaller element) and a next pointer (to the next/larger element).
The mapping is direct:
- The BST
leftpointer becomes the Listprevpointer. Because of that, * The BSTrightpointer becomes the Listnextpointer. Practically speaking, * The in-order traversal of a BST visits nodes in ascending order. Because of this, performing an in-order traversal while rewiring pointers effectively flattens the tree into the desired list.
The challenge lies in performing this rewiring in-place—without allocating new nodes—typically achieving O(N) time complexity and O(H) space complexity (where H is the height of the tree, due to the recursion stack).
Approach 1: Recursive In-Order Traversal (Standard Solution)
The most intuitive method leverages the natural ordering of recursive in-order traversal (Left -> Root -> Right). We maintain a reference to the previously visited node (prev) and the head of the resulting list (head).
Algorithm Steps
- Initialize two pointers:
prev = nullandhead = null. - Traverse the tree recursively using in-order logic.
- Process Current Node:
- If
previsnull, this is the leftmost (smallest) node. Mark it ashead. - Otherwise, link
prev.right = currentandcurrent.left = prev. - Update
prev = current.
- If
- Finalize: After traversal completes,
headpoints to the start, andprevpoints to the end. For a circular list, connecthead.left = prevandprev.right = head.
Python Implementation
class Node:
def __init__(self, val, left=None, right=None):
self.val = val
self.left = left
self.right = right
def treeToDoublyList(root: 'Node') -> 'Node':
if not root:
return None
# Use a list or nonlocal variable to hold references across recursive calls
prev = [None]
head = [None]
def inorder(node):
if not node:
return
# 1. Worth adding: traverse Left Subtree
inorder(node. left)
# 2. In practice, process Current Node
# Link previous node to current
if prev[0]:
prev[0]. right = node
node.And left = prev[0]
else:
# This is the smallest element (Head)
head[0] = node
# Update prev to current
prev[0] = node
# 3. Traverse Right Subtree
inorder(node.
inorder(root)
# Optional: Make it Circular Doubly Linked List
head[0].left = prev[0]
prev[0].right = head[0]
return head[0]
Java Implementation
class Node {
public int val;
public Node left;
public Node right;
public Node(int val) { this.val = val; }
}
class Solution {
Node prev = null;
Node head = null;
public Node treeToDoublyList(Node root) {
if (root == null) return null;
helper(root);
// Close the loop for circular list
head.left = prev;
prev.right = head;
return head;
}
void helper(Node node) {
if (node == null) return;
helper(node.left);
if (prev !So naturally, = null) {
prev. In practice, right = node;
node. left = prev;
} else {
head = node; // Leftmost node
}
prev = node;
helper(node.
**Complexity Analysis:**
* **Time:** *O(N)* — Every node is visited exactly once.
* **Space:** *O(H)* — Recursion stack depth equals the height of the tree. *O(N)* worst case (skewed tree), *O(log N)* best case (balanced tree).
## Approach 2: Iterative In-Order Traversal (Explicit Stack)
Recursion carries the risk of **Stack Overflow** for extremely deep trees (e.g.Also, , a skewed tree with 100,000+ nodes). An iterative approach using an explicit stack mitigates this risk by utilizing heap memory instead of the limited call stack.
### Algorithm Steps
1. Initialize `stack`, `curr = root`, `prev = null`, `head = null`.
2. Loop while `curr != null` OR `stack` is not empty:
* Push all left children onto stack (`curr = curr.left`).
* Pop `node` from stack.
* Process `node` (link with `prev`, update `head`/`prev` exactly like recursive version).
* Move to right child (`curr = node.right`).
3. Connect `head` and `prev` for circularity.
### Python Iterative Code
```python
def treeToDoublyListIterative(root):
if not root: return None
stack = []
curr = root
prev = None
head = None
while curr or stack:
# Go as left as possible
while curr:
stack.append(curr)
curr = curr.left
# Process node
node = stack.pop()
if prev:
prev.right = node
node.left = prev
else:
head = node
prev = node
curr = node.right # Move to right subtree
# Make circular
head.left = prev
prev.right = head
return head
This approach maintains O(N) time and O(H) space but is significantly more solid for production environments handling large datasets And that's really what it comes down to..
Approach 3: Morris Traversal (O(1) Space)
For the ultimate space optimization, Morris Traversal allows in-order traversal without recursion or a stack by temporarily modifying the tree structure (creating "threads" to in-order successors) Most people skip this — try not to..
The Logic
- Initialize
curr = root,prev = null,head = null. - While
currexists:- Case A: No Left Child. Process
curr. Movecurr = curr.right. - Case B: Left Child Exists. Find
curr's in-order predecessor (rightmost node in left subtree).- If predecessor's
rightisnull: Create thread (predecessor.right = curr), movecurr = curr.left. - If predecessor's
rightiscurr: Thread exists (returning from left subtree). Remove thread (predecessor.right = null). Processcurr. Movecurr = curr.right.
- If predecessor's
- Case A: No Left Child. Process
Python Morris Implementation
def