1.单链表
1.1概念
单链表:是一种数据结构,用于存储线性表中的数据元素,通过一组地址任意的存储单元来存放数据。每个数据元素在链表中以结点的形式存在,每个结点包含两个部分:数据域和指针域。数据域用于存储实际的数据,而指针域存储指向链表中下一个节点的地址。这种结构使得单链表不需要连续的存储空间,数据元素的逻辑顺序通过链表中的指针链接次序实现。
1.2组成
- 数据域:用于存储实际的数据。
- 指针域:存储指向链表中下一个节点的地址。
- 头节点:链表的第一个节点,通常被称为“头节点”或“首节点”。
- 终端节点:链表的最后一个节点,其指针域通常为空(NULL),表明链表的结束。
2.单链表的书写
我们以单向不带头链表为例进行书写
2.1 单链表的创建
typedef int SListData;
typedef struct SListNote {
SListData data;//存储的数据类型
struct SListNote* next;//指向下一个节点的指针
}Note;
利用结构体指针来牵引下一个节点。
2.2 初始
若不带头一般将头结点初始为NULL;
int main{
Note* list = NULL;
return 0;
}
2.3 开辟空间
每次存储一个数据,我们需要开辟一块空间进行存储,因为为链式结构,我们可以使用malloc进行空间开辟,利用结构体指针进行连接。
Note* SListBuyData(SListData x) {
Note* newnode = (Note*)malloc(sizeof(Note));
//判断是否开辟成功
if (newnode == NULL) {
perror("malloc fail!");
exit(1);
}
//开辟成功
newnode->data = x;
newnode->next = NULL;
//返回开辟成功的结构体指针
return newnode;
}
2.4插入
2.4.1尾部插入
将数据插入已有单链表数据的尾部
注意:如果不带头的话,第一个数据将直接插入头结点中
我们原本创建的头结点
给新数据申请的空间
图示如下
代码如下
void SListpushback(Note**pphead,SListData x) {
assert(pphead);
Note* newnode = SListBuyData(x);
//判断是否头为空//
if (*pphead == NULL) {
*pphead = newnode;
return;
}
//不为空,创建新结构体指针保存头部//
Note* ptail = *pphead;
//找节点尾部//
while (ptail->next) {
ptail = ptail->next;
}
ptail->next = newnode;
}
2.4.2头部插入
将数据插入原头结点前面
图示
代码实现
//头插//
void SListpushfront(Note** pphead, SListData x) {
assert(pphead);
Note* newnode = SListBuyData(x);
newnode->next = *pphead;//将原本的头后移一个节点
*pphead = newnode;//新数据作为头//
}
2.5尾部删除
2.5.1尾部删除
删除尾部的数据,使尾部的前一个节点的next指针指向NULL
图示
代码
void SListpopback(Note** pphead) {
assert(pphead);
//链表不为空
assert(*pphead);
//链表内容不为空
if ((*pphead)->next == NULL) {
free(*pphead);
*pphead = NULL;
return;
}
Note* ptail = *pphead;
Note* prev = NULL;//记录最后节点的前一个//
while (ptail->next) {
prev = ptail;
ptail = ptail->next;
}
prev->next = NULL;
}
2.5.2头部删除
将头结点删除,头结点的下一个节点作为头
图示
代码
//头删//
void SListpopfront(Note** pphead) {
assert(pphead);
assert(*pphead);
//把第二个节点成为头
//释放原头
Note*next= (*pphead)->next;
free(*pphead);
*pphead = next;
}
2.6 查找
查找需要查找的元素,遍历一遍现有链表,如存在,则返回其所在地址,不存在返回NULL
代码 如下
//查找
Note* SListFind(Note** pphead, SListData x) {
assert(pphead);
assert(*pphead);
//保存头结点
Note* ptail = *pphead;
while (ptail) {
if (ptail->data == x) {
return ptail;
}
ptail = ptail->next;
}
return NULL;
}
3. 任意位置
3.1指定位置插入
1.指定位置之前
首先要调用查找函数返回的地址,然后遍历找到该地址的前一个节点。然后使该位置前节点的next指针指向新来辟的节点,新节点的next指针指向指定位置的节点
函数声明
//指定位置之前插入//
void SListInsert(Note** pphead, Note* pos, SListData x);//pos代表插入位置的地址
//查找//
Note* SListFind(Note** pphead, SListData x);
图示
代码如下
void SListInsert(Note** pphead, Note* pos, SListData x) {
assert(pphead);
assert(pos);
assert(*pphead);
Note* newnode = SListBuyData(x);
if (pos == *pphead) {
SListpushfront(pphead, x);
return;
}
Note* prev = *pphead;
while (prev->next!=pos) {
prev = prev->next;
}
prev->next = newnode;
newnode->next = pos;
}
2.指定位置之后
首先要调用查找函数返回的地址,然后遍历找到该地址。新节点的next指针指向指定位置的next指针节点然后使该位置的next指针指向新来辟的节点。
函数声明
//指定位置之后//
void SListInsertAfter(Note* pos, SListData x);
//查找//
Note* SListFind(Note** pphead, SListData x);
图示
代码
//指定位置之后插入
void SListInsertAfter(Note* pos, SListData x) {
assert(pos);
Note* newnode = SListBuyData(x);
newnode->next = pos->next;
pos->next = newnode;
}
3.2指定位置删除
1.该位置删除
找到该位置的前一个节点,让其next指针指向该节点的下一个节点,如果为头节点可直接调用头删。
函数声明
//删除指定位置节点
void SListpopInsert(Note** pphead, Note* pos);
//查找//
Note* SListFind(Note** pphead, SListData x);
图示:
代码如下
//删除指定位置节点
void SListpopInsert(Note** pphead, Note* pos) {
assert(pphead);
assert(pos);
assert(*pphead);
if (pos == *pphead) {
SListpopfront(pphead);
return;
}
Note* prut = *pphead;
//找指定位置的前一个节点
while (prut->next != pos) {
prut = prut->next;
}
prut->next = pos->next;
}
2.指定位置之后
找到该位置,使该位置的next指针指向该位置的下下个节点。
图示:
代码
//删除指定位置之后//
void SListpopInsertAfter(Note* pos) {
assert(pos);
assert(pos->next);
Note* del = pos->next;
pos->next = pos->next->next;
free(del);
del = NULL;
}
4.打印
将单链表中存储的数据打印出来
代码:
//打印//
void SListPrint(Note* pphead) {
assert(pphead);
Note* pcur = pphead;
while (pcur) {
printf("%d->", pcur->data);
pcur = pcur->next;
}
printf("NULL\n");
}
5.销毁
将创建的链表空间进行销毁,将内存给释放出来,如果释放会导致内存泄漏,导致程序崩溃。
代码
void SListDestroy(Note** pphead) {
assert(pphead);
assert(*pphead);
Note* pcur = *pphead;
while (pcur) {
//将下一个节点保存,释放当前节点
Note* next = pcur->next;
free(pcur);
pcur = next;
}
*pphead = NULL;
}
6. 总代码
头文件
#include<stdio.h>
#include<assert.h>
#include<stdlib.h>
typedef int SListData;
typedef struct SListNote {
SListData data;
struct SListNote* next;
}Note;
//尾插//
void SListpushback(Note**pphead,SListData x);
//头插//
void SListpushfront(Note**pphead,SListData x);
//尾删//
void SListpopback(Note** pphead);
//头删//
void SListpopfront(Note** pphead);
//查找//
Note* SListFind(Note** pphead, SListData x);
//指定位置之前插入//
void SListInsert(Note** pphead, Note* pos, SListData x);
//指定位置之后//
void SListInsertAfter(Note* pos, SListData x);
//删除指定位置节点
void SListpopInsert(Note** pphead, Note* pos);
//删除指定位置之后//
void SListpopInsertAfter(Note* pos);
//销毁//
void SListDestroy(Note** pphead);
//打印//
void SListPrint(Note* pphead);
源文件
#include"SListData.h"
//开辟空间//
Note* SListBuyData(SListData x) {
Note* newnode = (Note*)malloc(sizeof(Note));
if (newnode == NULL) {
perror("malloc fail!");
exit(1);
}
newnode->data = x;
newnode->next = NULL;
return newnode;
}
//尾插//
void SListpushback(Note**pphead,SListData x) {
assert(pphead);
Note* newnode = SListBuyData(x);
//判断是否头为空//
if (*pphead == NULL) {
*pphead = newnode;
return;
}
//不为空,创建新结构体指针保存头部//
Note* ptail = *pphead;
//找节点尾部//
while (ptail->next) {
ptail = ptail->next;
}
ptail->next = newnode;
}
//头插//
void SListpushfront(Note** pphead, SListData x) {
assert(pphead);
Note* newnode = SListBuyData(x);
newnode->next = *pphead;//将原本的头后移一个节点
*pphead = newnode;//新数据作为头//
}
//尾删//
void SListpopback(Note** pphead) {
assert(pphead);
//链表不为空
assert(*pphead);
//链表内容不为空
if ((*pphead)->next == NULL) {
free(*pphead);
*pphead = NULL;
return;
}
Note* ptail = *pphead;
Note* prev = NULL;//记录最后节点的前一个//
while (ptail->next) {
prev = ptail;
ptail = ptail->next;
}
prev->next = NULL;
}
//头删//
void SListpopfront(Note** pphead) {
assert(pphead);
assert(*pphead);
//把第二个节点成为头
//释放原头
Note*next= (*pphead)->next;
free(*pphead);
*pphead = next;
}
//查找
Note* SListFind(Note** pphead, SListData x) {
assert(pphead);
assert(*pphead);
Note* ptail = *pphead;
while (ptail) {
if (ptail->data == x) {
return ptail;
}
ptail = ptail->next;
}
return NULL;
}
//指定位置之前插入
void SListInsert(Note** pphead, Note* pos, SListData x) {
assert(pphead);
assert(pos);
assert(*pphead);
Note* newnode = SListBuyData(x);
if (pos == *pphead) {
SListpushfront(pphead, x);
return;
}
Note* prev = *pphead;
while (prev->next!=pos) {
prev = prev->next;
}
prev->next = newnode;
newnode->next = pos;
}
//指定位置之后插入
void SListInsertAfter(Note* pos, SListData x) {
assert(pos);
Note* newnode = SListBuyData(x);
newnode->next = pos->next;
pos->next = newnode;
}
//删除指定位置节点
void SListpopInsert(Note** pphead, Note* pos) {
assert(pphead);
assert(pos);
assert(*pphead);
if (pos == *pphead) {
SListpopfront(pphead);
return;
}
Note* prut = *pphead;
while (prut->next != pos) {
prut = prut->next;
}
prut->next = pos->next;
}
//删除指定位置之后//
void SListpopInsertAfter(Note* pos) {
assert(pos);
assert(pos->next);
Note* del = pos->next;
pos->next = pos->next->next;
free(del);
del = NULL;
}
//销毁//
void SListDestroy(Note** pphead) {
assert(pphead);
assert(*pphead);
Note* pcur = *pphead;
while (pcur) {
Note* next = pcur->next;
free(pcur);
pcur = next;
}
*pphead = NULL;
}
//打印//
void SListPrint(Note* pphead) {
assert(pphead);
Note* pcur = pphead;
while (pcur) {
printf("%d->", pcur->data);
pcur = pcur->next;
}
printf("NULL\n");
}
测试文件
#include"SListData.h"
int main() {
Note* list = NULL;
//尾插//
SListpushback(&list, 1);
SListpushback(&list, 2);
SListpushback(&list, 3);
SListpushback(&list, 4);
SListPrint(list);
//头插//
SListpushfront(&list, 2);
SListPrint(list);
//尾删//
SListpopback(&list);
SListPrint(list);
//头删//
SListpopfront(&list);
SListPrint(list);
Note* FindRet = SListFind(&list, 1);
/*if (FindRet) {
printf("找到啦!");
}
else {
printf("没找到!");
}*/
/*SListInsert(&list, FindRet, 100);
SListPrint(list);*/
SListInsertAfter(FindRet, 5);
SListPrint(list);
SListpopInsert(&list, FindRet);
SListPrint(list);
SListDestroy(&list);
}
7.总结
单链表的优缺点
-
优点:不要求大片连续空间,改变容量方便。
-
缺点:不可随机存取,要耗费一定空间存放指针。