今天这篇博客我会介绍链表以及它的实现
链表的定义
链表是一种物理存储结构上非连续且非顺序的存储结构,链表的逻辑顺序通过各个节点之间的指针链接实现
链表的结构
链表的结构可以这样表示:
可以把它理解成一辆小火车,指针起到链接每个车厢的作用
链表有很多种类,包括带头/不带头、循环/非循环、双向/非双向
带头链表
带头链表相比不带头链表,多出了一个不存储任何数据的头节点,这个头节点又被称为“哨兵位”
循环链表
循环链表最后一个节点的指针指向它的头节点,这让它在逻辑上形成了一个闭环
双向链表
双向链表的每个节点多出了一个指针,用于指向该节点的前一个节点,这样我们就能在访问一个节点时回到它的前一个节点
链表的实现
接下来我会演示无头单向非循环单链表的实现,学会这一个后就能触类旁通实现其他链表
先讲讲一个节点是怎么出来的,各个节点是怎么链接的
节点的创建
第一步:开辟节点
链表一个节点的代码表示
struct plist
{
DataType Data;
struct plist* next;
};
首先创建链表的第一个节点,它的next指针指向NULL,同时我们需要记录它的地址,用于在之后访问整个链表
第二步:链接节点
创建第二个节点之后,我们需要对这几个节点进行链接,具体就是将一个节点的next指针指向下一个节点,往后需要以这个地址来访问整个链表
链表的基本操作
链表的基本操作包括增删查改,包括:
头部插入/尾部插入节点
头部删除/尾部删除节点
链表节点的查找
指定位置插入/删除节点
销毁链表
几点需要注意的地方:
1.对于这个的查找和尾部删除等操作,都需要通过保存的第一个节点的位置来遍历整个链表进行
2.对各个节点的next指针进行的操作都需要使用二级指针,因为我们对链表的增删查改等操作时改变了next指针,事实上我们是改变了这个next指针的地址。当我们只是将一级指针作为参数传给函数时,相当于只是将一级指针作为形式参数在函数中进行操作,影响不了这个一级指针的地址
(我在尽力用我有限的文化来解释这个原因)
3.对于每个函数接口,我们需要进行assert操作,来预防对函数的不正确操作引起的程序崩溃等问题
4.可以将链表的数据类型统一typedef,这样方便各种数据类型的存储
链表的完整代码
typedef int DataType;
#include<assert.h>
#include <stdio.h>
#include <stdlib.h>
typedef struct plist
{
DataType Data;
struct plist* next;
}SList;
//打印链表
void plistPrint(SList** pphead)
{
assert(pphead);
if (*pphead != NULL)
{
SList* cur = *pphead;
while (cur)
{
printf("%d->", cur->Data);
cur = cur->next;
}
printf("NULL");
}
else
printf("NULL");
}
//开辟节点
SList* BuyNode(DataType x)
{
SList* node = (SList*)malloc(sizeof(SList));
node->Data = x;
node->next = NULL;
return node;
}
//尾插
void PushBack(DataType x, SList** pphead)
{
SList* node = BuyNode(x);
if (*pphead == NULL)
{
*pphead = node;
return;
}
SList* cur = *pphead;
while (cur->next)
{
cur = cur->next;
}
cur->next = node;
}
//头插
void PushFront(DataType x, SList** pphead)
{
assert(pphead);
SList* SLNode = BuyNode(x);
SLNode->next = *pphead;
*pphead = SLNode;
}
//尾删
void PopBack(SList** pphead)
{
assert(pphead);
assert(*pphead);
//单个节点
if ((*pphead)->next == NULL)
{
*pphead = NULL;
}
else
{
//多个节点
SList* prev = *pphead;
SList* cur = (*pphead)->next;
while (cur->next)
{
cur = cur->next;
prev = prev->next;
}
free(cur);
cur = NULL;
prev->next = NULL;
}
}
//查找
SList* SLFind(SList** pphead,DataType x)
{
assert(pphead);
assert(*pphead);
SList* cur = *pphead;
while (cur->Data != x)
{
cur = cur->next;
}
if (cur->Data == x)
return cur;
else
return NULL;
}
//选择插入(在节点前插入)
void SLInsert(SList** pphead, SList* pos, DataType x)
{
assert(pphead);
assert(pos);
assert(*pphead);
//只有一个节点
if (pos == *pphead)
{
PushFront(x,pphead);
}
//多个节点
else
{
SList* node = BuyNode(x);
SList* cur = (*pphead)->next;
SList* prev = *pphead;
while (cur != pos)
{
cur = cur->next;
prev = prev->next;
}
prev->next = node;
prev->next->next = cur;
}
}
//选择插入(在之后插入)
void InsertAfter(SList* pos, DataType x)
{
assert(pos);
SList* node = BuyNode(x);
node->next = pos->next;
pos->next = node;
}
//删除在pos之后的数
void PopAfter(SList* pos)
{
assert(pos);
if (pos->next == NULL)
return;
else
{
SList* tmp = pos->next;
pos->next = pos->next->next;
free(tmp);
tmp = NULL;
}
}
//头删
void PopFront(SList** pphead)
{
assert(pphead);
SList* tmp = *pphead;
//只有一个节点
if ((*pphead)->next == NULL)
{
free(*pphead);
*pphead = NULL;
}
else
{
SList* tmp = *pphead;
*pphead = (*pphead)->next;
free(tmp);
tmp = NULL;
}
}
//删除pos位置
void SLErase(SList** pphead, SList* pos)
{
assert(pphead);
assert(*pphead);
assert(pos);
if (pos == *pphead)
{
PopFront(pphead);
}
else
{
SList* cur = (*pphead)->next;
SList* prev = *pphead;
while (cur != pos)
{
cur = cur->next;
prev = prev->next;
}
prev->next = pos->next;
free(pos);
pos = NULL;
}
}
//销毁链表
void SLDestroy(SList** pphead)
{
assert(pphead);
SList* cur = (*pphead);
while (cur)
{
SList* next = cur->next;
free(cur);
cur = NULL;
}
*pphead = NULL;
}