typedef struct Node {
int value;
struct Node *next;
} Node;在有序链表中插入 value,要找到「最后一个小于 value 的节点」,新节点插在它后面。朴素写法要为「插在表头」单独写分支,因为改的是根指针本身;更漂亮的做法是用「指向 link 字段的指针」当游标:
/* 返回值:是否插入成功(内存不足失败) */
int sll_insert(Node **rootp, int new_value)
{
Node *cur = *rootp; /* cur:当前 link 字段指向的节点 */
Node *prev = NULL;
while (cur != NULL && cur->value < new_value) {
prev = cur;
cur = cur->next;
}
/* 此时应插在 prev(可能 NULL)之后、cur(可能 NULL)之前 */
Node *fresh = malloc(sizeof *fresh);
if (fresh == NULL)
return 0;
fresh->value = new_value;
if (prev == NULL) { /* 插表头:改根指针 */
fresh->next = *rootp;
*rootp = fresh;
} else { /* 插中间/尾部:改 prev 的 link */
fresh->next = cur;
prev->next = fresh;
}
return 1;
}
/* 调用:sll_insert(&root, 42); —— 传根指针的地址 */上面的 if/else 两个分支本质是同一句话:「让某个 link 字段指向新节点,新节点指向 cur」。表头分支改的是根指针,中间分支改的是某节点的 next——如果游标直接落在「link 字段本身」,两种情况就统一了:
int sll_insert2(Node **rootp, int new_value)
{
Node **linkp = rootp; /* linkp:下一个 link 字段的位置 */
Node *cur;
while ((cur = *linkp) != NULL && cur->value < new_value)
linkp = &cur->next; /* 游标推进到下一个 link 字段 */
Node *fresh = malloc(sizeof *fresh);
if (fresh == NULL)
return 0;
fresh->value = new_value;
fresh->next = cur;
*linkp = fresh; /* 统一写法:改 link 字段,一招通吃 */
return 1;
}单链表删除某节点需要先找到前驱(O(n));双链表每个节点带 prev 指针,拿到节点就能自删。代价是多一个指针的存储和维护:
typedef struct DNode {
int value;
struct DNode *fwd; /* 向后 */
struct DNode *bwd; /* 向前 */
} DNode;
int dll_insert(DNode **rootp, int new_value)
{
DNode **fwdp = rootp, *bwd = NULL, *cur;
while ((cur = *fwdp) != NULL && cur->value < new_value) {
bwd = cur;
fwdp = &cur->fwd;
}
if (cur != NULL && cur->value == new_value)
return 1; /* 已存在 */
DNode *fresh = malloc(sizeof *fresh);
if (fresh == NULL)
return 0;
fresh->value = new_value;
fresh->fwd = cur; /* 右接 */
*fwdp = fresh; /* 左接(统一 link 技巧) */
fresh->bwd = bwd; /* 反向指针两个方向都要修 */
if (cur != NULL)
cur->bwd = fresh;
return 1;
}双链表的检查口诀:每个操作后,正向走一遍、反向走一遍,都通才算对。fwd 和 bwd 共 4 处指针(2 组),改漏任何一处链就断。