目录
预备知识
C语言常见概念,循环,数组,函数,指针,二级指针,结构体指针,动态内存管理
单链表的概念以及结构
在上期顺序表的详解后,解决了一部分数组无法解决的问题,但即使是动态顺序表,仍存在空间浪费的情况,为了使得空间利用最大化,就出现了链表,链表可以实现有多少数据就开辟多少空间,理论上不存在浪费的情况。链表由一个个节点构成,一个节点的数据包含该节点的数据和下一个节点的地址组成,当下个节点的地址为NULL时,则链表走到了尾部。这样,所有节点连接起来就形成了链表。区别于线性表,链表的逻辑结构上是线性的,但是在物理结构上并不是线性的。
单链表的实现
接下来将实现一下功能
//链表的打印
void SLTPrint(SLTNode* phead);
//链表的尾插
void SLTPushBack(SLTNode** pphead, SLDataType x);
//链表的头插
void SLTPushFront(SLTNode** pphead, SLDataType x);
//链表的尾删
void SLTPopBack(SLTNode** pphead);
//链表的头删
void SLTPopFront(SLTNode** pphead);
//链表元素的查找
SLTNode* SLTFind(SLTNode** pphead, SLDataType x);
//在指定位置之前插入数据
void SLTInsert(SLTNode** pphead, SLTNode* pos, SLDataType x);
//在指定位置之后插入数据
void SLTAfter(SLTNode* pos, SLDataType x);
//在指定位置删除数据
void SLTErase(SLTNode** pphead, SLTNode* pos);
//删除pos之后的一个节点
void SLTEraseAfter(SLTNode* pos);
//销毁链表
void SListDesTroy(SLTNode** pphead);
单链表的创建
链表的基本组成单位为节点,节点由两部分组成
一个是存储的数据,另一个为下一个节点的地址
typedef int SLDataType;
typedef struct SListNode
{
SLDataType data;
struct SListNode* next;//指向下一个节点
}SLTNode;
手动创建节点
我们创建一个test.c的测试文件
我们先手动创建几个节点
#include"SList.h"
void test01()
{
//创建四个节点
SLTNode* node1 = (SLTNode*)malloc(sizeof(SLDataType));
assert(node1);//预防malloc失败
node1->data = 1;
SLTNode* node2 = (SLTNode*)malloc(sizeof(SLDataType));
assert(node2);
node2->data = 2;
SLTNode* node3 = (SLTNode*)malloc(sizeof(SLDataType));
assert(node3);
node3->data = 3;
SLTNode* node4 = (SLTNode*)malloc(sizeof(SLDataType));
assert(node4);
node4->data = 4;
//首尾相连
node1->next = node2;
node2->next = node3;
node3->next = node4;
node4->next = NULL;
SLTNode* plist = node1;//创建一个链表使第一个节点为node1
SLTPrint(plist);
//销毁链表
SListDesTroy(&plist);
}
int main()
{
test01();
return 0;
}
在调试窗口,我们看见节点已经创建成功
我们手动创建了四个节点,并将节点首尾相接,那么我们先进行链表的打印
链表的打印
//链表的打印
void SLTPrint(SLTNode* phead)
{
assert(phead);
SLTNode* prev = phead;
while(prev)
{
printf("%d->", prev->data);
prev = prev->next;
}
printf("NULL\n");
}
- 首先对指针进行assert断言
- 然后创建一个临时的指针指向phead,一般不对phead直接操作,避免后期出现一些问题
- 使用while循环,判断prev是否为空指针,如果为空指针就说明遍历了链表,就会跳出while循环。
- 最后调用printf进行打印
我们运行test.c
链表中的元素就被打印出来了
链表的尾插
//创建新节点
SLTNode* SLTCreNode(SLDataType x)
{
SLTNode* newnode = (SLTNode*)malloc(sizeof(SLTNode));
//预防malloc失败
if (newnode == NULL)
{
perror("malloc failed");
exit(1);
}
newnode->data = x;
newnode->next = NULL;
return newnode;
}
//链表的尾插
void SLTPushBack(SLTNode* phead, SLDataType x)
{
assert(phead);
SLTNode* newnode = SLTCreNode(x);
if (phead == NULL)
{
phead = newnode;
}
else
{
//先找到尾部
SLTNode* prev = phead;
while (prev->next)
{
prev = prev->next;
}
prev->next = newnode;
}
}
- 链表尾插的第一步是创建一个新的节点,我们可以将创建节点单独写成一个函数,方便之后调用。
- 如果链表是空链表,直接将创建的节点赋给phead即可,如果不是空链表,我们首先要找到链表的尾部,如果prev->next为空指针,就说明prev已经是最后一个节点了,就找到尾部了
- 然后再将最后一个节点接上创建好的新节点newnode
此时我们对尾插进行测试
#include"SList.h"
void test02()
{
SLTNode* plist = NULL;
SLTPushBack(&plist, 1);
SLTPushBack(&plist, 2);
SLTPushBack(&plist, 3);
SLTPushBack(&plist, 4);
SLTPrint(plist);
}
int main()
{
test02();
return 0;
}
此时我们运行程序,会发现程序报错,那么问题出现在哪里?
我们仔细观察所创建的节点,首先我们创建了第一个节点,并赋值了NULL
但是plist已经是第一个节点的地址了,我们传参数时又取了plist的地址
所以传入的数据时地址的地址,也就是指针的指针,即二级指针
那么我们就需要用二级指针来接收。
正确代码
//链表的尾插
void SLTPushBack(SLTNode** pphead, SLDataType x)
{
assert(pphead);
SLTNode* newnode = SLTCreNode(x);
if (*pphead == NULL)
{
*pphead = newnode;
}
else
{
//找尾
SLTNode* prev = *pphead;
while (prev->next)
{
prev = prev->next;
}
prev->next = newnode;
}
}
在使用指针进行操作时,最重要的是要搞清楚指针的层级关系,要一级对应一级。
链表的头插
//链表的头插
void SLTPushFront(SLTNode** pphead, SLDataType x)
{
assert(pphead);
SLTNode* newnode = SLTCreNode(x);
newnode->next = *pphead;
*pphead = newnode;
}
- 首先先创建一个节点,再将创建的节点连接*pphead
- 将首节点置为newnode
链表的尾删
//链表的尾删
void SLTPopBack(SLTNode** pphead)
{
assert(pphead && *pphead);
if ((*pphead)->next == NULL)//注意这里的*pphead的括号,因为->的优先级高于*,所以需要括号
{
free(*pphead);
*pphead = NULL;
}
else
{
SLTNode* prev = *pphead;//尾节点的前一个节点
SLTNode* ptail = *pphead;//尾节点
//找尾
while (ptail->next)
{
prev = ptail;
ptail = ptail->next;
}
free(ptail);
ptail = NULL;
prev->next = NULL;
}
}
如果想要删除尾节点,需要找出两个节点,由于链表的不可逆操作,所以需要找出尾节点和尾节点之前的一个节点
- pphead和*pphead都不能为空指针,删除的数据不能为空
- 释放尾节点并置为空指针
- 找到尾节点的前一个节点使其指向NULL
链表的头删
//链表的头删
void SLTPopFront(SLTNode** pphead)
{
assert(pphead && *pphead);
SLTNode* prev = *pphead;
*pphead = prev->next;
free(prev);
prev = NULL;
}
- pphead和*pphead都不能为空指针
- 先让*pphead指向下一个节点
- 释放原节点并置为空指针
链表元素的查找
SLTNode* SLTFind(SLTNode** pphead, SLDataType x)
{
assert(pphead && *pphead);
SLTNode* prev = *pphead;
if (prev->next == NULL)
{
if (prev->data == x)
{
return prev;
}
return NULL;
}
else
{
while (prev->next)
{
if (prev->data == x)
{
return prev;
}
prev = prev->next;
}
return NULL;
}
}
- pphead和*pphead都不能为空指针
- 如果只有一个元素,则直接比较
- 如果又多个元素,则进行遍历
- 找到了返回节点的地址,没找到返回空指针
在指定位置之前插入数据
//在指定位置之前插入数据
void SLTInsert(SLTNode** pphead, SLTNode* pos, SLDataType x)
{
assert(pphead && *pphead);
assert(pos);
//判断是否为头插
if (*pphead == pos)
{
SLTPushFront(pphead, x);//直接调用头插
}
else
{
SLTNode* newnode = SLTCreNode(x);//创建新节点
SLTNode* prev = *pphead;
while (prev->next != pos)
{
prev = prev->next;
}
newnode->next = pos;
prev->next = newnode;
}
}
- pphead和*pphead都不能为空指针,位置不能为第0位
- 如果位置是第一位,则直接调用头插
- 通过循环找到pos的位置
- 将新节点newnode指向pos
- pos前一个节点prev指向newnode
在指定位置之后插入数据
//在指定位置之后插入数据
void SLTAfter(SLTNode* pos, SLDataType x)
{
assert(pos);
SLTNode* newnode = SLTCreNode(x);
//插入前pos - pos->next
//插入后pos - newnode - pos->next
newnode->next = pos->next;
pos->next = newnode;
}
- 创建一个新节点
- 让newnode->next指向原来pos指向的数
- 再让pos指向newnode
在指定位置删除数据
//在指定位置删除数据
void SLTErase(SLTNode** pphead, SLTNode* pos)
{
assert(pphead && *pphead);
if (*pphead->next == pos)
{
//如果指向第一个元素就是头删
SLTPopFront(pphead, x);
}
else
{
SLTNode* prev = *pphead;
//找出pos的前一个节点prev
while (prev->next != pos)
{
prev = prev->next;
}
//prev连接pos后的节点
prev->next = pos->next;
free(pos);
pos = NULL;
}
}
- pphead和*pphead都不能为空指针
- 如果要删除的是第一位数据,则调用头删
- 先找出pos的前一个节点prev
- 再将prev连接pos后的节点
- 释放pos并置为空指针
删除pos之后的一个节点
//删除pos之后的一个节点
void SLTEraseAfter(SLTNode* pos)
{
assert(pos);
//prev指向需要删除的节点
SLTNode* prev = pos->next;
//pos指向prev之后的节点
pos->next = prev->next;
free(prev);
prev = NULL;
}
- pos不能是空指针
- prev指向需要删除的节点
- 使pos指向prev之后的节点
- 释放prev并置为空指针
销毁链表
//销毁链表
void SListDesTroy(SLTNode** pphead)
{
assert(pphead && *pphead);
SLTNode* prev = *pphead;\
while (prev->next)
{
SLTNode* pcur = prev;
prev = prev->next;
free(pcur);
pcur = NULL;
}
prev = NULL;
}
- pphead和*pphead不能为空指针
- 循环遍历链表,创建变量复制prev的值
- prev快pcur一个节点,prev往前走,pcur在后面释放
- 最后把prev置为空指针
源代码
SList.h
#pragma once
#include<stdio.h>
#include<stdlib.h>
#include<assert.h>
typedef int SLDataType;
typedef struct SListNode
{
SLDataType data;
struct SListNode* next;
}SLTNode;
//链表的打印
void SLTPrint(SLTNode* phead);
//链表的尾插
void SLTPushBack(SLTNode** pphead, SLDataType x);
//链表的头插
void SLTPushFront(SLTNode** pphead, SLDataType x);
//链表的尾删
void SLTPopBack(SLTNode** pphead);
//链表的头删
void SLTPopFront(SLTNode** pphead);
//链表元素的查找
SLTNode* SLTFind(SLTNode** pphead, SLDataType x);
//在指定位置之前插入数据
void SLTInsert(SLTNode** pphead, SLTNode* pos, SLDataType x);
//在指定位置之后插入数据
void SLTAfter(SLTNode* pos, SLDataType x);
//在指定位置删除数据
void SLTErase(SLTNode** pphead, SLTNode* pos);
//删除pos之后的一个节点
void SLTEraseAfter(SLTNode* pos);
//销毁链表
void SListDesTroy(SLTNode** pphead);
SList.c
#include"SList.h"
//链表的打印
void SLTPrint(SLTNode* phead)
{
assert(phead);
SLTNode* prev = phead;
while (prev)
{
printf("%d->", prev->data);
prev = prev->next;
}
printf("NULL\n");
}
//销毁链表
void SListDesTroy(SLTNode** pphead)
{
assert(pphead && *pphead);
SLTNode* prev = *pphead; \
while (prev->next)
{
SLTNode* pcur = prev;
prev = prev->next;
free(pcur);
pcur = NULL;
}
prev = NULL;
}
//创建新节点
SLTNode* SLTCreNode(SLDataType x)
{
SLTNode* newnode = (SLTNode*)malloc(sizeof(SLTNode));
//预防malloc失败
if (newnode == NULL)
{
perror("malloc failed");
exit(1);
}
newnode->data = x;
newnode->next = NULL;
return newnode;
}
//ERROR
链表的尾插
//void SLTPushBack(SLTNode* phead, SLDataType x)
//{
// assert(phead);
// SLTNode* newnode = SLTCreNode(x);
// if (phead == NULL)
// {
// phead = newnode;
// }
// else
// {
// //先找到尾部
// SLTNode* prev = phead;
//
// while (prev->next)
// {
// prev = prev->next;
// }
// prev->next = newnode;
// }
//}
//链表的尾插
void SLTPushBack(SLTNode** pphead, SLDataType x)
{
assert(pphead);
SLTNode* newnode = SLTCreNode(x);
if (*pphead == NULL)
{
*pphead = newnode;
}
else
{
//找尾
SLTNode* prev = *pphead;
while (prev->next)
{
prev = prev->next;
}
prev->next = newnode;
}
}
//链表的头插
void SLTPushFront(SLTNode** pphead, SLDataType x)
{
assert(pphead);
SLTNode* newnode = SLTCreNode(x);
newnode->next = *pphead;
*pphead = newnode;
}
//链表的尾删
void SLTPopBack(SLTNode** pphead)
{
assert(pphead && *pphead);
if ((*pphead)->next == NULL)
{
free(*pphead);
*pphead = NULL;
}
else
{
SLTNode* prev = *pphead;//尾节点的前一个节点
SLTNode* ptail = *pphead;//尾节点
//找尾
while (ptail->next)
{
prev = ptail;
ptail = ptail->next;
}
free(ptail);
ptail = NULL;
prev->next = NULL;
}
}
//链表的头删
void SLTPopFront(SLTNode** pphead)
{
assert(pphead && *pphead);
SLTNode* prev = *pphead;
*pphead = prev->next;
free(prev);
prev = NULL;
}
SLTNode* SLTFind(SLTNode** pphead, SLDataType x)
{
assert(pphead && *pphead);
SLTNode* prev = *pphead;
if (prev->next == NULL)
{
if (prev->data == x)
{
return prev;
}
return NULL;
}
else
{
while (prev->next)
{
if (prev->data == x)
{
return prev;
}
prev = prev->next;
}
return NULL;
}
}
//在指定位置之前插入数据
void SLTInsert(SLTNode** pphead, SLTNode* pos, SLDataType x)
{
assert(pphead && *pphead);
assert(pos);
//判断是否为头插
if (*pphead == pos)
{
SLTPushFront(pphead, x);//直接调用头插
}
else
{
SLTNode* newnode = SLTCreNode(x);//创建新节点
SLTNode* prev = *pphead;
while (prev->next != pos)
{
prev = prev->next;
}
newnode->next = pos;
prev->next = newnode;
}
}
//在指定位置之后插入数据
void SLTAfter(SLTNode* pos, SLDataType x)
{
assert(pos);
SLTNode* newnode = SLTCreNode(x);
//插入前pos - pos->next
//插入后pos - newnode - pos->next
newnode->next = pos->next;
pos->next = newnode;
}
//在指定位置删除数据
void SLTErase(SLTNode** pphead, SLTNode* pos)
{
assert(pphead && *pphead);
if ((*pphead)->next == pos)
{
//如果指向第一个元素就是头删
SLTPopFront(pphead);
}
else
{
SLTNode* prev = *pphead;
//找出pos的前一个节点prev
while (prev->next != pos)
{
prev = prev->next;
}
//prev连接pos后的节点
prev->next = pos->next;
free(pos);
pos = NULL;
}
}
//删除pos之后的一个节点
void SLTEraseAfter(SLTNode* pos)
{
assert(pos);
//prev指向需要删除的节点
SLTNode* prev = pos->next;
//pos指向prev之后的节点
pos->next = prev->next;
free(prev);
prev = NULL;
}
test.c
#include"SList.h"
void test01()
{
printf("test01\n");
//创建四个节点
SLTNode* node1 = (SLTNode*)malloc(sizeof(SLDataType));
assert(node1);//预防malloc失败
node1->data = 1;
SLTNode* node2 = (SLTNode*)malloc(sizeof(SLDataType));
assert(node2);
node2->data = 2;
SLTNode* node3 = (SLTNode*)malloc(sizeof(SLDataType));
assert(node3);
node3->data = 3;
SLTNode* node4 = (SLTNode*)malloc(sizeof(SLDataType));
assert(node4);
node4->data = 4;
//首尾相连
node1->next = node2;
node2->next = node3;
node3->next = node4;
node4->next = NULL;
SLTNode* plist = node1;//创建一个链表使第一个节点为node1
SLTPrint(plist);
SListDesTroy(&plist);
printf("\n");
}
void test02()
{
printf("test02\n");
SLTNode* plist = NULL;
//链表的尾插
SLTPushBack(&plist, 1);
SLTPushBack(&plist, 2);
SLTPushBack(&plist, 3);
SLTPushBack(&plist, 4);
SLTPrint(plist);
//链表的头插
SLTPushFront(&plist, 0);
SLTPrint(plist);
//链表的尾删
SLTPopBack(&plist);
SLTPrint(plist);
//链表的头删
SLTPopFront(&plist);
SLTPrint(plist);
//链表元素的查找
SLTNode* Find = SLTFind(&plist, 1);
if (Find == NULL)
printf("没找到!\n");
else
printf("找到了!\n");
//在指定位置之前插入数据
SLTInsert(&plist, Find, 0);
SLTPrint(plist);
//在指定位置之后插入数据
SLTAfter(Find, 1);
SLTPrint(plist);
//在指定位置删除数据
SLTErase(&plist, Find);
SLTPrint(plist);
//删除pos之后的一个节点
SLTEraseAfter(Find);
SLTPrint(plist);
//销毁链表
SListDesTroy(&plist);
}
int main()
{
test01();
test02();
return 0;
}