1.单链表的按位序插入(带头结点)
cpp
struct LNode { //定义单链表节点类型
ElemType data; //每个节点存放一个数据元素
struct LNode* next; //指针指向下一个节点
};
struct LNode* p = (struct LNode*)malloc(sizeof(struct LNode));
//增加一个新的结点:在内存中申请一个结点所需空间,并用指针p指向这个结点

完整代码:
cpp
#include <iostream>
#include <cstdlib>
using namespace std;
using ElemType = int;
typedef struct LNode {
ElemType data;
struct LNode* next;
}LNode,*LinkList;
bool ListInsert(LinkList& L, int i, ElemType e) {
if (i < 1) {
return false; //非法位序
}
LNode* p = L; //p指向头结点
int j = 0; //j表示p的位序(头结点算0)
while (p != NULL && j < i - 1) { // 找到第 i-1 个结点
p = p->next;
j++;
}
if (p == NULL) { // i 过大,链表不够长
return false;
}
LNode* s = (LNode*)malloc(sizeof(LNode));
s->data = e;
s->next = p->next;
p->next = s; ----解释1
return true;
}
/* 辅助:初始化一个空链表(只含头结点) */
void InitList(LinkList& L) {
L = (LNode*)malloc(sizeof(LNode));
L->next = nullptr;
}
//尾插法建表,方便测试
void CreateListTail(LinkList& L, int n) {
InitList(L);
LNode* r = L;
cout << "请依次输入" << n << "个元素:";
for (int i = 0;i < n;i++) {
ElemType x;
cin >> x;
LNode* s = (LNode*)malloc(sizeof(LNode));
s->data = x;
s->next = NULL;
r -> next = s;
r = s;
}
} ----解释2
//打印链表
void PrintList(LinkList L) {
LNode* p = L->next;
cout << "当前链表为:";
while (p) {
cout << p->data << (p->next ? "->" : "");
p = p->next;
}
cout << endl;
} ----解释3
/* 测试 */
int main() {
LinkList L;
InitList(L); // 空链表
/* 手动插入几个元素 */
ListInsert(L, 1, 10); // 第 1 位插入 10
ListInsert(L, 2, 20); // 第 2 位插入 20
ListInsert(L, 1, 5); // 再插到最前面
PrintList(L); // 应输出:5 -> 10 -> 20
/* 交互式测试 */
int pos, val;
cout << "请输入要插入的位序 和 元素值(位序<1结束):";
while (cin >> pos >> val) {
if (pos < 1) break;
if (ListInsert(L, pos, val))
PrintList(L);
else
cout << "插入失败,位序非法或内存不足!" << endl;
}
return 0;
}
解释1:
cpp
LNode* s = (LNode*)malloc(sizeof(LNode));
s->data = e;
s->next = p->next;
p->next = s;
这四句就是"把一个新生成结点 `s` 插到 `p` 结点后面"的核心四步,逐行解释如下:
1. `LNode* s = (LNode*)malloc(sizeof(LNode));`
在内存堆里申请一块能装一个 `LNode` 的空间,并把这块空间的首地址赋给指针 `s`。
通俗讲:造出一个"空结点",让 `s` 指向它。
2. `s->data = e;`
把要插入的元素值 `e` 放进这个新结点的数据域。
3. `s->next = p->next;`
让新结点 `s` 的指针域先指向 `p` 当前的后继结点。
换句话说:先把 `p` 原来的"下一跳"借过来,变成 `s` 的"下一跳",这样后面那条链就不会丢。
4. `p->next = s;`
最后把 `p` 的指针域改成指向 `s`,完成"插队"------从此 `p` 后面不再是旧结点,而是新结点 `s`。
画一条链更直观:
插入前:
... → p → A → ...
插入后:
... → p → s → A → ...
步骤 3、4 的顺序不能反,如果先执行 `p->next = s` 再执行 `s->next = p->next`,就会把 `A` 的地址丢掉,造成断链。
解释2:
cpp
//尾插法建表,方便测试
void CreateListTail(LinkList& L, int n) {
InitList(L);
LNode* r = L;
cout << "请依次输入" << n << "个元素:";
for (int i = 0;i < n;i++) {
ElemType x;
cin >> x;
LNode* s = (LNode*)malloc(sizeof(LNode));
s->data = x;
s->next = NULL;
r -> next = s;
r = s;
}
}
这段代码干的事就是:
"从键盘读 n 个整数,用尾插法把它们依次接成一条带头结点的单链表。"
逐句直译:
1. `InitList(L);`
先给链表弄个头结点,里面不放数据,`L->next` 现在是 `nullptr`。
2. `LNode* r = L;`
`r` 叫"尾指针",永远指向当前链表的最后一个结点。一开始链表只有头结点,所以 `r` 就是 `L`。
3. `for (int i = 0; i < n; ++i)`
循环 `n` 次,每次读一个数。
4. `cin >> x;`
从键盘读一个整数到 `x`。
5. `LNode* s = (LNode*)malloc(sizeof(LNode));`
在堆里新生成一个空结点,让 `s` 指向它。
6. `s->data = x;`
把刚读到的整数塞进新结点的数据域。
7. `s->next = nullptr;`
新结点后面暂时没东西,所以先指向空。
8. `r->next = s;`
把新结点接在尾指针 `r` 的后面。
9. `r = s;`
尾指针往前挪一步,现在 `r` 又指向"最新"的最后一个结点了。
循环结束后,链表顺序就是键盘输入的顺序,头结点还在最前面,不保存有效数据。
解释3:
cpp
//打印链表
void PrintList(LinkList L) {
LNode* p = L->next;
cout << "当前链表为:";
while (p) {
cout << p->data << (p->next ? "->" : "");
p = p->next;
}
cout << endl;
}
一句话:
"跳过头结点,把链表里的有效数据按顺序打印成 `a -> b -> c` 这种形式。"
逐句解释:
1. `LNode* p = L->next;`
头结点不存数据,从第一个真正结点开始。
2. `while (p)`
只要当前结点存在就继续。
3. `cout << p->data`
输出当前结点的数据。
4. `(p->next ? " -> " : "")`
如果后面还有结点,就输出箭头,否则什么都不输出(避免末尾多一个 `->`)。
5. `p = p->next;`
指针往后挪一个。
2.单链表的按位序插入(不带头结点)

