1 链表的概念及结构
概念:链表是一种物理存储结构上非连续、非顺序的存储结构,数据元素的逻辑顺序中的逻辑顺序是通过链表中的指针链接次序实现的
。
链表的结构跟火车车厢很相似,在淡季的时候车厢会相应的减少,在旺季的时候车厢会额外增加几节。只需要将火车里的某节车厢去掉或者加上,不会影响其他的车厢,每节车厢都是独立存在的。
车厢独立存在,并且在每节车厢之间连接的过道都有一扇车门。假设有以下情景:每节车厢的车门都是上锁的状态,需要不同的钥匙才能解锁,每次只能携带一把钥匙的情况下如何从车头走到车尾呢?
最简单的方法当然是每一节车厢中都放了下一节车厢的门钥匙。
现在我们将这列火车放在链表中,那这列火车的“车厢”又是什么样子的呢?
与顺序表不同的是,链表里的每节“车厢”都是独立申请下来的空间,我们称之为“节点”。节点的组成主要有两个部分:当前节点保存的数据和保存下一个节点的地址(指针变量)。
图中指针变量plist保存的是第一个节点的地址,我们称plist此时“指向”第一个节点,若我们希望plist“指向”第二个节点时,只需要修改plist保存的内容为0x0012AAA0
为什么还需要指针变量来保存下一个节点的位置?
链表中的每个节点都是独立申请的(即需要插入数据是才去申请一块节点的空间),我们需要通过指针变量来保存下一个节点的位置才能从当前节点找到下一个节点。
结合结构体知识,我们可以给出每个节点对应的结构体代码:
假设当前保存的节点为整型:
struct SListNode
{
int data; //节点数据
struct SListNode* next; //指针变量用于保存下一个节点的地址
};
当我们想要保存一个整型数据时,实际是向操作系统中申请了一块内存,这个内存不仅要保存整型数据,也要保存下一个节点的地址(当下一个节点为空时保存的地址为空)。
当我们想要从第一个“车厢”走到最后一个“车厢”时,只需要在前一个“车厢”拿上下一个“车厢”的钥匙(下一个节点的地址)就可以了。
补充说明:
- 链式结构在逻辑上是连续的,在物理结构上不一定连续
- 节点一般都是从堆上申请的
- 从堆上申请来的空间,是按照一定策略分配出来的,每次申请的空间可能连续,也可能不连续
2 单链表的实现
SList.h 定义单链表的结构,单链表要实现的接口/方法
SList.c 具体实现单链表里定义的接口/方法
test.c 测试单链表
2.1 准备工作
SList.h
#include<stdio.h>
#include<stdlib.h>
#include<assert.h>
typedef int SLTDataType;
//链表是由节点组成
//错误
//typedef struct SListNode
//{
// SLTDataType data;
// SLTNode* next; //编译器是向上编译的,在以上代码中无法找到定义SLTNOde的代码,所以这里不能直
//接这样写
//}SLTNode;
//正确
typedef struct SListNode
{
SLTDataType data;
struct SListNode* next;
}SLTNode;
2.2 打印单链表
SList.h
void SLTPrint(SLTNode* phead);
SList.c
#include"SList.h"
void SLTPrint(SLTNode* phead)
{
SLTNode* pcur = phead; //这里创建零时变量而不直接用phead是因为在这个函数结束时phead指向是
while(pcur) //NULL,后面使用phead的时候就无法找到第一个节点了
{
printf("%d->", pcur->data);
pcur = pcur->next;
}
printf("NULL\n");
}
test.c
#include"SList.h"
void SlistTest()
{
//一般情况下不会这样创建链表,这里只是为了展示链表的打印
SLTNode* node1 = (SLTNode*)malloc(sizeof(SLTNode));
node1->data = 2;
SLTNode* node2 = (SLTNode*)malloc(sizeof(SLTNode));
node1->data = 3;
SLTNode* node1 = (SLTNode*)malloc(sizeof(SLTNode));
node1->data = 3;
SLTNode* node4 = (SLTNode*)malloc(sizeof(SLTNode));
node1->data = 4;
node1->next = node2;
node2->next = node3;
node3->next = node4;
node4->next = NULL;
SLTNode* plist = node1;
SLPrint(plist);
}
int main()
{
SlistTest();
return 0;
}
打印结果为
1->2->3->4->NULL
2.3 链表的尾插
尾插会出现两种情况
- 链表为空,新节点作为phead
- 链表不为空,找尾节点
SList.h
void SLTPushBack(SLTNode* phead, SLTDataType x);
错误示范
SList.c
void SLTPushBack(SLTNode* phead, SLTDataType x)
{
SLTNode* newnode = (SLTNode*)malloc(sizeof(SLTNode));
newnode->data = x;
newnode->next = NULL;
//链表为空,新节点作为phead
if(phead == NULL)
{
phead = newnode;
return;
}
//链表不为空,找尾节点
SLTNode* ptail = phead;
while(ptail->next)
{
ptail = ptail->next;
}
//ptail就是尾节点
ptail->next = newnode;
}
test.c
void SlistTest()
{
SLTNode* plist = NULL;
SLTPushBack(plist, 1);
SLTPrint(plist); //预期的打印结果尾为 1->NULL 而实际打印结果为NULL
} //对于plist来说这里的NULL是一个值,一级指针plist也有一个地址
//这里的SLTPushBack(plist, 1);传的是值而不是传的地址
int main()
{
SlistRest();
return 0;
}
正确示范
SList.h
void SLTPushBack(SLTNode** phead, SLTDataType x);
SList.c
//申请新节点的方法
SLTNode* SLTBuyNode(SLTDatatypex)
{
SLTNode* newnode = (SLTNode*)malloc(sizeof(SLTNode));
newnode->data = x;
newnode->next = NULL;
return newnode;
}
void SLTPushBack(SLTNode** phead, SLTDataType x) //必须用二级指针接收一级指针的地址
{
assert(pphead);
SLTNode* newnode = SLTBuyNOde(x);
//链表为空,新节点作为phead
if(*pphead == NULL)
{
*pphead = newnode;
return;
}
//链表不为空,找尾节点
SLTNode* ptail = *pphead;
while(ptail->next)
{
ptail = ptail->next;
}
//ptail就是尾节点
ptail->next = newnode;
}
test.c
void SlistTest()
{
SLTNode* plist = NULL;
SLTPushBack(&plist, 1);
SLTPushBack(&plist, 2);
SLTPushBack(&plist, 3);
SLTPushBack(&plist, 4);
SLTPushBack(&plist, 9);
SLTPushBack(&plist, 6);
SLTPrint(plist);
}
int main()
{
SlistRest();
return 0;
}
输出结果为
1->2->3->4->9->6->NULL
2.4 链表的头插
头插也会出现两种情况
- 链表为空,新节点作为phead
- 链表不为空,找尾节点
SList.h
void SLTPushFront(SLTNode** pphead, SLTDaraType x);
SList.c
void SLTPushFront(SLTNode** pphead, SLTDaraType x)
{
assert(pphead);
SLTNode* newnode = SLTBuyNode(x);
nwenode->next = *pphead; //让新的节点存原本头节点的地址
*pphead = newnode; //让新的节点变成新的头节点
}
test.c
void SlistTest()
{
SLTNode* plist = NULL;
SLTPushBack(&plist, 1);
SLTPushBack(&plist, 2);
SLTPushBack(&plist, 3);
SLTPushBack(&plist, 4);
SLTPrint(plist);
SLTPushFront(&plist, 7);
SLTPrint(plist);
SLTPushFront(&plist, 8);
SLTPrint(plist);
SLTPushFront(&plist, 9);
SLTPrint(plist);
}
int main()
{
SlistRest();
return 0;
}
输出结果为
1->2->3->4->NULL
7->1->2->3->4->NULL
8->7->1->2->3->4->NULL
9->8->7->1->2->3->4->NULL
2.5 链表的尾删
SList.h
void SLTPopBack(SLTNode** pphead);
SList.c
void SLTPopBack(SLTNode** pphead)
{
assert(pphead);
//链表不能为空
asssert(*pphead);
//链表不为空
//链表只有一个节点
if((*pphead)->next == NULL)
{
free(*pphead);
*pphead = NULL;
return;
}
//链表有多个节点
SLTNode* ptail = *pphead;
SLTNode* prev = NULL;
while(ptail->next)
{
prev = ptail;
ptail = ptail->next;
}
prev->next = NULL;
//销毁尾节点
free(ptail);
ptail = NULL;
)
test.c
void SlistTest()
{
SLTNode* plist = NULL;
SLTPushBack(&plist, 1);
SLTPushBack(&plist, 2);
SLTPushBack(&plist, 3);
SLTPushBack(&plist, 4);
SLTPrint(plist);
SLTPopBack(&plist);
SLTPrint(plist);
SLTPopBack(&plist);
SLTPrint(plist);
SLTPopBack(&plist);
SLTPrint(plist);
SLTPopBack(&plist);
SLTPrint(plist);
}
int main()
{
SlistRest();
return 0;
}
输出结果为
1->2->3->4->NULL
1->2->3->NULL
1->2->NULL
1->NULL
NULL
2.6 链表的头删
SList.h
void SLTPopFront(SLTNode** pphead);
SList.c
void SLTPopFront(SLTNode** pphead)
{
assert(pphead);
//链表不能为空
assert(*pphead);
//让第二个节点成为新的头,把旧的头节点释放掉
SLTNode* next = (*pphead)->next; //定义了一个零时的指针指向第二个节点
//->的优先级高于*,所以要加括号
free(*pphead); //把pphead直接释放掉
*pphead = next; //再让pphead指向next指向的这个节点
}
test.c
void SlistTest()
{
SLTNode* plist = NULL;
SLTPushBack(&plist, 1);
SLTPushBack(&plist, 2);
SLTPushBack(&plist, 3);
SLTPushBack(&plist, 4);
SLTPrint(plist);
SLTPopFront(&plist);
SLTPrint(plist);
SLTPopFront(&plist);
SLTPrint(plist);
SLTPopFront(&plist);
SLTPrint(plist);
SLTPopFront(&plist);
SLTPrint(plist);
}
int main()
{
SlistRest();
return 0;
}
输出结果为
1->2->3->4->NULL
2->3->4->NULL
3->4->NULL
4->NULL
NULL
2.7 查找链表中的数据
SList.h
SLTNode* SLTFind(SLTNode** pphead, SLTDataType x);
SList.c
SLTNode* SLTFind(SLTNode** pphead, SLTDataType x)
{
assert(pphead);
//遍历链表
SLTNode* pcur = *pphead;
while(pcur) //等价于pcur != NULL
{
if(pcur->data = x)
{
return pcur;
}
pcur = pcur->next;
}
//没有找到
return NULL;
}
test.c
void SlistTest()
{
SLTNode* plist = NULL;
SLTPushBack(&plist, 1);
SLTPushBack(&plist, 2);
SLTPushBack(&plist, 3);
SLTPushBack(&plist, 4);
SLTPrint(plist);
SLTNode* FindRet1 = SLTFind(&plist,1);
if(FindRet1)
{
printf("找到啦~\n");
}
else
{
printf("没有找到哦~\n");
}
SLTNode* FindRet2 = SLTFind(&plist,3);
if(FindRet2)
{
printf("找到啦~\n");
}
else
{
printf("没有找到哦~\n");
}
SLTNode* FindRet3 = SLTFind(&plist,love);
if(FindRet3)
{
printf("找到啦~\n");
}
else
{
printf("没有找到哦~\n");
}
}
int main()
{
SlistRest();
return 0;
}
输出结果为
1->2->3->4->NULL
找到啦~
找到啦~
没有找到哦~
2.8 指定位置之前插入节点
SList.h
void SLTInsert(SLTNode** pphead, SLTNode* pos, SLTDataType x);
SList.c
void SLTInsert(SLTNode** pphead, SLTNode* pos, SLTDataType x)
{
assert(pphead);
assert(pos);
//要加上链表不能为空,因为链表为空pos就也为空,这样就无法找到前驱节点了
assert(*pphead);
SLTNode* newnode = SLTBuyNode(x);
//pos刚好就是头节点
if(pos == *pphead)
{
//头插即可
SLTPushFront(ppphead, x);
return;
}
//pos不是头节点
SLTNode* prev = *pphead;
while(prev->next != pos)
{
prev = prev->next;
}
prev->next = newnode; //前驱节点存的地址指向新插入的节点
newnode->next = pos; //新插入的节点存的地址指向pos指向的节点
}
test.c
void SlistTest()
{
SLTNode* plist = NULL;
SLTPushBack(&plist, 1);
SLTPushBack(&plist, 3);
SLTPushBack(&plist, 1;
SLTPushBack(&plist, 4);
SLTPushBack(&plist, 666);
SLTPrint(plist);
SLTNode* FindRet1 = SLFind(&plist, 1);
SLTInsert(&plist, FindRet1, 520);
SLTPrint(plist);
SLTNode* FindRet = SLFind(&plist, 666);
SLTInsert(&plist, FindRet1, 666);
SLTPrint(plist);
}
int main()
{
SlistRest();
return 0;
}
输出结果为
1->3->1->4->666->NULL
520->1->3->1->4->666->NULL
520->1->3->1->4->666->666->NULL
2.9 指定位置之后插入节点
SList.h
void SLTInsertAfter(SLTNode* pos, SLTDataType x);
SList.c
void SLTInsertAfter(SLTNode* pos, SLTDataType x)
{
assert(pos);
SLTNode* nesnode = SLTBuyNode(x);
//错误写法
//pos->next = newnode; //顺序错误导致pos->next不再指向pos的下一个节点
//nwenode->next = pos->next; //此处相当于pos->next = pos->next
//正确写法
nwenode->next = pos->next;
pos->next = newnode;
}
test.c
void SlistTest()
{
SLTNode* plist = NULL;
SLTPushBack(&plist, 1);
SLTPushBack(&plist, 2);
SLTPushBack(&plist, 3);
SLTPushBack(&plist, 4);
SLTPrint(plist);
SLTNode* FindRet1 = SLFind(&plist, 1);
SLTInsertFront(&plist, FindRet1, 520);
SLTPrint(plist);
}
int main()
{
SlistRest();
return 0;
}
输出结果为
1->2->3->4->NULL
1->520->2->3->4->NULL
2.10 删除指定位置节点
SList.h
void SLTErase(SLTNode** pphead, SLTNode* pos);
SList.c
void SLTErase(SLTNode** pphead, SLTNode* pos)
{
assert(pphead);
assert(*pphead);
assert(pos);
//pos刚好是头节点,没有前驱节点,执行头插操作
if(*pphead == pos)
{
//头删
SLTPopFront(pphead);
return;
}
SLTNode* prev = *pphead;
while(prev->next != pos)
{
prev = prev->next;
}
prev->next = pos->next; //先转方向再销毁
free(pos);
pos = NULL;
}
test.c
void SlistTest()
{
SLTNode* plist = NULL;
SLTPushBack(&plist, 1);
SLTPushBack(&plist, 2);
SLTPushBack(&plist, 3);
SLTPushBack(&plist, 4);
SLTPushBack(&plist, 5);
SLTPrint(plist);
SLTNode* FindRet1 = SLFind(&plist, 1);
SLTErase(&plist, FindRet1);
SLTPrint(plist);
SLTNode* FindRet2 = SLFind(&plist, 5);
SLTErase(&plist, FindRet2);
SLTPrint(plist);
SLTNode* FindRet3 = SLFind(&plist, 3);
SLTErase(&plist, FindRet3);
SLTPrint(plist);
}
int main()
{
SlistRest();
return 0;
}
输出结果为
1->2->3->4->5->NULL
2->3->4->5->NULL
2->3->4->NULL
2->4->NULL
2.11 删除指定位置之后的节点
SList.h
void SLTEraseAfter(SLTNode* pos);
SList.c
void SLTEraseAfter(SLTNode* pos)
{
assert(pos);
//pos->next不能为空,为空就是尾节点了,就删不了了
assert(pos->next);
SLTNode* del = pos->next; //记录要删除节点的位置
pos->next = pos->next->next; //改变节点的指向
free(del); //释放原先记录的节点即要删除的节点
del = NULL;
}
test.c
void SlistTest()
{
SLTNode* plist = NULL;
SLTPushBack(&plist, 1);
SLTPushBack(&plist, 2);
SLTPushBack(&plist, 3);
SLTPushBack(&plist, 4);
SLTPushBack(&plist, 5);
SLTPrint(plist);
SLTNode* FindRet1 = SLFind(&plist, 1);
SLTEraseAfter(&plist, FindRet1);
SLTPrint(plist);
SLTNode* FindRet2 = SLFind(&plist, 3);
SLTEraseAfter(&plist, FindRet2);
SLTPrint(plist);
}
int main()
{
SlistRest();
return 0;
}
输出结果为
1->2->3->4->5->NULL
1->3->4->5->NULL
1->3->5->NULL
2.12 销毁链表
SList.h
void SListDesTroy(SLTNode** pphead);
SList.c
void SListDesTroy(SLTNode** pphead)
{
assert(pphead);
assert(*pphead);
SLTNode* pcur = *pphead;
while(pcur) //循环销毁
{
SLTNode* next = pcur->next; //记录当前节点的下一个节点的地址
free(pcur); //释放当前节点
pcur = next; //到下一节点
}
*pphead = NULL; //对头节点置NULL
}
3 整理单链表代码
SList.h
#include<stdio.h>
#include<stdlib.h>
#inlcude<assert.h>
typedef int SLTDataType;
//链表是由节点组成
//错误
//typedef struct SListNode
//{
// SLTDataType data;
// SLTNode* next; //编译器是向上编译的,在以上代码中无法找到定义SLTNOde的代码,所以这里不能直
//接这样写
//}SLTNode;
//正确
typedef struct SListNode
{
SLTDataType data;
struct SListNode* next;
}SLTNode;
//打印链表
void SLTPrint(SLTNode* phead);
//链表的头插和尾插
void SLTPushBack(SLTNode* phead, SLTDataType x);
void SLTPushFront(SLTNode** pphead, SLTDaraType x);
//链表的头删和尾删
void SLTPopBack(SLTNode** pphead);
void SLTPopFront(SLTNode** pphead);
//查找链表中的数据
SLTNode* SLTFind(SLTNode** pphead, SLTDataType x);
//指定位置之前插入节点
void SLTInsert(SLTNode** pphead, SLTNode* pos, SLTDataType x);
//指定位置之后插入节点
void SLTInsertAfter(SLTNode* pos, SLTDataType x);
//删除指定位置节点
void SLTErase(SLTNode** pphead, SLTNode* pos);
//删除指定位置之后的节点
void SLTEraseAfter(SLTNode* pos);
//销毁链表
void SListDesTroy(SLTNode** pphead);
SList.c
#include"SList.h"
//打印链表
void SLTPrint(SLTNode* phead)
{
SLTNode* pcur = phead; //这里创建零时变量而不直接用phead是因为在这个函数结束时phead指向是
while(pcur) //NULL,后面使用phead的时候就无法找到第一个节点了
{
printf("%d->", pcur->data);
pcur = pcur->next;
}
printf("NULL\n");
}
//链表的尾插
void SLTPushBack(SLTNode* phead, SLTDataType x)
{
SLTNode* newnode = (SLTNode*)malloc(sizeof(SLTNode));
newnode->data = x;
newnode->next = NULL;
//链表为空,新节点作为phead
if(phead == NULL)
{
phead = newnode;
return;
}
//链表不为空,找尾节点
SLTNode* ptail = phead;
while(ptail->next)
{
ptail = ptail->next;
}
//ptail就是尾节点
ptail->next = newnode;
}
//链表的头插
void SLTPushFront(SLTNode** pphead, SLTDaraType x)
{
assert(pphead);
SLTNode* newnode = SLTBuyNode(x);
nwenode->next = *pphead; //让新的节点存原本头节点的地址
*pphead = newnode; //让新的节点变成新的头节点
}
//链表的头删
void SLTPopFront(SLTNode** pphead)
{
assert(pphead);
//链表不能为空
assert(*pphead);
//让第二个节点成为新的头,把旧的头节点释放掉
SLTNode* next = (*pphead)->next; //定义了一个零时的指针指向第二个节点
//->的优先级高于*,所以要加括号
free(*pphead); //把pphead直接释放掉
*pphead = next; //再让pphead指向next指向的这个节点
}
//链表的尾删
void SLTPopBack(SLTNode** pphead)
{
assert(pphead);
//链表不能为空
asssert(*pphead);
//链表不为空
//链表只有一个节点
if((*pphead)->next == NULL)
{
free(*pphead);
*pphead = NULL;
return;
}
//链表有多个节点
SLTNode* ptail = *pphead;
SLTNode* prev = NULL;
while(ptail->next)
{
prev = ptail;
ptail = ptail->next;
}
prev->next = NULL;
//销毁尾节点
free(ptail);
ptail = NULL;
)
//查找链表中的数据
SLTNode* SLTFind(SLTNode** pphead, SLTDataType x)
{
assert(pphead);
//遍历链表
SLTNode* pcur = *pphead;
while(pcur) //等价于pcur != NULL
{
if(pcur->data = x)
{
return pcur;
}
pcur = pcur->next;
}
//没有找到
return NULL;
}
//指定位置之前插入节点
void SLTInsert(SLTNode** pphead, SLTNode* pos, SLTDataType x)
{
assert(pphead);
assert(pos);
//要加上链表不能为空,因为链表为空pos就也为空,这样就无法找到前驱节点了
assert(*pphead);
SLTNode* newnode = SLTBuyNode(x);
//pos刚好就是头节点
if(pos == *pphead)
{
//头插即可
SLTPushFront(ppphead, x);
return;
}
//pos不是头节点
SLTNode* prev = *pphead;
while(prev->next != pos)
{
prev = prev->next;
}
prev->next = newnode; //前驱节点存的地址指向新插入的节点
newnode->next = pos; //新插入的节点存的地址指向pos指向的节点
}
//指定位置之后插入节点
void SLTInsertAfter(SLTNode* pos, SLTDataType x)
{
assert(pos);
SLTNode* nesnode = SLTBuyNode(x);
//错误写法
//pos->next = newnode; //顺序错误导致pos->next不再指向pos的下一个节点
//nwenode->next = pos->next; //此处相当于pos->next = pos->next
//正确写法
nwenode->next = pos->next;
pos->next = newnode;
}
//删除指定位置节点
void SLTErase(SLTNode** pphead, SLTNode* pos)
{
assert(pphead);
assert(*pphead);
assert(pos);
//pos刚好是头节点,没有前驱节点,执行头插操作
if(*pphead == pos)
{
//头删
SLTPopFront(pphead);
return;
}
SLTNode* prev = *pphead;
while(prev->next != pos)
{
prev = prev->next;
}
prev->next = pos->next; //先转方向再销毁
free(pos);
pos = NULL;
}
//删除指定位置之后的节点
void SLTEraseAfter(SLTNode* pos)
{
assert(pos);
//pos->next不能为空,为空就是尾节点了,就删不了了
assert(pos->next);
SLTNode* del = pos->next; //记录要删除节点的位置
pos->next = pos->next->next; //改变节点的指向
free(del); //释放原先记录的节点即要删除的节点
del = NULL;
}
//销毁链表
void SListDesTroy(SLTNode** pphead)
{
assert(pphead);
assert(*pphead);
SLTNode* pcur = *pphead;
while(pcur) //循环销毁
{
SLTNode* next = pcur->next; //记录当前节点的下一个节点的地址
free(pcur); //释放当前节点
pcur = next; //到下一节点
}
*pphead = NULL; //对头节点置NULL
}