當資料在記憶體中不連續分佈時,單向鏈表透過每個節點攜帶的 next 指標,將離散塊串聯成邏輯整體。相較陣列,鏈表在動態增刪、記憶體彈性上具顯著優勢,代價是隨機存取效率較低(O(N))。本文系統梳理單向鏈表的結構設計與八大核心操作,附完整 C 語言實作。
typedef struct _LINK_NODE {
int data;
struct _LINK_NODE* next;
} LINK_NODE;
LINK_NODE* alloca_node(int value) {
LINK_NODE* pNode = (LINK_NODE*)malloc(sizeof(LINK_NODE));
assert(pNode);
pNode->data = value;
pNode->next = NULL;
return pNode;
}
void delete_node(LINK_NODE** ppHead) {
if (!ppHead || !*ppHead) return;
LINK_NODE** ppNext = &(*ppHead)->next;
free(*ppHead);
delete_node(ppNext);
}
STATUS _add_data(LINK_NODE** ppCur, LINK_NODE* pNew) {
if (!*ppCur) { *ppCur = pNew; return TRUE; }
return _add_data(&(*ppCur)->next, pNew);
}
STATUS add_data(LINK_NODE** ppHead, int value) {
if (!ppHead) return FALSE;
LINK_NODE* pNew = alloca_node(value);
assert(pNew);
return _add_data(ppHead, pNew);
}
STATUS _delete_data(LINK_NODE** ppCur, int value) {
if (!(*ppCur)->next) return FALSE;
LINK_NODE* pTarget = (*ppCur)->next;
if (pTarget->data == value) {
(*ppCur)->next = pTarget->next;
free(pTarget);
return TRUE;
}
return _delete_data(&(*ppCur)->next, value);
}
STATUS delete_data(LINK_NODE** ppHead, int value) {
if (!ppHead || !*ppHead) return FALSE;
if ((*ppHead)->data == value) {
LINK_NODE* pTmp = *ppHead;
*ppHead = pTmp->next;
free(pTmp);
return TRUE;
}
return _delete_data(ppHead, value);
}
LINK_NODE* find_data(const LINK_NODE* pHead, int value) {
if (!pHead) return NULL;
if (pHead->data == value) return (LINK_NODE*)pHead;
return find_data(pHead->next, value);
}
void print_node(const LINK_NODE* pHead) {
if (pHead) {
printf("%d\n", pHead->data);
print_node(pHead->next);
}
}
int count_node(const LINK_NODE* pHead) {
if (!pHead) return 0;
return 1 + count_node(pHead->next);
}
本文採用遞迴風格展現鏈表操作的自相似結構,代碼簡潔優雅。但需注意:
| 場景 | 推薦結構 | 理由 |
|---|---|---|
| 頻繁頭尾增刪、長度不可預知 | 鏈表 | O(1) 插入/刪除,無擴容開銷 |
| 高頻隨機存取、二分搜尋 | 陣列/動態陣列 | O(1) 索引,記憶體連續利於快取 |
| 需持久化、網路傳輸 | 陣列 | 序列化簡單,無指標重定位問題 |
| 實現 Stack/Queue | 鏈表 | 天然適配動態大小,無容量上限 |
掌握單向鏈表的八大操作,是理解更複雜資料結構(雙向鏈表、循環鏈表、跳表、鄰接表)的基石。建議動手重寫迭代版本,體會指標操作的微妙之處。