线性表
线性表常用操作与算法代码总结(C 语言版)
王道《数据结构》第二章线性表
线性表位序从1开始,数组下标从0开始;链表默认采用带头结点写法。
一、公共头文件
#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
C 语言动态内存管理:
LNode *p = (LNode *)malloc(sizeof(LNode));
free(p);
malloc申请失败时返回NULL。申请的结点使用完后必须用free释放。
二、顺序表
1. 静态顺序表定义
#define MAX_SIZE 100
typedef struct {
int data[MAX_SIZE];
int length;
} SqList;
2. 动态顺序表定义
#define INIT_SIZE 100
typedef struct {
int *data;
int length;
int maxSize;
} SeqList;
3. 初始化
静态顺序表
void InitSqList(SqList *L) {
L->length = 0;
}
调用:
SqList L;
InitSqList(&L);
动态顺序表
bool InitSeqList(SeqList *L) {
L->data = (int *)malloc(INIT_SIZE * sizeof(int));
if (L->data == NULL)
return false;
L->length = 0;
L->maxSize = INIT_SIZE;
return true;
}
4. 动态顺序表扩容
bool IncreaseSize(SeqList *L, int increase) {
if (increase <= 0)
return false;
int newSize = L->maxSize + increase;
int *newData = (int *)malloc(newSize * sizeof(int));
if (newData == NULL)
return false;
for (int i = 0; i < L->length; i++)
newData[i] = L->data[i];
free(L->data);
L->data = newData;
L->maxSize = newSize;
return true;
}
时间复杂度:\(O(n)\)。
5. 判空与求表长
bool SqListEmpty(const SqList *L) {
return L->length == 0;
}
int SqListLength(const SqList *L) {
return L->length;
}
6. 按位查找
bool GetElem(const SqList *L, int i, int *e) {
if (i < 1 || i > L->length)
return false;
*e = L->data[i - 1];
return true;
}
核心关系:
时间复杂度:\(O(1)\)。
7. 按值查找
int LocateElem(const SqList *L, int e) {
for (int i = 0; i < L->length; i++) {
if (L->data[i] == e)
return i + 1;
}
return 0;
}
时间复杂度:\(O(n)\)。
8. 插入元素
在第 i 个位置插入 e,合法范围为:
bool ListInsert(SqList *L, int i, int e) {
if (i < 1 || i > L->length + 1)
return false;
if (L->length >= MAX_SIZE)
return false;
for (int j = L->length; j >= i; j--)
L->data[j] = L->data[j - 1];
L->data[i - 1] = e;
L->length++;
return true;
}
插入时从后向前移动,防止原元素被覆盖。
9. 删除元素
bool ListDelete(SqList *L, int i, int *e) {
if (i < 1 || i > L->length)
return false;
*e = L->data[i - 1];
for (int j = i; j < L->length; j++)
L->data[j - 1] = L->data[j];
L->length--;
return true;
}
删除时从前向后移动。
10. 遍历
void PrintSqList(const SqList *L) {
for (int i = 0; i < L->length; i++)
printf("%d ", L->data[i]);
printf("\n");
}
11. 销毁动态顺序表
void DestroySeqList(SeqList *L) {
free(L->data);
L->data = NULL;
L->length = 0;
L->maxSize = 0;
}
三、单链表
1. 结点定义
typedef struct LNode {
int data;
struct LNode *next;
} LNode, *LinkList;
其中 LinkList 等价于 LNode *。
2. 初始化带头结点单链表
bool InitList(LinkList *L) {
*L = (LNode *)malloc(sizeof(LNode));
if (*L == NULL)
return false;
(*L)->next = NULL;
return true;
}
调用:
LinkList L;
InitList(&L);
注意两层指针:
L是头指针变量;&L是头指针变量的地址;- 函数中用
*L修改外部头指针。
3. 判空与求表长
bool ListEmpty(LinkList L) {
return L->next == NULL;
}
int ListLength(LinkList L) {
int length = 0;
LNode *p = L->next;
while (p != NULL) {
length++;
p = p->next;
}
return length;
}
4. 按位查找
带头结点时,头结点看作第 0 个结点。
LNode *GetElemNode(LinkList L, int i) {
if (i < 0)
return NULL;
LNode *p = L;
int j = 0;
while (p != NULL && j < i) {
p = p->next;
j++;
}
return p;
}
GetElemNode(L, 0)返回头结点;GetElemNode(L, 1)返回第一个数据结点;- 时间复杂度为 \(O(n)\)。
5. 按值查找
LNode *LocateElemNode(LinkList L, int e) {
LNode *p = L->next;
while (p != NULL && p->data != e)
p = p->next;
return p;
}
6. 在指定结点后插入
bool InsertNextNode(LNode *p, int e) {
if (p == NULL)
return false;
LNode *s = (LNode *)malloc(sizeof(LNode));
if (s == NULL)
return false;
s->data = e;
s->next = p->next;
p->next = s;
return true;
}
核心顺序:
s->next = p->next;
p->next = s;
7. 按位序插入
在第 i 个位置插入,需要先找到第 i-1 个结点。
bool LinkListInsert(LinkList L, int i, int e) {
if (i < 1)
return false;
LNode *p = GetElemNode(L, i - 1);
if (p == NULL)
return false;
return InsertNextNode(p, e);
}
8. 只知道结点 p 时实现前插
bool InsertPriorNode(LNode *p, int e) {
if (p == NULL)
return false;
LNode *s = (LNode *)malloc(sizeof(LNode));
if (s == NULL)
return false;
s->next = p->next;
p->next = s;
s->data = p->data;
p->data = e;
return true;
}
本质:先在 p 后插,再交换数据,时间复杂度为 \(O(1)\)。
9. 删除指定结点的后继
bool DeleteNextNode(LNode *p, int *e) {
if (p == NULL || p->next == NULL)
return false;
LNode *q = p->next;
*e = q->data;
p->next = q->next;
free(q);
return true;
}
10. 按位序删除
bool LinkListDelete(LinkList L, int i, int *e) {
if (i < 1)
return false;
LNode *p = GetElemNode(L, i - 1);
if (p == NULL)
return false;
return DeleteNextNode(p, e);
}
11. 只知道结点 p 时删除该结点
bool DeleteNode(LNode *p) {
if (p == NULL || p->next == NULL)
return false;
LNode *q = p->next;
p->data = q->data;
p->next = q->next;
free(q);
return true;
}
此方法不能删除尾结点,因为尾结点没有后继。
12. 头插法建立单链表
bool ListHeadInsert(LinkList *L, int n) {
if (!InitList(L))
return false;
for (int i = 0; i < n; i++) {
int x;
scanf("%d", &x);
LNode *s = (LNode *)malloc(sizeof(LNode));
if (s == NULL)
return false;
s->data = x;
s->next = (*L)->next;
(*L)->next = s;
}
return true;
}
输入顺序与链表最终顺序相反,时间复杂度为 \(O(n)\)。
13. 尾插法建立单链表
bool ListTailInsert(LinkList *L, int n) {
if (!InitList(L))
return false;
LNode *rear = *L;
for (int i = 0; i < n; i++) {
int x;
scanf("%d", &x);
LNode *s = (LNode *)malloc(sizeof(LNode));
if (s == NULL)
return false;
s->data = x;
s->next = NULL;
rear->next = s;
rear = s;
}
return true;
}
输入顺序与链表最终顺序相同,时间复杂度为 \(O(n)\)。
14. 遍历单链表
void PrintList(LinkList L) {
LNode *p = L->next;
while (p != NULL) {
printf("%d ", p->data);
p = p->next;
}
printf("\n");
}
15. 销毁单链表
void DestroyList(LinkList *L) {
LNode *p;
while (*L != NULL) {
p = *L;
*L = (*L)->next;
free(p);
}
}
四、双链表
1. 结点定义
typedef struct DNode {
int data;
struct DNode *prior;
struct DNode *next;
} DNode, *DLinkList;
2. 初始化
bool InitDLinkList(DLinkList *L) {
*L = (DNode *)malloc(sizeof(DNode));
if (*L == NULL)
return false;
(*L)->prior = NULL;
(*L)->next = NULL;
return true;
}
3. 在 p 后插入结点 s
bool InsertNextDNode(DNode *p, DNode *s) {
if (p == NULL || s == NULL)
return false;
s->next = p->next;
if (p->next != NULL)
p->next->prior = s;
s->prior = p;
p->next = s;
return true;
}
4. 删除 p 的后继结点
bool DeleteNextDNode(DNode *p, int *e) {
if (p == NULL || p->next == NULL)
return false;
DNode *q = p->next;
*e = q->data;
p->next = q->next;
if (q->next != NULL)
q->next->prior = p;
free(q);
return true;
}
5. 删除已知结点 p
bool DeleteDNode(DNode *p) {
if (p == NULL || p->prior == NULL)
return false;
p->prior->next = p->next;
if (p->next != NULL)
p->next->prior = p->prior;
free(p);
return true;
}
已知待删除结点地址时,双链表删除的时间复杂度为 \(O(1)\)。
五、循环链表
1. 循环单链表初始化
bool InitCircularList(LinkList *L) {
*L = (LNode *)malloc(sizeof(LNode));
if (*L == NULL)
return false;
(*L)->next = *L;
return true;
}
2. 循环单链表判空、判尾和遍历
bool CircularListEmpty(LinkList L) {
return L->next == L;
}
bool IsTail(LinkList L, LNode *p) {
return p->next == L;
}
void PrintCircularList(LinkList L) {
LNode *p = L->next;
while (p != L) {
printf("%d ", p->data);
p = p->next;
}
printf("\n");
}
普通单链表遍历到
NULL结束;带头结点循环单链表重新回到L时结束。
3. 循环双链表初始化
bool InitCircularDList(DLinkList *L) {
*L = (DNode *)malloc(sizeof(DNode));
if (*L == NULL)
return false;
(*L)->prior = *L;
(*L)->next = *L;
return true;
}
4. 循环双链表插入和删除
bool InsertNextCircularDNode(DNode *p, DNode *s) {
if (p == NULL || s == NULL)
return false;
s->next = p->next;
p->next->prior = s;
s->prior = p;
p->next = s;
return true;
}
bool DeleteNextCircularDNode(DLinkList L, DNode *p, int *e) {
if (p == NULL || p->next == L)
return false;
DNode *q = p->next;
*e = q->data;
p->next = q->next;
q->next->prior = p;
free(q);
return true;
}
六、静态链表
1. 定义
#define S_MAX_SIZE 100
typedef struct {
int data;
int next;
} SNode;
typedef SNode SLinkList[S_MAX_SIZE];
next 保存的是下一个结点的数组下标,而不是真实地址。通常用 -1 表示链尾。
2. 遍历
void PrintSLinkList(SLinkList L, int first) {
int p = first;
while (p != -1) {
printf("%d ", L[p].data);
p = L[p].next;
}
printf("\n");
}
3. 插入和删除
void InsertNextSNode(SLinkList L, int p, int s) {
L[s].next = L[p].next;
L[p].next = s;
}
int DeleteNextSNode(SLinkList L, int p) {
int q = L[p].next;
if (q == -1)
return -1;
L[p].next = L[q].next;
return q;
}
静态链表删除结点后,还应按照具体实现把下标
q归还给备用链表。
七、高频算法题模板
1. 删除顺序表中所有值为 x 的元素
用 k 记录保留下来的元素个数。
void DeleteAllX(SqList *L, int x) {
int k = 0;
for (int i = 0; i < L->length; i++) {
if (L->data[i] != x)
L->data[k++] = L->data[i];
}
L->length = k;
}
时间复杂度 \(O(n)\),空间复杂度 \(O(1)\)。
2. 删除有序顺序表中的重复元素
bool DeleteSame(SqList *L) {
if (L->length == 0)
return false;
int k = 0;
for (int i = 1; i < L->length; i++) {
if (L->data[i] != L->data[k]) {
k++;
L->data[k] = L->data[i];
}
}
L->length = k + 1;
return true;
}
3. 逆置顺序表
void ReverseSqList(SqList *L) {
for (int i = 0; i < L->length / 2; i++) {
int temp = L->data[i];
L->data[i] = L->data[L->length - 1 - i];
L->data[L->length - 1 - i] = temp;
}
}
4. 合并两个递增顺序表
bool MergeSqList(const SqList *A, const SqList *B, SqList *C) {
if (A->length + B->length > MAX_SIZE)
return false;
int i = 0, j = 0, k = 0;
while (i < A->length && j < B->length) {
if (A->data[i] <= B->data[j])
C->data[k++] = A->data[i++];
else
C->data[k++] = B->data[j++];
}
while (i < A->length)
C->data[k++] = A->data[i++];
while (j < B->length)
C->data[k++] = B->data[j++];
C->length = k;
return true;
}
时间复杂度:\(O(m+n)\)。
5. 删除单链表中所有值为 x 的结点
void DeleteAllXNode(LinkList L, int x) {
LNode *p = L;
while (p->next != NULL) {
if (p->next->data == x) {
LNode *q = p->next;
p->next = q->next;
free(q);
} else {
p = p->next;
}
}
}
删除成功后
p不后移,因为新的p->next仍可能等于x。
6. 单链表逆置——头插法
void ReverseByHeadInsert(LinkList L) {
LNode *p = L->next;
L->next = NULL;
while (p != NULL) {
LNode *q = p->next;
p->next = L->next;
L->next = p;
p = q;
}
}
7. 单链表逆置——三指针法
void ReverseByPointers(LinkList L) {
LNode *pre = NULL;
LNode *cur = L->next;
while (cur != NULL) {
LNode *next = cur->next;
cur->next = pre;
pre = cur;
cur = next;
}
L->next = pre;
}
8. 查找倒数第 k 个结点
LNode *FindLastK(LinkList L, int k) {
if (k <= 0)
return NULL;
LNode *fast = L->next;
LNode *slow = L->next;
for (int i = 0; i < k; i++) {
if (fast == NULL)
return NULL;
fast = fast->next;
}
while (fast != NULL) {
fast = fast->next;
slow = slow->next;
}
return slow;
}
9. 查找中间结点
LNode *FindMiddle(LinkList L) {
LNode *slow = L->next;
LNode *fast = L->next;
while (fast != NULL && fast->next != NULL) {
slow = slow->next;
fast = fast->next->next;
}
return slow;
}
当结点数为偶数时,此写法返回两个中间结点中的后一个。
10. 判断单链表是否有环
bool HasCycle(LinkList L) {
LNode *slow = L;
LNode *fast = L;
while (fast != NULL && fast->next != NULL) {
slow = slow->next;
fast = fast->next->next;
if (slow == fast)
return true;
}
return false;
}
11. 寻找环的入口结点
LNode *FindCycleEntry(LinkList L) {
LNode *slow = L;
LNode *fast = L;
do {
if (fast == NULL || fast->next == NULL)
return NULL;
slow = slow->next;
fast = fast->next->next;
} while (slow != fast);
slow = L;
while (slow != fast) {
slow = slow->next;
fast = fast->next;
}
return slow;
}
12. 合并两个递增单链表,结果仍递增
复用原有结点,以 A 的头结点作为结果表头。
LinkList MergeListAscending(LinkList A, LinkList B) {
LNode *pa = A->next;
LNode *pb = B->next;
LNode *tail = A;
while (pa != NULL && pb != NULL) {
if (pa->data <= pb->data) {
tail->next = pa;
pa = pa->next;
} else {
tail->next = pb;
pb = pb->next;
}
tail = tail->next;
}
tail->next = (pa != NULL) ? pa : pb;
free(B);
return A;
}
13. 合并两个递增单链表,结果递减
比较两表当前较小结点,并将其头插到 A 中。
LinkList MergeListDescending(LinkList A, LinkList B) {
LNode *pa = A->next;
LNode *pb = B->next;
A->next = NULL;
while (pa != NULL && pb != NULL) {
LNode *p;
if (pa->data <= pb->data) {
p = pa;
pa = pa->next;
} else {
p = pb;
pb = pb->next;
}
p->next = A->next;
A->next = p;
}
LNode *remain = (pa != NULL) ? pa : pb;
while (remain != NULL) {
LNode *p = remain;
remain = remain->next;
p->next = A->next;
A->next = p;
}
free(B);
return A;
}
14. 删除递增单链表中的重复结点
void DeleteDuplicate(LinkList L) {
LNode *p = L->next;
while (p != NULL && p->next != NULL) {
if (p->data == p->next->data) {
LNode *q = p->next;
p->next = q->next;
free(q);
} else {
p = p->next;
}
}
}
15. 将单链表拆分成奇数位表和偶数位表
A保留原链表奇数位结点;B保存原链表偶数位结点;- 两表中的相对次序均保持不变。
bool SplitByPosition(LinkList A, LinkList *B) {
*B = (LNode *)malloc(sizeof(LNode));
if (*B == NULL)
return false;
(*B)->next = NULL;
LNode *tailA = A;
LNode *tailB = *B;
LNode *p = A->next;
int position = 1;
A->next = NULL;
while (p != NULL) {
LNode *next = p->next;
p->next = NULL;
if (position % 2 == 1) {
tailA->next = p;
tailA = p;
} else {
tailB->next = p;
tailB = p;
}
position++;
p = next;
}
return true;
}
八、复杂度速查表
1. 基本操作
| 操作 | 顺序表 | 单链表 | 双链表 |
|---|---|---|---|
| 按位访问 | \(O(1)\) | \(O(n)\) | \(O(n)\) |
| 按值查找 | \(O(n)\) | \(O(n)\) | \(O(n)\) |
| 按位序插入 | \(O(n)\) | \(O(n)\) | \(O(n)\) |
| 按位序删除 | \(O(n)\) | \(O(n)\) | \(O(n)\) |
| 已知结点后插 | — | \(O(1)\) | \(O(1)\) |
| 已知前驱删除后继 | — | \(O(1)\) | \(O(1)\) |
| 已知待删除结点 | — | 通常 \(O(n)\) | \(O(1)\) |
链表插入、删除只有在相关结点地址已经给出时才是 \(O(1)\);如果还要按位查找,总复杂度仍为 \(O(n)\)。
2. 高频算法
| 算法 | 时间复杂度 | 额外空间 |
|---|---|---|
删除所有值为 x 的元素 |
\(O(n)\) | \(O(1)\) |
| 顺序表逆置 | \(O(n)\) | \(O(1)\) |
| 单链表逆置 | \(O(n)\) | \(O(1)\) |
查找倒数第 k 个结点 |
\(O(n)\) | \(O(1)\) |
| 查找中间结点 | \(O(n)\) | \(O(1)\) |
| 判断是否有环 | \(O(n)\) | \(O(1)\) |
| 合并两个有序表 | \(O(m+n)\) | \(O(1)\) |
| 删除有序表重复元素 | \(O(n)\) | \(O(1)\) |
| 拆分单链表 | \(O(n)\) | \(O(1)\) |
九、C 语言指针参数速记
1. 只读取或修改头结点之后的链接
传入 LinkList L 即可:
void ReverseByHeadInsert(LinkList L);
void DeleteAllXNode(LinkList L, int x);
因为虽然形参 L 是指针的副本,但 L 和外部头指针仍指向同一个头结点。
2. 需要改变头指针本身
必须传入 LinkList *L:
bool InitList(LinkList *L);
void DestroyList(LinkList *L);
调用时使用:
InitList(&L);
DestroyList(&L);
3. 用参数返回一个普通整数
传入 int *e:
int value;
ListDelete(&L, 2, &value);
函数内部通过 *e = ... 修改调用者的变量。
十、常用指针操作口诀
1. 单链表后插
s->next = p->next;
p->next = s;
先让新结点接后面,再让前面接新结点。
2. 单链表删除后继
q = p->next;
p->next = q->next;
free(q);
先保存,后断链,再释放。
3. 双链表后插
s->next = p->next;
p->next->prior = s;
s->prior = p;
p->next = s;
4. 双链表删除后继
q = p->next;
p->next = q->next;
q->next->prior = p;
free(q);
普通双链表必须判断 q->next 是否为 NULL;循环双链表不需要。
十一、高频易错点
-
C 语言没有引用参数,不能写
SqList &L或int &e。 -
修改结构体变量本身时传地址:
void InitSqList(SqList *L); InitSqList(&L); -
结构体指针用
->,结构体变量用.。 -
C 语言使用
malloc/free,不使用new/delete。 -
malloc后要判断是否为NULL。 -
free(p)后不能继续访问p指向的内存。 -
顺序表第
i个元素对应数组下标i-1。 -
顺序表插入的合法位置为 \(1\le i\le n+1\)。
-
顺序表删除的合法位置为 \(1\le i\le n\)。
-
顺序表插入从后向前移动,删除从前向后移动。
-
带头结点普通单链表的空表条件:
L->next == NULL -
不带头结点单链表的空表条件:
L == NULL -
带头结点循环单链表的空表条件:
L->next == L -
普通单链表尾结点条件为
p->next == NULL;循环单链表尾结点条件为p->next == L。 -
删除链表结点前必须先保存相关地址,否则可能断链。
-
删除结点后必须
free;仅修改指针会造成内存泄漏。 -
单链表已知结点
p的地址,不代表已经知道它的前驱。 -
单链表“复制后继数据删除自身”的方法不能用于尾结点。
-
头插法建立链表会逆序;尾插法保持输入顺序。
-
动态顺序表扩容后仍然是顺序存储,元素地址仍连续。

浙公网安备 33010602011771号