一. 链表的定义
链表是一种物理存储结构上非连续的线性存储结构, 其每个元素都是一个节点对象, 每个节点由指针域和数据域组成, 各个节点之间通过指针连接, 从当前节点通过指针可以访问到下一个节点. 由于当前节点的指针记录了下一个节点的内存地址, 因此无需保证内存地址的连续性, 从而可以将各个节点分散存储在内存各处.
二. 链表的分类
链表可以以三种基准分类.
1. 单向或者双向
2. 带头或者不带头 (哨兵位)
3. 循环或者非循环
组合起来就有8种链表结构.
三. 单链表各种接口的实现
typedef int SLTDataType;
typedef struct SListNode
{
SLTDataType data; // 数据域
struct SListNode* next; // 指针域
}SLTNode;
1. 打印
我们使用 for 循环来打印链表, 每次打印完当前节点的 data 后, 通过 next 指针前往下一个节点. 当走到链表尾部 (即 curr 指针指向空节点), 跳出循环, 打印结束.
void SLTPrint(SLTNode* phead)
{
SLTNode* curr = phead;
while (curr != NULL) {
printf("%d->", curr->data); // 打印当前节点的 data
curr = curr->next; // 前往下一个节点
}
printf("NULL\n");
}
2. 动态申请一个节点
使用 malloc 函数在堆区上动态开辟一个链表节点, data 赋值为 x , next 指针置空.
SLTNode* BuySLTNode(SLTDataType x)
{
SLTNode* newnode = (SLTNode*)malloc(sizeof(SLTNode));
if (newnode == NULL) {
perror("malloc fail");
return NULL;
}
newnode->data = x;
newnode->next = NULL;
return newnode;
}
3. 尾插
尾插, 即在链表的尾节点后插入一个新节点.
要进行尾插, 我们需要找到链表的尾节点, 将新节点的地址赋给尾节点的 next 指针.
void SLTPushBack(SLTNode** pphead, SLTDataType x)
{
assert(pphead);
SLTNode* newnode = BuySLTNode(x);
// 链表为空
if (*pphead == NULL) {
*pphead = newnode;
} else { // 链表不为空
// 找尾节点
SLTNode* tail = *pphead;
while (tail->next != NULL) {
tail = tail->next;
}
tail->next = newnode;
}
}
4. 头插
头插, 即在链表的头节点前插入一个新节点.
Tip: 由以下两张示意图可以看出, 无论链表是否为空, 以下代码都适用.
void SLTPushFront(SLTNode** pphead, SLTDataType x)
{
assert(pphead);
SLTNode* newnode = BuySLTNode(x);
newnode->next = *pphead;
*pphead = newnode;
}
5. 尾删
尾删, 即删除链表的尾节点.
void SLTPopBack(SLTNode** pphead)
{
assert(pphead);
assert(*pphead); // 判断链表是否为空表, 若为空表, 则无法对其进行尾删操作
// 链表只有一个节点
if ((*pphead)->next == NULL) {
free(*pphead);
*pphead = NULL;
} else { // 链表有多个节点
// 找尾节点的前一个节点
SLTNode* tailPrev = *pphead;
while (tailPrev->next->next != NULL) {
tailPrev = tailPrev->next;
}
free(tailPrev->next);
tailPrev->next = NULL;
}
}
6. 头删
头删, 删除链表的头节点.
void SLTPopFront(SLTNode** pphead)
{
assert(pphead);
assert(*pphead); // 判断链表是否为空表, 若为空表, 则无法对其进行头删操作
SLTNode* first = *pphead;
*pphead = first->next;
free(first);
}
7. 查找
查找到存放数据 x 的节点的地址, 若查找不到, 返回 NULL .
SLTNode* SLTFind(SLTNode* phead, SLTDataType x)
{
SLTNode* curr = phead;
while (curr != NULL) {
if (curr->data == x) {
return curr;
}
curr = curr->next;
}
return NULL;
}
8. pos 之前插入
在内存地址为 pos 的节点前插入一个新节点.
void SLTInsertBefore(SLTNode** pphead, SLTNode* pos, SLTDataType x)
{
assert(pphead);
assert(pos); // 该函数不能用来尾插, 所以 pos 不能为空
if (pos == *pphead) {
SLTPushFront(pphead, x);
} else { // 找到 pos 的前一个位置
SLTNode* prev = *pphead;
while (prev->next != pos) {
prev = prev->next;
}
SLTNode* newnode = BuySLTNode(x);
prev->next = newnode;
newnode->next = pos;
}
}
9. pos 位置删除
删除内存地址为 pos 的节点.
void SLTErase(SLTNode** pphead, SLTNode* pos)
{
assert(pphead);
assert(pos); // 不能删除空节点
if (pos == *pphead) {
SLTPopFront(pphead);
} else { // 找到 pos 的前一个位置
SLTNode* prev = *pphead;
while (prev->next != pos) {
prev = prev->next;
}
prev->next = pos->next;
free(pos);
}
}
10. pos 之后插入
在内存地址为 pos 的节点后插入一个新节点.
void SLTInsertAfter(SLTNode* pos, SLTDataType x)
{
assert(pos);
SLTNode* newnode = BuySLTNode(x);
newnode->next = pos->next;
pos->next = newnode;
}
11. pos 之后删除
删除内存地址为 pos 的节点的后一个节点.
void SLTEraseAfter(SLTNode* pos)
{
assert(pos);
assert(pos->next);
SLTNode* del = pos->next;
pos->next = del->next;
free(del);
}
12. 销毁
从头节点开始, 一一释放链表中的每一个节点, 最后通过 pphead 将 phead 置空, 完成对链表的销毁.
void SLTDestroy(SLTNode** pphead)
{
assert(pphead);
SLTNode* curr = *pphead;
while (curr != NULL) {
SLTNode* temp = curr->next;
free(curr);
curr = temp;
}
*pphead = NULL;
}
四. 单链表的源码
SList.h
#pragma once
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
typedef int SLTDataType;
typedef struct SListNode
{
SLTDataType data; // 数据域
struct SListNode* next; // 指针域
}SLTNode;
// 打印
void SLTPrint(SLTNode* phead);
// 动态申请一个节点
SLTNode* BuySLTNode(SLTDataType x);
// 尾插
void SLTPushBack(SLTNode** pphead, SLTDataType x);
// 头插
void SLTPushFront(SLTNode** pphead, SLTDataType x);
// 尾删
void SLTPopBack(SLTNode** pphead);
// 头删
void SLTPopFront(SLTNode** pphead);
// 查找
SLTNode* SLTFind(SLTNode* phead, SLTDataType x);
// pos之前插入
void SLTInsertBefore(SLTNode** pphead, SLTNode* pos, SLTDataType x);
// pos位置删除
void SLTErase(SLTNode** pphead, SLTNode* pos);
// pos之后插入
void SLTInsertAfter(SLTNode* pos, SLTDataType x);
// pos之后删除
void SLTEraseAfter(SLTNode* pos);
// 销毁
void SLTDestroy(SLTNode** pphead);
SList.c
#include "SList.h"
void SLTPrint(SLTNode* phead)
{
SLTNode* curr = phead;
while (curr != NULL) {
printf("%d->", curr->data); // 打印当前节点的 data
curr = curr->next; // 前往下一个节点
}
printf("NULL\n");
}
SLTNode* BuySLTNode(SLTDataType x)
{
SLTNode* newnode = (SLTNode*)malloc(sizeof(SLTNode));
if (newnode == NULL) {
perror("malloc fail");
return NULL;
}
newnode->data = x;
newnode->next = NULL;
return newnode;
}
void SLTPushBack(SLTNode** pphead, SLTDataType x)
{
assert(pphead);
SLTNode* newnode = BuySLTNode(x);
// 链表为空
if (*pphead == NULL) {
*pphead = newnode;
} else { // 链表不为空
// 找尾节点
SLTNode* tail = *pphead;
while (tail->next != NULL) {
tail = tail->next;
}
tail->next = newnode;
}
}
void SLTPushFront(SLTNode** pphead, SLTDataType x)
{
assert(pphead);
SLTNode* newnode = BuySLTNode(x);
newnode->next = *pphead;
*pphead = newnode;
}
void SLTPopBack(SLTNode** pphead)
{
assert(pphead);
assert(*pphead); // 判断链表是否为空表, 若为空表, 则无法对其进行尾删操作
// 链表只有一个节点
if ((*pphead)->next == NULL) {
free(*pphead);
*pphead = NULL;
} else { // 链表有多个节点
// 找尾节点的前一个节点
SLTNode* tailPrev = *pphead;
while (tailPrev->next->next != NULL) {
tailPrev = tailPrev->next;
}
free(tailPrev->next);
tailPrev->next = NULL;
}
}
void SLTPopFront(SLTNode** pphead)
{
assert(pphead);
assert(*pphead); // 判断链表是否为空表, 若为空表, 则无法对其进行头删操作
SLTNode* first = *pphead;
*pphead = first->next;
free(first);
}
SLTNode* SLTFind(SLTNode* phead, SLTDataType x)
{
SLTNode* curr = phead;
while (curr != NULL) {
if (curr->data == x) {
return curr;
}
curr = curr->next;
}
return NULL;
}
void SLTInsertBefore(SLTNode** pphead, SLTNode* pos, SLTDataType x)
{
assert(pphead);
assert(pos); // 该函数不能用来尾插, 所以 pos 不能为空
if (pos == *pphead) {
SLTPushFront(pphead, x);
} else { // 找到 pos 的前一个位置
SLTNode* prev = *pphead;
while (prev->next != pos) {
prev = prev->next;
}
SLTNode* newnode = BuySLTNode(x);
prev->next = newnode;
newnode->next = pos;
}
}
void SLTErase(SLTNode** pphead, SLTNode* pos)
{
assert(pphead);
assert(pos); // 不能删除空节点
if (pos == *pphead) {
SLTPopFront(pphead);
} else { // 找到 pos 的前一个位置
SLTNode* prev = *pphead;
while (prev->next != pos) {
prev = prev->next;
}
prev->next = pos->next;
free(pos);
}
}
void SLTInsertAfter(SLTNode* pos, SLTDataType x)
{
assert(pos);
SLTNode* newnode = BuySLTNode(x);
newnode->next = pos->next;
pos->next = newnode;
}
void SLTEraseAfter(SLTNode* pos)
{
assert(pos);
assert(pos->next);
SLTNode* del = pos->next;
pos->next = del->next;
free(del);
}
void SLTDestroy(SLTNode** pphead)
{
assert(pphead);
SLTNode* curr = *pphead;
while (curr != NULL) {
SLTNode* temp = curr->next;
free(curr);
curr = temp;
}
*pphead = NULL;
}