如果不带头结点,则插入,删除第1个元素时,需要更改头指针L;
cpp
#include <iostream>
#include <cstdlib>
using namespace std;
using ElemType = int;
typedef struct LNode {
ElemType data;
struct LNode* next;
}LNode,*LinkList;
bool ListInsert(LinkList& L, int i, ElemType e) {
if (i < 1) {
return false; //非法位序
}
LNode* p = L; //p指向头结点
int j = 0; //j表示p的位序(头结点算0)
while (p != NULL && j < i - 1) { // 找到第 i-1 个结点
p = p->next;
j++;
}
if (p == NULL) { // i 过大,链表不够长
return false;
}
LNode* s = (LNode*)malloc(sizeof(LNode));
s->data = e;
s->next = p->next;
p->next = s;
return true;
}
/* 辅助:初始化一个空链表(只含头结点) */
void InitList(LinkList& L) {
L = (LNode*)malloc(sizeof(LNode));
L->next = nullptr;
}
//尾插法建表,方便测试
void CreateListTail(LinkList& L, int n) {
InitList(L);
LNode* r = L;
cout << "请依次输入" << n << "个元素:";
for (int i = 0;i < n;i++) {
ElemType x;
cin >> x;
LNode* s = (LNode*)malloc(sizeof(LNode));
s->data = x;
s->next = NULL;
r -> next = s;
r = s;
}
}
//打印链表
void PrintList(LinkList L) {
LNode* p = L->next;
cout << "当前链表为:";
while (p) {
cout << p->data << (p->next ? "->" : "");
p = p->next;
}
cout << endl;
}
/* 测试 */
int main() {
LinkList L;
InitList(L); // 空链表
/* 手动插入几个元素 */
ListInsert(L, 1, 10); // 第 1 位插入 10
ListInsert(L, 2, 20); // 第 2 位插入 20
ListInsert(L, 1, 5); // 再插到最前面
PrintList(L); // 应输出:5 -> 10 -> 20
/* 交互式测试 */
int pos, val;
cout << "请输入要插入的位序 和 元素值(位序<1结束):";
while (cin >> pos >> val) {
if (pos < 1) break;
if (ListInsert(L, pos, val))
PrintList(L);
else
cout << "插入失败,位序非法或内存不足!" << endl;
}
return 0;
}
运行结果:

3.单链表的删除
1.按位序删除(删除第 i 个结点,数据带回)
cpp
bool ListDelete(LinkList &L, int i, ElemType &e){
if (i < 1) return false; // 位序非法
LNode *p = L; // p 指向头结点(位序 0)
int j = 0;
while (p && j < i - 1){ // 找第 i-1 个结点
p = p->next;
++j;
}
if (!p || !p->next) return false; // i 过大或已到表尾
LNode *q = p->next; // q 指向待删结点(第 i 个)
e = q->data; // 带回数据
p->next = q->next; // 跨过待删结点
free(q);
return true;
}
2. 指定结点删除(偷后继法,O(1),p 不能是尾结点)
cpp
bool DeleteNode(LNode *p){
if (!p || !p->next) return false; // p 为空或是尾结点则失败
LNode *q = p->next; // q 是后继
p->data = q->data; // 数据搬上来
p->next = q->next; // 跨过后继
free(q);
return true;
}
使用示例:
cpp
LinkList L;
InitList(L); // 建带头结点的空表
/* ...建表... */
int x;
if (ListDelete(L, 3, x)) cout << "被删元素=" << x << endl;
LNode *p = GetElem(L, 2); // 拿到已知结点
if (DeleteNode(p)) cout << "指定结点已删除" << endl;