链表也是线性表的一种。
链表是一种物理存储结构上非连续、非顺序的存储结构,数据元素的逻辑顺序是通过链表中的指针链接次序实现的 。
链表的结构跟火车车厢相似,每节车厢都是独立存在的,需要将火车的某节车厢去掉/加上,不会影响其他车厢。
与顺序表不同的是,链表里的每节"车厢"都是独立申请下来的空间,我们称之为“结点/节点”。
节点的组成主要有两个部分:当前节点要保存的数据和保存下一个节点的地址(指针变量)。
图中指针变量 plist保存的是第一个节点的地址,我们称plist此时“指向”第一个节点,如果我们希望plist“指向”第二个节点时,只需要修改plist保存的内容为0x0012FFA0。
为什么还需要指针变量来保存下一个节点的位置?
链表中每个节点都是独立申请的(即需要插入数据时才去申请一块节点的空间),我们需要通过指针变量来保存下一个节点位置才能从当前节点找到下一个节点。
链表节点的结构
struct SListNode
{
int data;
struct SListNode* next;
}
当我们想要保存一个整型数据时,实际是向操作系统申请了一块内存,这个内存不仅要保存整型数据,也需要保存下一个节点的地址(当下一个节点为空时保存的地址为空)。
当我们想要从第一个节点走到最后一个节点时,只需要在前一个节点拿上下一个节点的地址(下一个节点的钥匙)就可以了。
在test.c
文件中
先要用malloc
向操作系统申请一块空间,然后让第一个节点的数据等于1.
SLNode* node1 = (SLNode*)malloc(sizeof(SLNode));
node1->data = 1;
然后用同样的方法再申请三个节点
SLNode* node1 = (SLNode*)malloc(sizeof(SLNode));
node1->data = 1;
SLNode* node2 = (SLNode*)malloc(sizeof(SLNode));
node2->data = 2;
SLNode* node3 = (SLNode*)malloc(sizeof(SLNode));
node3->data = 3;
SLNode* node4 = (SLNode*)malloc(sizeof(SLNode));
node4->data = 4;
用图展示就是这样
然后让node1
保存node2
的地址,再让node2
保存node3
的地址,再让node3
保存node4
的地址,最后让node4
的next置为NULL
。
用代码实现
node1->next = node2;
node2->next = node3;
node3->next = node4;
node4->next = NULL;
先定义一个变量让它指向第一个节点。
SLNode* plist = node1;
SLPrint(plist);
将链表的地址传过去。
然后在构造打印函数,因为之后可能还涉及到链表的打印,所以定义了一个SList.c
用来存储链表所用到的函数。
这里用循环来实现打印,用可以用phead
直接来访问。但是注意,当代码走到第14行的时候,此时phead
已经变成NULL
了。若代码没写完,我还要继续使用指向第一个节点的地址时,这时我就找不到第一个节点的地址。
所以用一个新的指针变量phead
来存储。
void SLPrint(SLNode* phead)
{
SLNode* pcur = phead;
while (pcur)
{
printf("%d ->", pcur->data);
pcur = pcur->next;
}
printf("NULL\n");
}
在实现链表尾插时我们需要考虑两点:
1.链表为空时,申请新节点,新节点作为头节点。
2.当链表不为空时,申请新节点,原链表中的为节点的next
指针指向新节点的地址。
通过指针pcur = pcur->next
,跳往下一个节点。
void SLPushBack(SLNode** pphead, SLDataType x) {
SLNode* node = SLBuyNode(x);
if (*pphead == NULL)
{
*pphead = node;
return;
}
//说明链表不为空,找尾
SLNode* pcur = *pphead;
while (pcur->next)
{
pcur = pcur->next;
}
pcur->next = node;
}
在链表第一个节点之前插入x=4。
先让node
的next
保存phead
,再让4成为新的头节点。
而当链表为空时也同样适用。
void SLPushFront(SLNode** pphead, SLDataType x)
{
assert(pphead);
SLNode* node = SLBuyNode(x);
//新节点跟头结点连接起来
node->next = *pphead;//plist
//让新的节点成为头结点
*pphead = node;
}
要实现链表的尾删除,首先要找到尾节点。
用cur
指针找,还需要记录尾节点的前面一个节点用prev
记录。然后将prev
的next
置为NULL
。
void SLPopBack(SLNode** pphead)
{
assert(pphead);//第一个节点不能为空
assert(*pphead);//只有一个节点的情况
if ((*pphead)->next == NULL)
{
//直接把头结点删除
free(*pphead);
*pphead = NULL;
}
else
{
//有多个节点的情况
//找尾结点和尾结点的前一个节点
SLNode* prev = NULL;
SLNode* ptail = *pphead;
while (ptail->next != NULL)
{
prev = ptail;
ptail = ptail->next;
}
//prev的next指针不再指向ptail,而是指向ptail的下一个节点
prev->next = ptail->next;
free(ptail);
//为什么必须要置为空?代码后面明明没有再使用ptail?打印链表方法里有判断节点的地址是否为空
ptail = NULL;
}
}
首先使用临时节点的指针指向头节点,然后将头节点指向新的头,最后把临时指针指向的节点释放掉。
当只有一个节点时也可以处理
void SLPopFront(SLNode** pphead)
{
assert(pphead);
assert(*pphead);
SLNode* del = *pphead;
*pphead = (*pphead)->next;
free(del);
del = NULL;//出于代码规范
}
在pos前插入一个x=11,先找到pos
前的一个节点,然后处理2和3。将prev->next=node
;再将node->next=pos
。
注意在这里prev->next=node
和node->next=pos
,位置可以互换,因为3的位置已经用pos
来记录了,不会造成丢失。
不考虑链表为空时的情况。
当链表里面只有一个节点时,就相当于pos
刚好是头结点,实行头插。
//在指定位置之前插入数据
void SLInsert(SLNode** pphead, SLNode* pos, SLDataType x)
{
assert(pphead);
//约定链表不能为空,pos也不能为空
assert(pos);
assert(*pphead);
SLNode* node = SLBuyNode(x);
//pos刚好是头结点,头插
if (pos == *pphead) {
node->next = *pphead;
*pphead = node;
return;
}
//找pos的前一个节点
SLNode* prev = *pphead;
while (prev->next != pos)//第一次循环的时候(缺少pos刚好是第一个节点的判断)
{
prev = prev->next;
}
//prev node pos
node->next = pos;
prev->next = node;
}
与链表的节点前面插入不同的是,prev->next=node
和node->next=pos
,位置不可以互换,因为没有记录3节点的位置。
所以必须先执行node->next=pos
再执行prev->next=node
。
void SLInsertAfter(SLNode* pos, SLDataType x) {
assert(pos);
SLNode* node = SLBuyNode(x);
//pos node pos->next
node->next = pos->next;
pos->next = node;
}
pos
位置节点首先我们要先找到pos节点和找pos的前一个节点prev
。执行prev->next = pos->nex
t,再实现free(pos)
。
void SLErase(SLNode** pphead, SLNode* pos)
{
assert(pphead);
assert(*pphead);
assert(pos);
if (pos == *pphead) {
*pphead = (*pphead)->next;
free(pos);
return;
}
//找pos的前一个节点
SLNode* prev = *pphead;
while (prev->next != pos)
{
prev = prev->next;
}
//prev pos pos->next
prev->next = pos->next;
free(pos);
pos = NULL;//只是规范
}
先判断pos
及pos
的下一个节点是否为空。
用del
存储pos
的下一个节点,然后执行pos->next = del->next
;最后free(del)
。
void SLEraseAfter(SLNode* pos)
{
assert(pos && pos->next);
SLNode* del = pos->next;
pos->next = del->next;
free(del);
}
通过循环从前往后依次进行,而在销毁节点之前先把下一个个节点保存下来。
void SLDesTroy(SLNode** pphead) {
assert(pphead);
SLNode* pcur = *pphead;
//循环删除
while (pcur)
{
SLNode* next = pcur->next;
free(pcur);
pcur = next;
}
*pphead = NULL;
}
#pragma once
#include
#include
#include
#include
//定义链表节点的结构
typedef int SLDataType;
typedef struct SListNode {
SLDataType data;//要保存的数据
struct SListNode* next;
}SLNode;
//我来创建几个结点组成一个链表,并打印链表
void SLPrint(SLNode* phead);
//尾插
void SLPushBack(SLNode** pphead, SLDataType x);
void SLPushFront(SLNode** pphead, SLDataType x);
//删除
void SLPopBack(SLNode** pphead);
void SLPopFront(SLNode** pphead);
//找节点,这里的第一个参数写一级指针还是二级指针
//这里传一级实际上就可以了,因为不改变头结点
//但是这里要写二级指针,因为要保持接口一致性
SLNode* SLFind(SLNode** pphead, SLDataType x);
//指定位置的插入删除
//在指定位置之前插入数据
void SLInsert(SLNode** pphead, SLNode* pos, SLDataType x);
//在指定位置之后插入数据
void SLInsertAfter(SLNode* pos, SLDataType x);
//删除pos节点
void SLErase(SLNode** pphead, SLNode* pos);
//删除pos之后节点
void SLEraseAfter(SLNode* pos);
//销毁链表
void SLDesTroy(SLNode** pphead);
#include"SList.h"
void SLPrint(SLNode* phead) {
//循环打印
//可以用phead直接来访问,但是注意,当代码走到
//第14行的时候,此时phead已经变成NULL了
//若代码没写完,我还要继续使用指向第一个节点的地址时,这时我就
//找不到第一个节点的地址
SLNode* pcur = phead;
while (pcur != NULL)
{
printf("%d ->", pcur->data);
pcur = pcur->next;
}
printf("NULL\n");
}
SLNode* SLBuyNode(SLDataType x) {
SLNode* node = (SLNode*)malloc(sizeof(SLNode));
node->data = x;
node->next = NULL;
return node;
}
//尾插
void SLPushBack(SLNode** pphead, SLDataType x) {
assert(pphead);
SLNode* node = SLBuyNode(x);
if (*pphead == NULL) {
*pphead = node;
return;
}
//说明链表不为空,找尾
SLNode* pcur = *pphead;
while (pcur->next)
{
pcur = pcur->next;
}
pcur->next = node;
}
void SLPushFront(SLNode** pphead, SLDataType x) {
assert(pphead);
SLNode* node = SLBuyNode(x);
//新节点跟头结点连接起来
node->next = *pphead;//plist
//让新的节点成为头结点
*pphead = node;
}
void SLPopBack(SLNode** pphead) {
assert(pphead);
//第一个节点不能为空
assert(*pphead);
//只有一个节点的情况
if ((*pphead)->next == NULL) {
//直接把头结点删除
free(*pphead);
*pphead = NULL;
}
else {
//有多个节点的情况
//找尾结点和尾结点的前一个节点
SLNode* prev = NULL;
SLNode* ptail = *pphead;
while (ptail->next != NULL)
{
prev = ptail;
ptail = ptail->next;
}
//prev的next指针不再指向ptail,而是指向ptail的下一个节点
prev->next = ptail->next;
free(ptail);
//为什么必须要置为空?代码后面明明没有再使用ptail?打印链表方法里有判断节点的地址是否为空
ptail = NULL;
}
}
void SLPopFront(SLNode** pphead) {
assert(pphead);
assert(*pphead);
SLNode* del = *pphead;
*pphead = (*pphead)->next;
free(del);
del = NULL;//出于代码规范
}
//查找第一个为X的节点
SLNode* SLFind(SLNode** pphead, SLDataType x) {
assert(pphead);
SLNode* pcur = *pphead;
while (pcur)
{
if (pcur->data == x) {
return pcur;
}
pcur = pcur->next;
}
return NULL;
}
//在指定位置之前插入数据
void SLInsert(SLNode** pphead, SLNode* pos, SLDataType x) {
assert(pphead);
//约定链表不能为空,pos也不能为空
assert(pos);
assert(*pphead);
SLNode* node = SLBuyNode(x);
//pos刚好是头结点,头插
if (pos == *pphead) {
node->next = *pphead;
*pphead = node;
return;
}
//找pos的前一个节点
SLNode* prev = *pphead;
while (prev->next != pos)//第一次循环的时候(缺少pos刚好是第一个节点的判断)
{
prev = prev->next;
}
//prev node pos
node->next = pos;
prev->next = node;
}
//在指定位置之后插入数据
void SLInsertAfter(SLNode* pos, SLDataType x) {
assert(pos);
SLNode* node = SLBuyNode(x);
//pos node pos->next
node->next = pos->next;
pos->next = node;
}
//删除pos节点
void SLErase(SLNode** pphead, SLNode* pos) {
assert(pphead);
assert(*pphead);
assert(pos);
if (pos == *pphead) {
*pphead = (*pphead)->next;
free(pos);
return;
}
//找pos的前一个节点
SLNode* prev = *pphead;
while (prev->next != pos)
{
prev = prev->next;
}
//prev pos pos->next
prev->next = pos->next;
free(pos);
pos = NULL;//只是规范
}
//删除pos之后节点
void SLEraseAfter(SLNode* pos) {
assert(pos && pos->next);
SLNode* del = pos->next;
pos->next = del->next;
free(del);
}
//销毁链表
void SLDesTroy(SLNode** pphead) {
assert(pphead);
SLNode* pcur = *pphead;
//循环删除
while (pcur)
{
SLNode* next = pcur->next;
free(pcur);
pcur = next;
}
*pphead = NULL;
}
#include"SList.h"
void slttest() {
SLNode* node1 = (SLNode*)malloc(sizeof(SLNode));
node1->data = 1;
SLNode* node2 = (SLNode*)malloc(sizeof(SLNode));
node2->data = 2;
SLNode* node3 = (SLNode*)malloc(sizeof(SLNode));
node3->data = 3;
SLNode* node4 = (SLNode*)malloc(sizeof(SLNode));
node4->data = 4;
node1->next = node2;
node2->next = node3;
node3->next = node4;
node4->next = NULL;
//打印链表
SLNode* plist = node1;
SLPrint(plist);
}
void slttest01() {
SLNode* plist = NULL;
//尾插
SLPushBack(&plist, 1);
SLPushBack(&plist, 2);
SLPushBack(&plist, 3);
SLPushBack(&plist, 4);//1->2->3->4->NULL
SLPrint(plist);
//SLPushFront(&plist, 1);
//SLPushFront(&plist, 2);
//SLPushFront(&plist, 3);
//SLPushFront(&plist, 4);//4->3->2->1->NULL
//尾删
//SLPopBack(&plist);
//SLPopBack(&plist);
//SLPopBack(&plist);
//SLPopBack(&plist);
//头删
//SLPopFront(&plist);
//SLPopFront(&plist);
//SLPopFront(&plist);
//SLPopFront(&plist);
//SLPopFront(&plist);
//SLNode* find = SLFind(&plist, 4);
//SLInsert(&plist, find,11);//1->11->2->3->4->NULL
//在指定位置之后插入数据
//SLInsertAfter(find, 100);
//删除pos位置的节点
//SLErase(&plist, find);//1->2->3->NULL
//删除pos之后的节点
//SLEraseAfter(find);
//销毁链表
SLDesTroy(&plist);
SLPrint(plist);
}
int main() {
//slttest();
slttest01();
return 0;
}