單鏈表刪除操作分為按位置刪除與按值刪除兩大類。無頭結點版本需特判首節點刪除(更新頭指針),邏輯稍顯繁瑣。本文詳細實現兩種刪除算法,並對比其適用場景。
typedef struct Node {
int element;
struct Node* next;
} Node;
int SizeList(Node* head); // 獲取長度
Node* DeletePosElement(Node* pHead, int pos) {
// pos: 1-based,1=首節點,size=尾節點
int size = SizeList(pHead);
if (pos <= 0 || pos > size) {
printf("%s: pos=%d 非法\n", __FUNCTION__, pos);
return pHead;
}
Node* pMove = pHead;
// 特判:刪除首節點
if (pos == 1) {
pHead = pHead->next;
free(pMove);
printf("%s: 刪除首節點成功\n", __FUNCTION__);
return pHead; // 新頭指針
}
// 定位到第 pos-1 個節點(被刪節點的前驅)
for (int i = 1; i < pos - 1; i++) {
pMove = pMove->next;
}
Node* pDel = pMove->next;
pMove->next = pDel->next;
free(pDel);
printf("%s: 刪除 pos=%d 成功\n", __FUNCTION__, pos);
return pHead;
}
Node* DeleteXElement(Node* pHead, int x) {
if (!pHead) {
printf("%s: 鏈表為空\n", __FUNCTION__);
return NULL;
}
Node *pPrev = pHead, *pCurr = pHead->next;
// 特判:首節點匹配
if (pHead->element == x) {
pHead = pHead->next;
free(pPrev);
printf("%s: 刪除首節點值 x=%d 成功\n", __FUNCTION__, x);
return pHead;
}
// 遍歷查找
while (pCurr) {
if (pCurr->element == x) {
pPrev->next = pCurr->next;
free(pCurr);
printf("%s: 刪除值 x=%d 成功\n", __FUNCTION__, x);
return pHead;
}
pPrev = pCurr;
pCurr = pCurr->next;
}
printf("%s: 未找到值 x=%d\n", __FUNCTION__, x);
return pHead;
}
| 維度 | 按位置刪除 | 按值刪除 |
|---|---|---|
| 輸入參數 | 索引 pos (1-based) | 目標值 x |
| 查找方式 | 直接計數定位 | 遍歷比對 element |
| 時間複雜度 | O(pos) | O(n) 最壞 |
| 刪除語義 | 精確位置 | 首個匹配值 |
| 適用場景 | 隨機訪問、索引操作 | 業務邏輯按值刪除 |
刪除前: A -> B -> C -> D
pPrev pCurr
操作: pPrev->next = pCurr->next; // A->next = C
free(pCurr); // 釋放 B
刪除後: A -> C -> D
int main() {
Node* list = CreateList(); // 尾插法建表:10 20 30 40 50
PrintList(list); // 10 20 30 40 50
list = DeletePosElement(list, 1); // 刪除首個 -> 20 30 40 50
PrintList(list);
list = DeletePosElement(list, 3); // 刪除第3個 -> 20 30 50
PrintList(list);
list = DeleteXElement(list, 30); // 刪除值30 -> 20 50
PrintList(list);
list = DeleteXElement(list, 99); // 不存在
PrintList(list); // 20 50
return 0;
}
無頭結點版本的「首節點特判」是 Bug 高發區。帶頭結點單鏈表下,刪除統一為:
// 統一刪除邏輯(帶頭結點)
Node* pPrev = pHead; // 頭結點作為哨兵
while (pPrev->next) {
if (/* 條件 */) {
Node* pDel = pPrev->next;
pPrev->next = pDel->next;
free(pDel);
break;
}
pPrev = pPrev->next;
}
無需特判首節點,頭指針永不變,代碼更簡潔健壯。實際項目強烈推薦帶頭結點版本。