数据结构——链表(超详细解读)

数据结构——链表(超详细解读)

🌈个人主页:七度光阴;

🎉系列专栏:数据结构与算法

目录

一、🚀链表的概念和结构

二、🚀链表的分类

三、🚀单链表的实现

📝动态申请节点+单链表查找

单链表的尾插+单链表的头插:

📝单链表的尾删+单链表的头删:

📝 单链表在pos位置之后插入x+单链表在pos位置之前插入x

📝删除pos位置的值

📝单链表的销毁

四、🚀所有代码

一、🚀链表的概念和结构

上一节我们了解了顺序表这个结构,同样的顺序结构还有单链表这个结构,通俗的来讲单链表就像一个小火车🚆一样,是一种形式上连续,物理空间上可能不连续的结构!

这里如果用一个指针变量执行我们的下一个节点,那么这就是单链表的结构了

图中的phead指针中存放的是第一个结点的地址,那么根据指着地址我们就可以找到这个结构体,又因为这个结构体中存放了下一个结构体的地址,所以又可以找到第二个结构体,循环往复就可以找到所有的结点,直到存放空地址的结构体。

注:图中的箭头实际上是不存在的,这里只是为了能够方便理解。

注意:

1. 从图中可以看出,链式结构在逻辑上是连续的,但在物理上不一定连续。 2. 现实中的结点一般都是从堆上申请出来的。 3. 从堆上申请的空间,是按照一定的策略来分配的,两次申请的空间可能连续,也可能不连续。

二、🚀链表的分类

实际中链表的结构非常多样,以下情况组合起来就有8种链表结构:

虽然链表结构如此之多,但是我们常用的就只有两种

三、🚀单链表的实现

所谓单链表就是无头+单向+非循环 链表

📝动态申请节点+单链表查找

SListNode* BuySListNode(SLTDateType x)

{

SListNode* newnode = (SListNode*)malloc(sizeof(SListNode));//realloc是调整空间,malloc是申请空间

assert(newnode);

newnode->data = x;

newnode->next = NULL;

return newnode;

}

SListNode* SListFind(SListNode* plist, SLTDateType x)

{

assert(plist);//防止链表为空,链表为空则查找的无意义

SListNode* cur = plist;//一般不希望指向头结点的指针plist被改变,想让plist不动始终指向头结点

while (cur)//保证不漏掉任何一个结点

{

if (cur->data == x)

{

return cur;

}

cur = cur->next;

}

return NULL;

}

单链表的尾插+单链表的头插:

单链表的尾插:

先创建一个新的结点(newnode),然后在创建一个新的指针变量tail,让tail一直遍历,一直遍历到尾结点,然后令尾结点的tail->next = newnode:,然后令newnode->next = NULL;

void SListPushBack(SListNode** pplist, SLTDateType x)

{

SListNode* newnode = BuySListNode(x);//先申请一个结点

if (*(pplist) == NULL)//链表为空时尾插

{

*pplist = newnode;

}

else//链表不为空时

{

SListNode* tail = *pplist;

while (tail->next != NULL)//令tail找到最后一个结点

{

tail = tail->next;

}

tail->next = newnode;

}

}

单链表的头插

先malloc一个结点(newnode),先令newnode->next = *pplist;,然后令*pplist = newnode;,

为什么传的是2级指针呢?

因为是要改变指针的内容,所以传的是指针的指针,然后对其解引用,即可访问指针的内容

void SListPushFront(SListNode** pplist, SLTDateType x)//注意:这里传的是2级指针,改变指针则传指针的地址

{

SListNode* newnode = BuySListNode(x);

if (*pplist == NULL)

{

*pplist = newnode;

}

else

{

newnode->next = *pplist;

*pplist = newnode;

}

}

📝单链表的尾删+单链表的头删:

单链表的尾删🍨

拿一个指针tail指向尾结点,在拿一个指针tailPrev指向尾结点的前一个结点,然后令tailPrev->next = NULL;,假如没拿一个指针tail记录尾结点的地址,当tailPrev->next = NULL;时那就意味着尾结点的地址丢了,此时会造成内存泄漏,因此拿一个指针tail记录尾结点的地址,最后千万不要忘记释放删除的节点free(tail);

void SListInsertAfter(SListNode* pos, SLTDateType x)

{

assert(pos);

SListNode* newnode = BuySListNode(x);

SListNode* next = pos->next;

pos->next = newnode;

newnode->next = next;

}

单链表的头删🍨

拿一个指针(next)记录首结点的下一个结点,然后free(*pplist);,随后在把plist指向新的首结点(*pplist = next:)

void SListPopFront(SListNode** pplist)

{

assert(*pplist);

if ((*pplist)->next == NULL)

{

free(*pplist);

*pplist = NULL;

}

SListNode* next = (*pplist)->next;

free(*pplist);

*pplist = next;

}

📝 单链表在pos位置之后插入x+单链表在pos位置之前插入x

单链表在pos位置之后插入🍨

首先要拿一个next指针记录pos结点之后的一个结点,然后链接pos结点和newnode结点(pos->next = newnode;),随后在链接newnode和next结点(newnode->next = next;),假如不拿一个指针next记录pos的下一个结点,那此时要注意插入的结点和前后结点的链接顺序,假如不注意链接顺序可能会丢掉pos之后结点的地址,导致pos之后的结点无法与新节点newnode链接到一起

void SListInsertAfter(SListNode* pos, SLTDateType x)

{

assert(pos);

SListNode* newnode = BuySListNode(x);

SListNode* next = pos->next;

pos->next = newnode;

newnode->next = next;

}

