根据提供的图片内容,整理链表核心知识点笔记如下:
定义:通过指针串联节点的线性结构,每个节点包含数据域和指向后继节点的指针。
typedef struct LNode {
ElemType data; //数据域
struct LNode *next; //指针域(指向后继结点)
} LNode, *LinkList; // LinkList为单链表头指针类型
特性:
L->next == NULL
,操作统一L == NULL
,首元结点需特殊处理bool ListInsert(LinkList &L, int i, ElemType e) {
if (i < 1) return false; // 位序合法性校验
LNode *p = L; // p指向头结点
int j = 0; // 当前位序(头结点为0)
while (p && j < i-1) { // 寻找第 i-1 个结点
p = p->next; j++;
}
if (!p) return false; // i超出表长
LNode *s = (LNode*)malloc(sizeof(LNode));
s->data = e;
s->next = p->next; // 新结点后继指向原第i个结点
p->next = s; // 前驱结点指向新结点
return true;
}
时间复杂度:O(n)
bool InsertNextNode(LNode *p, ElemType e) {
if (!p) return false;
LNode *s = (LNode*)malloc(sizeof(LNode));
if (!s) return false; // 内存分配失败
s->data = e;
s->next = p->next;
p->next = s;
return true;
}
时间复杂度:O(1)
bool ListDelete(LinkList &L, int i, ElemType &e) {
if (i < 1) return false;
LNode *p = L; 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指向待删除结点
e = q->data;
p->next = q->next; // 绕过q结点
free(q);
return true;
}
时间复杂度:O(n)
bool DeleteNode(LNode *p) {
if (!p || !p->next) return false; // 仅限非尾结点
LNode *q = p->next; // q指向p的后继
p->data = q->data; // 数据域覆盖(偷天换日)
p->next = q->next;
free(q);
return true;
}
注:尾结点需遍历找前驱,时间复杂度O(n)
LNode* GetElem(LinkList L, int i) {
if (i < 0) return NULL; // 位序非法
LNode *p = L; int j = 0;
while (p && j < i) { // 扫描到第i个结点
p = p->next; j++;
}
return p;
}
LNode* LocateElem(LinkList L, ElemType e) {
LNode *p = L->next; // 跳过头结点
while (p && p->data != e) p = p->next;
return p; // 找到返回结点,否则返回NULL
}
int Length(LinkList L) {
int len = 0;
LNode *p = L->next; // 从首元结点开始
while (p) {
len++; p = p->next;
}
return len;
}
时间复杂度:O(n)
void CreateList_Head(LinkList &L) {
L = (LNode*)malloc(sizeof(LNode)); // 创建头结点
L->next = NULL;
ElemType x;
while (scanf("%d", &x) != EOF) {
LNode *s = (LNode*)malloc(sizeof(LNode));
s->data = x;
s->next = L->next; // 新结点指向原首元结点
L->next = s; // 头结点指向新结点
}
}
应用:链表逆置
void CreateList_Tail(LinkList &L) {
L = (LNode*)malloc(sizeof(LNode));
LNode *r = L; // 尾指针
ElemType x;
while (scanf("%d", &x) != EOF) {
LNode *s = (LNode*)malloc(sizeof(LNode));
s->data = x;
r->next = s; // 尾结点指向新结点
r = s; // 更新尾指针
}
r->next = NULL; // 尾结点置空
}
优缺点:
优点:动态分配内存,空间利用率高,插入删除操作方便(只需修改指针)
缺点:仅支持单向遍历,查找前驱节点需从头遍历
定义:每个节点包含数据域、前驱指针(prior)和后继指针(next),支持双向遍历。
typedef struct DNode {
ElemType data;
struct DNode *prior, *next; // 前驱和后继指针
} DNode, *DLinkList;
bool InitDLinkList(DLinkList &L) {
L = (DNode*)malloc(sizeof(DNode));
if (!L) return false;
L->prior = NULL; // 头结点前驱永为NULL
L->next = NULL; // 空表
return true;
}
空表判断:L->next == NULL
bool InsertNextDNode(DNode *p, DNode *s) {
if (!p || !s) return false;
s->next = p->next;
if (p->next) p->next->prior = s; // 后继非空时修改其前驱
s->prior = p;
p->next = s;
return true;
}
关键:处理边界(尾结点插入时后继为NULL)
bool DeleteNextDNode(DNode *p) {
if (!p || !p->next) return false;
DNode *q = p->next; // 待删除结点
p->next = q->next;
if (q->next) q->next->prior = p; // 若q非尾结点
free(q);
return true;
}
void DestroyList(DLinkList &L) {
while (L->next) DeleteNextDNode(L); // 逐个删除数据结点
free(L); L = NULL; // 释放头结点
}
while (p) { p = p->next; }
while (p->prior) { p = p->prior; }
(跳过头结点)循环单链表
next
指向头结点L->next == L
(带头结点)循环双链表
prior
指向尾结点,尾结点 next
指向头结点L->next == L && L->prior == L
优缺点
优点:从任意节点可遍历整个链表,尾节点操作更高效(无需遍历找尾)
缺点:需注意循环终止条件,避免死循环
定义:首尾相连的链表,分为循环单链表(尾节点 next 指向头节点)和循环双链表(尾节点 next 指向头节点,头节点 prior 指向尾节点)。
#define MAXSIZE 100
typedef struct {
ElemType data;
int next; // 游标(模拟指针)
} SLinkList[MAXSIZE];
特点:
next=-1
表示表尾优缺点:
优点:无需指针,通过数组下标操作,适合嵌入式等无动态内存的环境
缺点:长度固定(受 MaxSize 限制),插入删除需移动大量元素
类型 | 特点 | 优势 | 劣势 |
---|---|---|---|
单链表 | 单向链接 | 节省空间 | 逆向访问困难 |
双链表 | 双向指针 | 支持双向遍历 | 空间开销大 |
循环链表 | 首尾相连 | 任意位置出发遍历全表 | 判空条件复杂 |
静态链表 | 数组实现,游标代替指针 | 无内存分配开销 | 长度固定,灵活性低 |
核心要点:
next
游标逻辑链接,物理存储连续