数据结构与算法——单链表的实现及增、插、删、查、印、毁
文章目录
- 一、前言
- 二、单链表的基本概念
- 2.1单链表的概念
- 2.2单链表的结构
- 2.3单链表的性质
- 2.4结点实现代码
- 三、单链表的增、插、删、查、印、毁
- 3.1结点的创建
- 3.2链表的尾插
- 3.3链表的头插
- 3.4链表指定位置之后插入数据
- 3.5链表指定位置之前插入数据
- 3.6链表的尾删
- 3.7链表的头删
- 3.8删除指定位置pos结点
- 3.9删除指定位置pos之后的结点
- 3.10链表元素的查找
- 3.11链表的打印
- 3.12链表的销毁
- 四、单链表整体代码分析
- 4.1头文件——SList.h及代码展示
- 4.2主体代码文件——Slist.c及代码展示
- 4.3测试文件——test.c及代码展示
- 4.3.1新结点创建演示
- 4.3.2尾插演示
- 4.3.3头插演示
- 4.3.4尾删演示
- 4.3.5头删演示
- 4.3.6元素查找演示
- 4.3.7指定位置之后插入演示
- 4.3.8指定位置之前插入演示
- 4.3.9删除pos结点演示
- 4.3.10删除pos之后结点演示
- 4.3.11单链表销毁演示
- 五、总结
一、前言
学习如逆水行舟,不进则退。今天的你还在坚持学习吗?最近up身边也是出现了一件很有意思的事情:我的高中的时候一个朋友,他询问我一些有关编程的知识,我一听到这不就兴奋起来了吗?于是我就把我一系列的学习方法推荐给了他,他当时也是说非常有用。可是过了几天他却就彻底躺平了,我问他:你最近怎么了啊?他回答我说:学习太累了,我想躺平了。我听完为之一震,后面即使过了好久,他也没继续学习。通过这件事情,up希望能引起大家的警醒,学习虽然很累,但是学完之后却非常幸福。
好了,一小碗鸡汤,希望与大家共勉。前面呢我已经给大家介绍完了顺序表这种数据结构,今天我给大家再介绍一种新的数据结构——单链表,请大家拭目以待。
二、单链表的基本概念
2.1单链表的概念
单链表是一种储存结构,它在物理储存结构上是非连续且非顺序的,数据元素的逻辑顺序是通过链表中的指针衔接次序实现的。
2.2单链表的结构
地铁相信大家都见过吧,它是由一节一节的车厢组成的,每节车厢作为一个个体单独存在,在最前面由一个火车头引领。在每节车厢的末尾,还有通往下一节车厢的大门,方便乘客们随时走动。其实啊,单链表也相当于是一个“火车”,那么单链表的结构又具体是什么样子的呢?如图:
由图可知:单链表也是由一节一节类似”车厢“的结点组成,除了头结点之外,每个结点都由两部分组成:分别是当前结点内存放的数据和另外一个结点的地址,而在这些单独结点的最开始,由头结点所引领。但是通过仔细观察,我们发现:结点与结点之间都不是连续存放的,通过存放在结点中另外一个结点的地址,就把这些单独的结点连接到了一起,而最后一个结点后面已经没有结点了,于是就置为空(NULL)。
2.3单链表的性质
1:单链表在逻辑结构上是连续的,在物理结构上是不连续的;
2:结点一般是从堆上申请的;
2:从堆上申请来的空间,可能是连续也可能是不连续的。
2.4结点实现代码
三、单链表的增、插、删、查、印、毁
3.1结点的创建
画图展示:
代码展示:
3.2链表的尾插
画图展示:
实质:如果链表为空,直接插入即可;如果链表不为空,把要插入的结点的地址放入原链表的最后一个结点所指向存放的地址中。
代码展示:
3.3链表的头插
画图展示:
实质:把原链表第一个结点的地址放入新结点所指向存放的地址中。
代码展示:
3.4链表指定位置之后插入数据
画图展示:
实质:先把原链表中指定位置之后的结点的地址放入所指向存放的地址中,再把新结点的地址放入pos位置结点所指向存放的地址中。
代码展示:
3.5链表指定位置之前插入数据
画图展示:
实质:如果是第一个结点之前,则是头插;如果不是第一个结点之前,先把新结点的地址放入pos位置前一个结点所指向存放的地址中,再把pos结点的地址放入新结点所指向存放的地址中。
代码展示:
3.6链表的尾删
画图展示:
实质:如果只有一个结点,把这个结点删除并且置为NULL即可;如果链表不止一个结点,把要删除的最后一个结点的前一个结点指向所存放的地址改为NULL,再把最后一个结点释放即可。
代码展示:
3.7链表的头删
画图展示:
实质:直接把头结点删除使第二个结点成为头结点即可。
代码展示;
3.8删除指定位置pos结点
画图展示:
实质:把原链表中pos位置之后的结点的地址放入原链表中pos位置之前的结点所指向存放的地址中即可,最后再把pos结点释放掉。
代码展示:
3.9删除指定位置pos之后的结点
画图展示:
实质:把原链表中pos位置之后的结点的地址放入原链表中pos位置之前的结点所指向存放的地址中即可。
代码展示:
3.10链表元素的查找
实质:遍历链表,依次查找。
代码展示:
3.11链表的打印
实质;遍历链表,依次打印。
代码展示:
3.12链表的销毁
实质:遍历链表,依次销毁。
代码展示:
四、单链表整体代码分析
OK啊,上面up就把单链表的各个功能模块的实现给大家讲解完毕,为了避免大家在单独看每个小模块的时候有疑惑,我还是给大家整体分析一波。单链表和顺序表一样,也可以由三个文件组成,分别是头文件——SList.h,主体代码文件——SList.c,测试文件——test.c。关于为什么要用3个文件而不是一个文件和各个文件的功能,up在上上一篇顺序表的实现中已经讲解地非常透彻了,有不知道的宝子们可以前往翻阅。
4.1头文件——SList.h及代码展示
代码粘贴:
//头文件SList.h
#pragma once
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
typedef int SLTDataType;
typedef struct SLTNode
{
SLTDataType data;
struct SLTNode* next;
}SLTNode;
SLTNode* SLTbuyNode(SLTDataType x);//新结点的创建
void SLTPushBack(SLTNode** pphead, SLTDataType x);//尾插
void SLTPushFront(SLTNode** pphead, SLTDataType x);//头插
void SLTPopBack(SLTNode** pphead);//尾删
void SLTPopFront(SLTNode** pphead);//头删
void SLTInsertAfter(SLTNode* pos, SLTDataType x);//指定位置之后插入结点
void SLTInsertFront(SLTNode** pphead, SLTNode* pos, SLTDataType x);//指定位置之前插入结点
void SLTErase(SLTNode** pphead, SLTNode* pos);//删除指定位置结点
void SLTEraseAfter(SLTNode* pos);//删除指定位置之后结点
SLTNode* SLTFind(SLTNode* phead, SLTDataType x);//链表元素的查找
void SLTPrint(SLTNode* phead);//链表元素的打印
void SLTDestroy(SLTNode** pphead);//链表的销毁
4.2主体代码文件——Slist.c及代码展示
代码粘贴:
//主体代码文件SList.c
#include "SList.h"
SLTNode* SLTbuyNode(SLTDataType x)//新结点的创建
{
SLTNode* newnode = (SLTNode*)malloc(sizeof(SLTNode));//申请空间
if (newnode == NULL)//判断是否申请成功
{
printf("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);
SLTNode* newnode = SLTbuyNode(x);//创建新结点
newnode->next = *pphead;
*pphead = newnode;
}
void SLTPopBack(SLTNode** pphead)//尾删
{
assert(pphead);
if ((*pphead)->next == NULL)//如果只有一个结点
{
free(*pphead);
*pphead = NULL;
}
else//如果不止一个结点
{
SLTNode* prev = NULL;
SLTNode* ptail = *pphead;
while (ptail->next)
{
prev = ptail;
ptail = ptail->next;
}
prev->next = NULL;
free(ptail);
ptail = NULL;
}
}
void SLTPopFront(SLTNode** pphead)//头删
{
assert(pphead);
SLTNode* next = (*pphead)->next;
free(*pphead);
*pphead = next;
}
void SLTInsertAfter(SLTNode* pos, SLTDataType x)//指定位置之后插入结点
{
assert(pos);
SLTNode* newnode = SLTbuyNode(x);
newnode->next = pos->next;
pos->next = newnode;
}
void SLTInsertFront(SLTNode** pphead, SLTNode* pos, SLTDataType x)//指定位置之前插入结点
{
assert(pphead && pos);
if (pos == *pphead)//判断是否为第一个结点之前
{
SLTPushFront(pphead, x);
}
else//不是第一个结点之前
{
SLTNode* newnode = SLTbuyNode(x);
SLTNode* prev = *pphead;
while (prev->next != pos)
{
prev = prev->next;
}
prev->next = newnode;
newnode->next = pos;
}
}
void SLTErase(SLTNode** pphead, SLTNode* pos)//删除指定位置结点
{
assert(pphead && pos);//判断是否为头结点
if (pos == *pphead)
{
SLTPopFront(pphead);
}
else//不是头结点
{
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);
SLTNode* del = pos->next;
pos->next = del->next;
free(del);
del = NULL;
}
SLTNode* SLTFind(SLTNode* phead, SLTDataType x)//链表元素的查找
{
SLTNode* pcur = phead;
while (pcur)
{
if (pcur->data == x)
{
return pcur;
}
pcur = pcur->next;
}
return NULL;
}
void SLTPrint(SLTNode* phead)//链表元素的打印
{
SLTNode* pcur = phead;
while (pcur)
{
printf("%d -> ", pcur->data);
pcur = pcur->next;
}
printf("NULL\n");
}
void SLTDestroy(SLTNode** pphead)//链表的销毁
{
SLTNode* pcur = *pphead;
while (pcur)
{
SLTNode* next = pcur->next;
free(pcur);
pcur = next;
}
*pphead = NULL;
}
4.3测试文件——test.c及代码展示
4.3.1新结点创建演示
4.3.2尾插演示
4.3.3头插演示
4.3.4尾删演示
4.3.5头删演示
4.3.6元素查找演示
4.3.7指定位置之后插入演示
4.3.8指定位置之前插入演示
4.3.9删除pos结点演示
4.3.10删除pos之后结点演示
4.3.11单链表销毁演示
#include "SList.h"
test01()
{
//创建4个新结点
SLTNode* node1 = (SLTNode*)malloc(sizeof(SLTNode));
SLTNode* node2 = (SLTNode*)malloc(sizeof(SLTNode));
SLTNode* node3 = (SLTNode*)malloc(sizeof(SLTNode));
SLTNode* node4 = (SLTNode*)malloc(sizeof(SLTNode));
node1->data = 1;
node2->data = 2;
node3->data = 3;
node4->data = 4;
node1->next = node2;
node2->next = node3;
node3->next = node4;
node4->next = NULL;
SLTNode* plist = node1;
//SLTPrint(plist);
}
test02()
{
SLTNode* plist = NULL;
//SLTPushBack(&plist, 1);
//SLTPushBack(&plist, 2);
//SLTPushBack(&plist, 3);
//SLTPushBack(&plist, 4);
//SLTPrint(plist);
SLTPushFront(&plist, 1);
SLTPushFront(&plist, 2);
SLTPushFront(&plist, 3);
SLTPushFront(&plist, 4);
//SLTPrint(plist);
//SLTPopBack(&plist);
//SLTPrint(plist);
//SLTPopFront(&plist);
//SLTPrint(plist);
//SLTNode* find = SLTFind(plist, 4);
SLTDestroy(plist);
SLTPrint(plist);
//SLTErase(&plist, find);
//SLTPrint(plist);
//SLTNode* find1 = SLTFind(plist, 3);
//SLTEraseAfter(find1);
//SLTPrint(plist);
//if (find)
//{
// printf("找到了!\n");
//}
//else
//{
// printf("未找到\n");
//}
//SLTInsertAfter( find, 100);
//SLTPrint(plist);
//SLTInsertFront(&plist, find, 100);
//SLTPrint(plist);
//SLTErase(&plist, find);
//SLTEraseAfter(find1);
//SLTDestroy(plist);
//SLTPrint(plist);
}
int main()
{
//test01();
test02();
return 0;
}
五、总结
关于数据结构——单链表的所有知识点,到现在已经全部over了,不知道屏幕前的你消化了多少呢?up个人觉得,单链表这里确实有一点抽象,建议大家反复观看加以理解。最后也希望大家在学习的路上再上一层楼。
学习之路如掌纹, 错综复杂却由你掌控