单链表在指定之前插入🍨

拿一个指针(prev)记录pos之前的一个结点,然后在malloc一个新的结点(newnode),然后令newnode中的next存下pos的地址,在令prev(原来pos之前的一个结点)和newnode链接到一起prev->next = newnode

void SListInsertFront(SListNode**pplist,SListNode* pos, SLTDateType x)

{

assert(pos);

assert(*pplist);

if (pos == *pplist)

{

SListPushFront(pplist, x);

}

else

{

SListNode* prev = *pplist;

while (prev->next != pos)//找到了pos结点之前结点的地址

{

prev = prev->next;

}

SListNode* newnode = BuySListNode(x);

newnode->next = pos;

prev->next = newnode;

}

}

📝删除pos位置的值

先拿一个指针(prev)记录下pos之前的一个结点,在拿一个指针(next)记录下pos之后位置的结点,然后在把prev指向的结点和next指向的结点链接到一起prev->next = next;,然后在free(pos);

void SListErase(SListNode** pplist, SListNode* pos)

{

assert(pos);

assert(*pplist);

//头删

if (pos == *pplist)

{

SListPopFront(pplist);

}

else

{

SListNode* prev = *pplist;

while (prev->next != pos)//找到pos之前的一个结点

{

prev = prev->next;

}

SListNode* next = pos->next;

free(pos);

prev->next = next;

}

}

📝单链表的销毁

建立一个循环,在建立一个指针(prev)去指向首结点(*pplist),然后先把指向首结点的指针(*pplist)去移动指向下一个结点,此时prev相当于记录了原来的首结点,然后在去free(prev);,以此去不断循环,最后当*pplist指向空时循环结束,同时也意味着链表销毁

void SListDestroy(SListNode** pplist)

{

assert(*pplist);//销毁链表一定是链表不为空才去销毁

while (*pplist)

{

SListNode* prev = *pplist;

*pplist = (*pplist)->next;

free(prev);

}

}

四、🚀所有代码

SeList.h

#pragma once

#include

#include

#include

typedef int SLTDateType;

typedef struct SListNode

{

SLTDateType data;

struct SListNode* next;

}SListNode;

// 动态申请一个节点

SListNode* BuySListNode(SLTDateType x);

// 单链表打印

void SListPrint(SListNode* plist);

// 单链表尾插

void SListPushBack(SListNode** pplist, SLTDateType x);

// 单链表的头插

void SListPushFront(SListNode** pplist, SLTDateType x);

// 单链表的尾删

void SListPopBack(SListNode** pplist);

// 单链表头删

void SListPopFront(SListNode** pplist);

// 单链表查找

SListNode* SListFind(SListNode* plist, SLTDateType x);

// 单链表在pos位置之后插入x

void SListInsertAfter(SListNode* pos, SLTDateType x);

//单链表在pos位置之前插入x

void SListInsertFront(SListNode** pplist,SListNode* pos, SLTDateType x);

// 单链表删除pos位置之后的值

void SListEraseAfter(SListNode* pos);

// 删除pos位置

void SListErase(SListNode**pplist,SListNode* pos);

// 单链表的销毁

void SListDestroy(SListNode**pplist);

SeqList.c

#include "SList.h"

void SLTPrint(SLTNode* pphead)

{

assert(pphead);

while (pphead)

{

printf("%d ", pphead->data);

pphead = pphead->next;

}

printf("NULL\n");

}

SLTNode* SLTBuyNode(SLTDataType x)

{

SLTNode* newNode = 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* pur = *pphead;

while (pur->next)

{

pur = pur->next;

}

pur->next = newNode;

}

}

//头插

void SLTPushFront(SLTNode** pphead, SLTDataType x)

{

SLTNode* newNode = SLTBuyNode(x);

newNode->next = *pphead;

*pphead = newNode;

}

//尾删

void SLTPopBack(SLTNode** pphead)

{

assert(pphead&&(*pphead));

SLTNode* pur =*pphead;

if ((*pphead)->next == NULL)

{

free(*pphead);

*pphead = NULL;

}

else

{

while (pur->next->next)

{

pur = pur->next;

}

free(pur->next);

pur->next = NULL;

}

}

//头删

void SLTPopFront(SLTNode** pphead)

{

assert(pphead && *pphead);

SLTNode* newnode = (*pphead)->next;

free(*pphead);

(*pphead) = newnode;

}

//查找节点

SLTNode* SLTFind(SLTNode* phead,SLTDataType x)

{

assert(phead);

while (phead)

{

if (phead->data == x)

return phead;

phead = phead->next;

}

return NULL;

}

//指定位置之后插入

void SLTInsert(SLTNode** pphead, SLTDataType x)

{

assert(pphead && *pphead);

SLTNode* newpphead = SLTBuyNode(x);

SLTNode* pur = (*pphead)->next;

newpphead->next = pur;

(*pphead)->next = newpphead;

}

小结:链表和顺序表是两个兄弟结构,大家如果不了解顺序表的可以点击下方跳转

数据结构——顺序表

🎀 相关推荐

内蒙古自治区概况
microsoft365破解版

内蒙古自治区概况

📅 06-29 👀 2948
左边一个单人旁右边一个同读什么
体育365网投

左边一个单人旁右边一个同读什么

📅 07-02 👀 194
红砖 (Brick) - [MC]我的世界原版 (Minecraft) - MC百科
microsoft365破解版

红砖 (Brick) - [MC]我的世界原版 (Minecraft) - MC百科

📅 07-04 👀 8267