一、链表简介
前面我们学习了顺序表,我们发现它有以下缺点:
1.进行数据增添或删除时,可能需要将很多数据后移或前移,时间复杂度高
2.增容需要申请新空间,拷贝数据,释放旧空间。会有不小的消耗。
3.增容⼀般是呈2倍的增⻓,势必会有⼀定的空间浪费。例如当前容量为100,满了以后增容到
200,我们再继续插⼊了5个数据,后⾯没有数据插⼊了,那么就浪费了95个数据空间。
为了解决这种情况,我们可以使用一种新的数据结构——链表
链表与顺序表一样属于线性表,与顺序表不同的是,它在物理结构上不一定是连续的,相较于顺序表,我们在对链表进行增、删、查、改操作时要方便许多。
二、链表的结构
链表分为很多种,这里我们主要研究的是单链表中的不带头单向不循环链表,链表是由一个一个的节点构成的,每个节点都可以用来存储数据(就像火车由一节一节的车厢组成),但是我们如何将这些节点联系起来?
typedef int SLTDataType;
typedef struct SListNode
{
SLTDataType data;//存储数据
struct SListNode* next;//指向下一个节点的指针
}SLTNode;
可以看到,我们通过指针来将节点联系起来, 每一个指针存储下一个节点的地址,这里我们将int定义为SListNode是因为我们要存储的数据类型不一定是int,还有可能是char等类型,如果我们需要将int改为char的话就只需改一次就可以了。
三、单链表的实现
由于代码量比较大,这里使用了SList.c、SList.h、Test.c三个文件来编写,为了方便后面解说,这里先将头文件里的代码全部写出:
#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);
//尾插
void SLTPushBack(SLTNode** phead, SLTDataType x);
//头插
void SLTPushFront(SLTNode** phead, SLTDataType x);
//尾删
void SLTPopBack(SLTNode** pphead);
//头删
void SLTPopFront(SLTNode** pphead);
//查找某个数据
SLTNode* SLTFind(SLTNode* phead, SLTDataType x);
//在指定位置之前插⼊数据
void SLTInsert(SLTNode** pphead, SLTNode* pos, SLTDataType x);
//删除pos节点
void SLTErase(SLTNode** pphead, SLTNode* pos);
//在指定位置之后插⼊数据
void SLTInsertAfter(SLTNode* pos, SLTDataType x);
//删除pos之后的节点
void SLTEraseAfter(SLTNode* pos);
//销毁链表
void SListDesTroy(SLTNode** pphead);
头文件中包含了 1.节点的定义 2.要实现的函数
1.链表的打印
void SLTPrint(SLTNode* phead)
{
//定义一个临时指针,使代码执行后不影响原指针
SLTNode* pcur = phead;
while (pcur)
{
printf("%d->", pcur->data);
pcur = pcur->next;
}
printf("NULL\n");
}
2.链表的尾插
通过上图我们我们知道了尾插的两种情况,下面是代码实现:
//申请新节点
SLTNode* SLTBuyNode(SLTDataType x)
{
SLTNode* newnode = (SLTNode*)malloc(sizeof(SLTNode));
if (newnode == NULL)
{
perror("malloc fail!");
exit(1);
}
newnode->data = x;
newnode->next = NULL;
return newnode;
}
void SLTPushBack(SLTNode** pphead, SLTDataType x)
{
//有空链表和非空链表两种情况
//申请新节点
SLTNode* newnode=SLTBuyNode(x);
if (*pphead == NULL)
{
*pphead = newnode;
}
else
{
SLTNode* ptail = *pphead;
while (ptail->next)
{
ptail = ptail->next;
}
ptail->next = newnode;
}
}
关于上图代码,有一个问题,我们能否将函数SLTPushBack的参数改为用一级指针接受,答案是不行,因为尾插函数的形参是要对链表进行改变的,而想要对实参进行改变,我们必须传地址而不是传值,在这个函数中我们要接收的是指向第一个节点的指针的地址,故用二级指针。
3.链表的头插
void SLTPushFront(SLTNode** pphead, SLTDataType x)
{
assert(pphead);
//为x申请一个新节点
SLTNode* newnode=SLTBuyNode(x);
//*pphead为空也可行
newnode->next = *pphead;
*pphead = newnode;
}
头插同样有两种情况,一种是链表为空,一种是链表不为空,但它们都适用于这段代码。
4.链表的尾删
void SLTPopBack(SLTNode** pphead)
{
assert(pphead);
//链表为空不能进行尾删
assert(*pphead);
//只有一个节点
if ((*pphead)->next == NULL)
{
free(*pphead);
*pphead = NULL;
}
//有多个节点
else
{
//记录尾节点
SLTNode* ptail = *pphead;
//记录尾节点的前一个节点
SLTNode* prev = *pphead;
while (ptail->next)
{
prev = ptail;
ptail = ptail->next;
}
free(ptail);
ptail = NULL;
prev->next = NULL;
}
}
5.链表的头删
void SLTPopFront(SLTNode** pphead)
{
assert(pphead && *pphead);
SLTNode* next = (*pphead)->next;
free(*pphead);
*pphead = next;
}
同时适用于只有一个节点和多个节点的情况
6.在链表中查找某个数据
直接遍历即可
//不需要改变实参,故传值就可以
SLTNode* SLTFind(SLTNode* phead, SLTDataType x)
{
SLTNode* pcur = phead;
while (pcur)
{
if (pcur->next == x)
{
return pcur;
}
pcur = pcur->next;
}
return NULL;
}
7.在指定位置之前插⼊数据
void SLTInsert(SLTNode** pphead, SLTNode* pos, SLTDataType x)
{
assert(pphead && pos && *pphead);
//为数据x申请一个节点
SLTNode* newnode = SLTBuyNode(x);
//用于找pos的前一个节点
if (pos == *pphead)
{
//调用头插函数
SLTPushFront(pphead, x);
}
else {
SLTNode* prev = *pphead;
while (prev->next != pos)
{
prev = prev->next;
}
prev->next = newnode;
newnode->next = pos;
}
}
8.删除pos节点
void SLTErase(SLTNode** pphead, SLTNode* pos)
{
assert(pphead && *pphead);
assert(pos);
SLTNode* prev = *pphead;
while (prev->next != pos)
{
prev = prev->next;
}
prev->next = pos->next;
free(pos);
pos = NULL;
}
9.在指定位置之后插入数据
void SLTErase(SLTNode** pphead, SLTNode* pos)
{
assert(pphead && *pphead);
assert(pos);
//pos是头节点
if (pos == *pphead)
{
//头删
SLTPopFront(pphead);
}//pos不是头节点
else {
SLTNode* prev = *pphead;
while (prev->next != pos)
{
prev = prev->next;
}
prev->next = pos->next;
free(pos);
pos = NULL;
}
}
10.删除pos之后的节点
void SLTEraseAfter(SLTNode* pos)
{
//pos和pos的下一个节点都不能为空
assert(pos && pos->next);
SLTNode* del = pos->next;
pos->next = del->next;
free(del);
del = NULL;
}
11.销毁链表
void SListDesTroy(SLTNode** pphead)
{
assert(pphead && *pphead);
SLTNode* pcur = *pphead;
while (pcur)
{
SLTNode* next = pcur->next;
free(pcur);
pcur = next;
}
*pphead = NULL;
}
下面是SList.c的全部代码,
#include"SList.h"
void SLTPrint(SLTNode* phead)
{
//定义一个临时指针,使代码执行后不影响原指针
SLTNode* pcur = phead;
while (pcur)
{
printf("%d->", pcur->data);
pcur = pcur->next;
}
printf("NULL\n");
}
//申请新节点
SLTNode* SLTBuyNode(SLTDataType x)
{
SLTNode* newnode = (SLTNode*)malloc(sizeof(SLTNode));
if (newnode == NULL)
{
perror("malloc fail!");
exit(1);
}
newnode->data = x;
newnode->next = NULL;
return newnode;
}
void SLTPushBack(SLTNode** pphead, SLTDataType x)
{
assert(pphead);
//有空链表和非空链表两种情况
//申请新节点
SLTNode* newnode=SLTBuyNode(x);
if (*pphead == NULL)
{
*pphead = newnode;
}
else
{
SLTNode* ptail = *pphead;
while (ptail->next)
{
ptail = ptail->next;
}
ptail->next = newnode;
}
}
void SLTPushFront(SLTNode** pphead, SLTDataType x)
{
assert(pphead);
//为x申请一个新节点
SLTNode* newnode=SLTBuyNode(x);
//*pphead为空也可行
newnode->next = *pphead;
*pphead = newnode;
}
void SLTPopBack(SLTNode** pphead)
{
assert(pphead);
//链表为空不能进行尾删
assert(*pphead);
//只有一个节点
if ((*pphead)->next == NULL)
{
free(*pphead);
*pphead = NULL;
}
//有多个节点
else
{
//记录尾节点
SLTNode* ptail = *pphead;
//记录尾节点的前一个节点
SLTNode* prev = *pphead;
while (ptail->next)
{
prev = ptail;
ptail = ptail->next;
}
free(ptail);
ptail = NULL;
prev->next = NULL;
}
}
void SLTPopFront(SLTNode** pphead)
{
assert(pphead && *pphead);
SLTNode* next = (*pphead)->next;
free(*pphead);
*pphead = next;
}
//不需要改变实参,故传值就可以
SLTNode* SLTFind(SLTNode* phead, SLTDataType x)
{
SLTNode* pcur = phead;
while (pcur)
{
if (pcur->next == x)
{
return pcur;
}
pcur = pcur->next;
}
return NULL;
}
void SLTInsert(SLTNode** pphead, SLTNode* pos, SLTDataType x)
{
assert(pphead && pos && *pphead);
//为数据x申请一个节点
SLTNode* newnode = SLTBuyNode(x);
//用于找pos的前一个节点
if (pos == *pphead)
{
//调用头插函数
SLTPushFront(pphead, x);
}
else {
SLTNode* prev = *pphead;
while (prev->next != pos)
{
prev = prev->next;
}
prev->next = newnode;
newnode->next = pos;
}
}
void SLTErase(SLTNode** pphead, SLTNode* pos)
{
assert(pphead && *pphead);
assert(pos);
//pos是头节点
if (pos == *pphead)
{
//头删
SLTPopFront(pphead);
}//pos不是头节点
else {
SLTNode* prev = *pphead;
while (prev->next != pos)
{
prev = prev->next;
}
prev->next = pos->next;
free(pos);
pos = NULL;
}
}
void SLTInsertAfter(SLTNode* pos, SLTDataType x)
{
assert(pos);
SLTNode* newnode = SLTBuyNode(x);
newnode->next = pos->next;
pos->next = newnode;
}
void SLTEraseAfter(SLTNode* pos)
{
//pos和pos的下一个节点都不能为空
assert(pos && pos->next);
SLTNode* del = pos->next;
pos->next = del->next;
free(del);
del = NULL;
}
void SListDesTroy(SLTNode** pphead)
{
assert(pphead && *pphead);
SLTNode* pcur = *pphead;
while (pcur)
{
SLTNode* next = pcur->next;
free(pcur);
pcur = next;
}
*pphead = NULL;
}