單鏈表是一種**線性動態資料結構**,由一系列**節點**組成,每個節點包含:
next)邏輯上連續,物理上**不連續** (堆上動態分配),支援 O(1) 插入/刪除 (已知前驅節點),但查詢為 O(n)。
// 節點結構體
typedef struct Node {
int data; // 資料域 (可替換為 void* 實現泛型)
struct Node *next; // 指標域,指向下一節點
} Node;
// 鏈表管理結構 (封裝頭指標、長度等)
typedef struct {
Node *head; // 虛擬頭節點 或 實際首節點
size_t size; // 節點數
} List;
建議使用虛擬頭節點:head 不存資料,head->next 指向首節點。插入/刪除統一邏輯,無需特判首節點。
bool list_init(List *list) {
list->head = malloc(sizeof(Node));
if (!list->head) return false;
list->head->next = NULL;
list->size = 0;
return true;
}
bool list_push_back(List *list, int val) {
Node *node = malloc(sizeof(Node));
if (!node) return false;
node->data = val;
node->next = NULL;
// 找到尾節點 (若維護 tail 指標可優化)
Node *cur = list->head;
while (cur->next) cur = cur->next;
cur->next = node;
list->size++;
return true;
}
bool list_push_front(List *list, int val) {
Node *node = malloc(sizeof(Node));
if (!node) return false;
node->data = val;
node->next = list->head->next;
list->head->next = node;
list->size++;
return true;
}
bool list_insert(List *list, size_t index, int val) {
if (index > list->size) return false;
Node *prev = list->head;
for (size_t i = 0; i < index; i++) prev = prev->next;
Node *node = malloc(sizeof(Node));
if (!node) return false;
node->data = val;
node->next = prev->next;
prev->next = node;
list->size++;
return true;
}
bool list_erase(List *list, size_t index) {
if (index >= list->size) return false;
Node *prev = list->head;
for (size_t i = 0; i < index; i++) prev = prev->next;
Node *del = prev->next;
prev->next = del->next;
free(del);
list->size--;
return true;
}
Node* list_find(List *list, int val) {
for (Node *p = list->head->next; p; p = p->next)
if (p->data == val) return p;
return NULL;
}
Node* list_at(List *list, size_t index) {
if (index >= list->size) return NULL;
Node *p = list->head->next;
for (size_t i = 0; i < index; i++) p = p->next;
return p;
}
void list_foreach(List *list, void (*visit)(int)) {
for (Node *p = list->head->next; p; p = p->next)
visit(p->data);
}
void list_destroy(List *list) {
Node *p = list->head;
while (p) {
Node *next = p->next;
free(p);
p = next;
}
list->head = NULL;
list->size = 0;
}
typedef struct GNode {
void *data;
struct GNode *next;
} GNode;
typedef struct {
GNode *head;
size_t size;
size_t data_size; // 資料大小,用於複製
void (*free_data)(void*); // 可選:自定義釋放
} GList;
bool glist_push_back(GList *list, const void *data) {
GNode *node = malloc(sizeof(GNode));
if (!node) return false;
node->data = malloc(list->data_size);
if (!node->data) { free(node); return false; }
memcpy(node->data, data, list->data_size);
node->next = NULL;
// ... 同尾插邏輯
return true;
}
用 void* + data_size + 複製函數實現型別無關容器。
// 迭代法 (三指標)
Node* reverse_iter(Node *head) {
Node *prev = NULL, *cur = head, *next;
while (cur) {
next = cur->next;
cur->next = prev;
prev = cur;
cur = next;
}
return prev; // 新頭節點
}
// 遞迴法
Node* reverse_rec(Node *head) {
if (!head || !head->next) return head;
Node *new_head = reverse_rec(head->next);
head->next->next = head;
head->next = NULL;
return new_head;
}
bool has_cycle(Node *head) {
Node *slow = head, *fast = head;
while (fast && fast->next) {
slow = slow->next;
fast = fast->next->next;
if (slow == fast) return true;
}
return false;
}
Node* merge_sorted(Node *a, Node *b) {
Node dummy, *tail = &dummy;
while (a && b) {
if (a->data <= b->data) { tail->next = a; a = a->next; }
else { tail->next = b; b = b->next; }
tail = tail->next;
}
tail->next = a ? a : b;
return dummy.next;
}
| 原則 | 做法 |
|---|---|
| 封裝 | 對外只暴露 List* 和操作函數,隱藏 Node 結構 |
| 錯誤處理 | 所有分配檢查 NULL,回傳 bool/錯誤碼 |
| const 正確性 | Node* list_find(const List*, int) |
| 迭代器介面 | 提供 list_begin, list_next, list_end 支援範圍 for |
| 記憶體池 | 高頻分配場景預分配節點陣列,減少 malloc 開銷 |
| 執行緒安全 | 加鎖或使用無鎖佇列 (進階) |
| 操作 | 動態陣列 | 單鏈表 |
|---|---|---|
| 隨機存取 | O(1) | O(n) |
| 頭部插入/刪除 | O(n) | O(1) |
| 尾部插入 | O(1) 攤還 | O(1) (維護 tail) |
| 中間插入/刪除 | O(n) | O(1) (已知前驅) |
| 記憶體局部性 | 極佳 | 差 (節點分散) |
| 記憶體額外開銷 | 低 (容量預留) | 高 (每節點 1 指標) |
| 適用場景 | 隨機存取多、資料量可預估 | 頻繁頭/中插刪、資料量動態大 |
單鏈表是理解指標、動態記憶體、遞迴、資料結構封裝的**最佳入門載體**。掌握:
void* + 複製/釋放回調從手寫 malloc/free 到設計泛型容器介面,單鏈表承載了 C 語言系統程式設計的核心功力。