❀❀❀ 文章由@不准备秃的大伟原创 ❀❀❀
♪♪♪ 若有转载,请联系博主哦~ ♪♪♪
❤❤❤ 致力学好编程的宝藏博主,代码兴国!❤❤❤
哈咯各位铁汁们,大家新年过得快乐吗?反正大伟是过得很快乐,天天就是玩玩玩,吃吃吃 (^▽^ )。不过堕落的生活不能持续太久,所以从今天开始大伟又要继续更新我们的数据结构啦!啪叽啪叽o( ̄ε ̄*)。
OK,那么今天要学的就是我们数据结构中很重要的一环:单链表。
那首先,我们需要知道链表是什么东西:
概念:链表是⼀种物理存储结构上⾮连续、⾮顺序的存储结构,数据元素的逻辑顺序是通过链表中的指针链接次序实现的。
形象一点就是:链表就是一列火车,链表中的每一个节点就是火车的每一节车厢,每一个车厢都是单独存在的,而每两个车厢都是有前后联系的,为了进入中间的某节车厢就需要从这节车厢的上一节或下一节进入。
那在链表中,这些车厢是什么样子的呢:
与顺序表不同的是,链表⾥的每节"⻋厢"都是独⽴申请下来的空间,我们称之为“结点/节点”, 节点的组成主要有两个部分:当前节点要保存的数据和保存下⼀个节点的地址(指针变量)。
图中指针变量plist保存的是第⼀个节点的地址,我们称plist此时“指向”第⼀个节点,如果我们希望plist“指向”第⼆个节点时,就需要指针变量来保存下⼀个节点的位置。那该如何做呢?
结合前⾯学到的结构体知识,我们可以给出每个节点对应的结构体代码:
struct SListNode
{
int data; //节点数据
struct SListNode* next; //指针变量⽤保存下⼀个节点的地址
};
当我们想要保存⼀个整型数据时,实际是向操作系统申请了⼀块内存,这个内存不仅要保存整型数 据,也需要保存下⼀个节点的地址(当下⼀个节点为空时保存的地址为空)。
而当我们想要从第⼀个节点⾛到最后⼀个节点时,只需要在前⼀个节点拿上下⼀个节点的地址(下⼀个 节点的钥匙)就可以了。
和循序表类似,我们的单链表也有增删查改的功能,那接下来就开码把!
当然了,首先我们还是开三个文件:SL.h SL.c test.c 。功能的话不需要多说了吧~
SL.h
#include<stdio.h>
#include<string.h>
#include<assert.h>
#include<stdlib.h>
#include<malloc.h>
typedef int SLDataTpye;
typedef struct SListNode
{
SLDataTpye data;
struct SListNode* next;
}SLNode;
void SLPrint(SLNode* phead);
//打印链表
SLNode* SLBuyNode(SLDataTpye x);
//创建链表节点
void SLPushFront(SLNode** pphead, SLDataTpye x);
//头插
void SLPushBack(SLNode** pphead, SLDataTpye x);
//尾插
void SLPopFront(SLNode** pphead);
//头删
void SLPopBack(SLNode** pphead);
//尾删
SLNode* SLFind(SLNode* phead, SLDataTpye x);
//查找链表元素
void SLInsertFront(SLNode** pphead, SLNode* pos, SLDataTpye x);
//在链表某位置前插入
void SLInsertAfter(SLNode** pphead, SLNode* pos, SLDataTpye x);
//在链表某位置后插入
void SLErase(SLNode** pphead, SLNode* pos);
//删除某位置(下标)元素
void SLEraseAfter(SLNode* pos);
//删除某位置元素(下标)后面一个位置的元素
void SLDestroy(SLNode* phead);
//摧毁链表
SL.c
SLPrint
void SLPrint(SLNode* phead)
{
SLNode* cur = phead;
//cur指向头结点,然后依次往下走
while (cur)
{
printf("%d->", cur->data);
cur = cur->next;
}
printf("NULL");
}
SLBuyNode
SLNode* SLBuyNode(SLDataTpye x)
{
SLNode* newnode = (SLNode*)malloc(sizeof(SLNode));
//每次开辟一个空间
if (newnode == NULL)
{
perror("malloc");
return;
}
//若开辟失败
newnode->data = x;
newnode->next = NULL;
return newnode;
}
SLPushFront
void SLPushFront(SLNode** pphead, SLDataTpye x)
{
assert(pphead);
SLNode* newnode = SLBuyNode(x);
//在链表头结点前插入,然后将头结点设置为新插入的节点
newnode->next = *pphead;
*pphead = newnode;
}
SLPushBack
void SLPushBack(SLNode** pphead, SLDataTpye x)
{
assert(pphead);
SLNode* newnode = SLBuyNode(x);
//若链表为空则直接将新的节点设置为头结点
if (*pphead == NULL)
{
*pphead = newnode;
}
//若链表不为空则循环找到最后一个节点,再将新创建的节点接到尾节点后面
else
{
SLNode* tail = *pphead;
while (tail->next)
{
tail = tail->next;
}
tail->next = newnode;
}
}
SLPopFront
void SLPopFront(SLNode** pphead)
{
assert(pphead);
assert(*pphead);
//让cur保存当前头结点,然后直接让头结点往后走一个,再释放刚刚保存的cur的空间(因为释放了空间且当前空间不再使用,所以里面的值也不需要关心了)
SLNode* cur = *pphead;
*pphead = (*pphead)->next;
free(cur);
}
SLPopBack
void SLPopBack(SLNode** pphead)
{
assert(pphead);
assert(*pphead);
//若链表只有一个元素,则将链表置空
if ((*pphead)->next == NULL)
{
free(*pphead);
*pphead = NULL;
}
//若链表有多余一个元素,找尾节点的前一个节点,再释放掉尾节点
else
{
SLNode* tail = *pphead;
while (tail->next->next)
{
tail = tail->next;
}
free(tail->next);
tail->next = NULL;
}
}
SLFind
SLNode* SLFind(SLNode* phead, SLDataTpye x)
{
SLNode* cur = phead;
//直接while循环找,若找到则返回此节点,若找不到则返回空
while (cur)
{
if (cur->data == x)
{
return cur;
}
cur = cur->next;
}
return NULL;
}
SLInsertFront
void SLInsertFront(SLNode** pphead, SLNode* pos, SLDataTpye x)
{
assert(pphead);
assert(pos);
//若插入的位置正好是头结点,则直接头插
if (pos == *pphead)
{
SLPushFront(pphead, x);
}
//若不是,则找到需要插入节点的前一个节点,然后将值为x的节点插入中间
else
{
SLNode* prev = *pphead;
while (prev->next != pos)
{
prev = prev->next;
}
SLNode*newnode = SLBuyNode(x);
newnode->next = pos;
prev->next = newnode;
}
}
SLInsertAfter
void SLInsertAfter(SLNode** pphead, SLNode* pos, SLDataTpye x)
{
assert(pphead);
assert(pos);
SLNode* newnode = SLBuyNode(x);
//注意顺序,要先将newnode的next指向pos的next,不然会找不到pos的next节点(大家可以想一想为什么)
newnode->next = pos->next;
pos->next = newnode;
}
SLErase
oid SLErase(SLNode** pphead, SLNode* pos)
{
assert(pphead);
assert(pos);
//若要删除的位置为头结点,则直接头删
if (pos == *pphead)
{
SLPopFront(pphead);
}
//否则则找到此节点的前一个节点,然后让前一个节点的next指向当前节点的next,再释放当前节点的空间
else
{
SLNode* prev = *pphead;
while (prev->next != pos)
{
prev = prev->next;
}
prev->next = pos->next;
free(pos);
}
}
SLEraseAfter
void SLEraseAfter(SLNode* pos)
{
assert(pos);
assert(pos->next);
//直接改变当前节点的next指向(但是要保存当前节点的next,否则不好释放空间)
SLNode* next = pos->next;
pos->next = pos->next->next;
free(next);
}
SLDestroy
void SLDestroy(SLNode* phead)
{
SLNode* cur = phead;
//全部空间释放
while (cur)
{
SLNode* next = cur->next;
free(cur);
cur = next;
}
}
OKOK,以上就是我们的整体代码的主逻辑的实现,那我们再来测试一下吧!
test.c
#include "SL.h"
void test1()
{
SLNode* plist = NULL;
SLPushFront(&plist, 5);//5->NULL
SLPushFront(&plist, 2);//2->5->NULL
SLPushBack(&plist, 7);//2->5->7->NULL
SLPushBack(&plist, 9);//2->5->7->9->NULL
SLPopBack(&plist);//2->5->7->NULL
SLPopFront(&plist);//5->7->NULL
SLNode* pos = SLFind(plist, 7);
SLInsertFront(&plist, pos, 40);//5->40->7->NULL
SLInsertAfter(&plist, pos, 40);5->40->7->40->NULL
SLPrint(plist);
printf("\n");
SLPrint(pos);
//SLErase(&plist, pos);
//printf("\n");
//SLPrint(plist);
SLEraseAfter(pos);//5->40->7->NULL
printf("\n");
SLPrint(plist);
}
int main()
{
test1();
return 0;
}
那我们来看看我们输出的结果是不是和我们的预期一样的呢:
可以看到啊,答案是完全对得上的,所以此刻我们的单链表就完美实现啦(大家私下可以自己试试)!
在最后我给大家提前预支一下:其实单链表一共有八种:
分别画图示意:
那铁汁们来分析一下,我们今天写的单链表是属于哪一种的呢?(σ`д′)σ
对咯,就是单向不带头不循环链表,而这种链表我们通常把它称为单链表,既然我们有八种链表,各位铁汁们,我们私下自己来想一想其他几种链表该怎么实现呢? 大伟将在下一篇博客里给大家介绍单链表的死对头:双向带头循环链表 。请敬请期待哦~
Genius only means hard-working all one's life. 天才只意味着终身不懈地努力
本篇博客也就到此为止了,送大家一碗鸡汤,勉励自己以及这世界上所有追逐梦想的赤子趁年华尚好努力提升自己,莫欺少年穷!