面對「妹紙」提出的算法挑戰:將擁有子鏈表指針的多層雙向鏈表,就地轉換為單層主鏈表,並能完美還原。本文記錄從圖解推演到代碼實現的完整思維過程,包含 O(n) 時間複雜度的展開算法、遞歸分離還原算法,及單向鏈表變體討論。
typedef struct ChainNode {
struct ChainNode *next; // 同層下一節點
struct ChainNode *previous; // 同層上一節點
struct ChainNode *child; // 子鏈表頭節點
int value; // 數據域
} ChainNode;
結構特徵:
child 指向一個獨立的子鏈表(同樣雙向)child,形成多層樹狀結構child 非空,將整個子鏈表**整體追加**到尾部

// 追加子鏈表到尾部,更新尾指針
void appendChain(ChainNode *child, ChainNode **tail) {
ChainNode *curr = child;
(*tail)->next = child; // 原尾連接子鏈表頭
child->previous = *tail; // 子鏈表頭回指原尾
// 遍歷到子鏈表末尾
while (curr->next) curr = curr->next;
*tail = curr; // 更新全局尾指針
}
// 主展開函數
void expandChain(ChainNode *head, ChainNode **tail) {
ChainNode *curr = head;
while (curr) {
if (curr->child) {
appendChain(curr->child, tail);
}
curr = curr->next; // 繼續遍歷(含新追加部分)
}
}
child 非空previous/next 指針)// 遞歸分離子鏈表
void separateChild(ChainNode *childHead) {
ChainNode *curr = childHead;
while (curr) {
if (curr->child) {
// 切斷:子鏈表頭的 previous 指向的節點 next 置 NULL
curr->child->previous->next = NULL;
// 子鏈表頭 previous 置 NULL
curr->child->previous = NULL;
// 遞歸處理子鏈表
separateChild(curr->child);
}
curr = curr->next;
}
}
// 還原主入口
void unexpandChain(ChainNode *head, ChainNode **tail) {
separateChild(head); // 從主鏈表頭開始遞歸分離
// 重新尋找尾指針
ChainNode *curr = head;
while (curr->next) curr = curr->next;
*tail = curr;
}
/********************* Author: AnnsShadow Time: 2015-12-03 *********************/ #include#include typedef struct ChainNode { struct ChainNode *next; struct ChainNode *previous; struct ChainNode *child; int value; } ChainNode; void appendChain(ChainNode *child, ChainNode **tail); void expandChain(ChainNode *head, ChainNode **tail); void separateChild(ChainNode *childHead); void unexpandChain(ChainNode *head, ChainNode **tail); // 輔助:打印鏈表 void printChain(ChainNode *head, const char *tag) { printf("%s: ", tag); for (ChainNode *p = head; p; p = p->next) { printf("%d ", p->value); } printf("\n"); } int main() { // 構建測試用多層鏈表 ChainNode nodes[10]; for (int i = 0; i < 10; ++i) { nodes[i].value = i+1; nodes[i].next = nodes[i].previous = nodes[i].child = NULL; } // 主鏈: 1-2-3-4 for (int i = 0; i < 3; ++i) { nodes[i].next = &nodes[i+1]; nodes[i+1].previous = &nodes[i]; } // 節點2有子鏈: 5-6 nodes[1].child = &nodes[4]; nodes[4].next = &nodes[5]; nodes[5].previous = &nodes[4]; // 節點5有子鏈: 7 nodes[4].child = &nodes[6]; // 節點3有子鏈: 8-9-10 nodes[2].child = &nodes[7]; nodes[7].next = &nodes[8]; nodes[8].previous = &nodes[7]; nodes[8].next = &nodes[9]; nodes[9].previous = &nodes[8]; ChainNode *head = &nodes[0], *tail = &nodes[3]; printChain(head, "原始主鏈(僅主層)"); expandChain(head, &tail); printChain(head, "展開後扁平鏈表"); unexpandChain(head, &tail); printChain(head, "還原後主鏈(僅主層)"); return 0; } // ... 此處粘貼上述 appendChain, expandChain, separateChild, unexpandChain 實現 ...
| 操作 | 時間複雜度 | 空間複雜度 | 說明 |
|---|---|---|---|
| 展開 | O(N) | O(1) | 每個節點恰好被訪問一次 |
| 還原 | O(N) | O(H) | H 為最大嵌套深度(遞歸棧) |

previous 指針,還原時無法 O(1) 切斷。需記錄「父節點」或改用棧/陣列輔助,空間變 O(N)雙向鏈表的 previous 指針是實現 O(1) 空間就地還原的關鍵。展開時「層序追加尾部」保證順序穩定;還原時「正向遞歸分離」利用雙向指針精準切斷,避免了逆向查找的高昂代價。這個算法展示了指針操作中「正向思維配合雙向結構」的優雅威力。