將一棵二叉搜索樹(或普通二叉樹)就地轉換為排序雙向鏈表:
TreeNode *prev = NULL, *head = NULL;
void inorder(TreeNode *root) {
if (!root) return;
inorder(root->left);
// 處理當前節點
root->left = prev;
if (prev) prev->right = root;
else head = root; // 首個節點為頭
prev = root;
inorder(root->right);
}
TreeNode* treeToDoublyList(TreeNode* root) {
inorder(root);
// 若需循環雙向鏈表,補上:
// head->left = prev; prev->right = head;
return head;
}
時間 O(n),空間 O(h)(遞歸棧,h 為樹高)。
TreeNode* treeToDoublyList(TreeNode* root) {
TreeNode *cur = root, *prev = NULL, *head = NULL;
while (cur) {
if (!cur->left) {
// 訪問 cur
cur->left = prev;
if (prev) prev->right = cur;
else head = cur;
prev = cur;
cur = cur->right;
} else {
// 找前驅
TreeNode *pre = cur->left;
while (pre->right && pre->right != cur) pre = pre->right;
if (!pre->right) {
pre->right = cur; // 建立線索
cur = cur->left;
} else {
pre->right = NULL; // 恢復樹結構
// 訪問 cur
cur->left = prev;
if (prev) prev->right = cur;
else head = cur;
prev = cur;
cur = cur->right;
}
}
}
return head;
}
利用葉節點右指針為空的特性建立「線索」,無需棧,空間 O(1)。
struct Pair { TreeNode *head, *tail; };
Pair convert(TreeNode *root) {
if (!root) return {NULL, NULL};
Pair L = convert(root->left);
Pair R = convert(root->right);
root->left = L.tail;
if (L.tail) L.tail->right = root;
root->right = R.head;
if (R.head) R.head->left = root;
return { L.head ? L.head : root, R.tail ? R.tail : root };
}
TreeNode* treeToDoublyList(TreeNode* root) {
return convert(root).head;
}
每層返回子鏈表頭尾,當前節點在中間串聯,邏輯清晰,無全局變量。
void printList(TreeNode *head) {
TreeNode *p = head;
while (p) {
printf("%d ", p->val);
p = p->right;
}
puts("");
// 反向驗證
while (p && p->left) p = p->left;
while (p) {
printf("%d ", p->val);
p = p->left;
}
puts("");
}
| 方法 | 時間 | 空間 | 優點 | 缺點 |
|---|---|---|---|---|
| 遞歸+全局尾指針 | O(n) | O(h) | 代碼簡短,易理解 | 全局變量,非線程安全 |
| Morris 遍歷 | O(n) | O(1) | 極致空間效率 | 修改樹結構過程較繁瑣 |
| 分治返回頭尾 | O(n) | O(h) | 函數式,無副作用,易並行 | 結構體返回略顯冗長 |