文章目录
顺序表容易造成空间浪费等缺点,至此弥补这类缺点的链表应运而生。但单链表还不是常规线性表的最佳线性结构,任有许多缺点。
单链表是一个逻辑连续而空间不连续的线性结构。
单链表操作逻辑图:
单链表的优缺点
优:
- 灵活的空间使用率。
- 优秀的插删效率。
- 没有扩容风险。
缺:
- 容易造成空间碎片(内存堆区)
- 不支持随机访问
单链表的定义和创建节点、打印
这次单链表是无头节点的单链表,所以无需顺序表似的初始化。
单链表的定义
链表是一种物理存储结构上非连续、非顺序的存储结构,数据元素的逻辑顺序是通过链表中的指针链 接次序实现的 。
typedef int SLTDateType; //给数据类型起别名
typedef struct SListNode
{
SLTDateType data; //数据元素
struct SListNode* next; //下个节点的地址
}SListNode;
单链表创建新节点
SListNode* BuySListNode(SLTDateType x) {
SListNode* ret = (SListNode*)calloc(1, sizeof(SListNode));//申请一个节点空间
ret->data = x;//给新节点赋值
return ret;//返回新结点地址
}
这里创建结点空间使用calloc
函数,这样创建出来的新结点所有内容为0,next
则为NULL,以便下面,尾插和尾删。
单链表打印
void SListPrint(SListNode* plist) {
SListNode* tmp = plist;
while (tmp)
{
printf("%d->", tmp->data);//打印data
tmp = tmp->next;//进入下一个节点
}
printf("NULL");
}
- 打印只需要便利指针
next
是否为空,然后打印data
即可。
单链表的增、删、查、改
增加
单链表常规添加也分为:头插和尾插两种方法,以及任意位置插入。
头插
头插分两种情况:
- 头指针所指向的地址有数据。
- 头指针所指向的地址没有数据。
第一种情况,逻辑图:
第二种情况,逻辑图:
所以我们在设计头插方法需要着重注意这两个情况
void SListPushFront(SListNode** pplist, SLTDateType x) {//考虑到第二种情况,使用二级指针即可改变外部指针链表情况。
assert(pplist);//判断传入的二级指针是否为空指针
SListNode* newNode = BuySListNode(x);//创建数据结点
if (*pplist == NULL)//区分情况。
{
*pplist = newNode;//第二种情况给头指针指向新结点
return;
}
newNode->next = *pplist;//将头指针所指向的结点赋值给newNode的next变量
*pplist = newNode;//给头指针指向新结点
}
尾插
尾插也分两种情况:
- 链表里没有数据,则直接给头指针赋值新结点地址即可。
- 链表里拥有数据,则需要便利链表找到最后一个结点。
情况一与头插法情况二相同,所以看头插情况二逻辑图即可,这里描述第二个情况逻辑图:
void SListPushBack(SListNode** pplist, SLTDateType x) {
assert(pplist);
SListNode* newNode = BuySListNode(x);
if (*pplist == NULL)//判断链表是否是空链表
{
*pplist = newNode;//给空链表指针指向新结点
return;
}
SListNode* tmp = *pplist;//创建一个临时变量,用于找到尾结点
while (tmp->next)//寻找尾结点
{
tmp = tmp->next;
}
tmp->next = newNode;//让尾结点指向新结点
}
删除
头删和尾删、以及任意位置删除。
头删
头删也是两种情况:
- 若是链表为空,则无需删除
- 链表有数据,则改变指针。
情况二 逻辑图:
void SListPopFront(SListNode** pplist) {
assert(pplist);
assert(*pplist);//若链表为空,报错
SListNode* phead = *pplist;//为注销第一个结点做准备
*pplist = phead->next;//让头指针指向下一个结点
free(phead);//释放第一个结点
}
尾删
头删也是两种情况:
- 若是链表为空,则无需删除。
- 链表有数据,则找到倒数第二个节点;并释放掉最后一个结点。
情况二,逻辑图:
void SListPopBack(SListNode** pplist) {
assert(pplist);
assert(*pplist);//检测单链表是否为空表
SListNode* tmp = *pplist;//不改变链表指针,创建一个变量
while (tmp->next->next)//寻找倒数第二个结点
{
tmp = tmp->next;
}
SListNode* Del = tmp->next;//为释放最后一个结点做准备
tmp->next = NULL;//将倒数第二个结点做为最后一个结点
free(Del);//释放原最后一个结点
}
查找和修改、以及任意位置的添加和删除
查找
SListNode* SListFind(SListNode* plist, SLTDateType x) {
assert(plist);
while (plist->data != x)//寻找数据为x的结点
{
plist = plist->next;
}
return plist;//若找到则返回该节点的地址
} //若找不到则返回NULL
修改
修改则是在外部测试函数内修改即可
SListNode * findNode = SListFind(plsit,6);//寻找该数据的地址
if(findNode != NULL)
{
findNode->data = 66;//修改数据。
}
任意位置的添加
逻辑图:
由于pos
位置我们可以通过查找函数来获取,所以我们只需创建结点和修改指针即可。
void SListInsertAfter(SListNode* pos, SLTDateType x) {
assert(pos);
SListNode* newNode = BuySListNode(x);//创建结点
newNode->next = pos->next;//让newNode下一个结点为pos的下一个结点
pos->next = newNode;//让pos指向newNode,即在pos后插入newNode
}
任意位置的插入的复用
头插:
void SListPushFront(SListNode** pplist, SLTDateType x) {//考虑到第二种情况,使用二级指针即可改变外部指针链表情况。
assert(pplist);
SListInsertAfter(*pplist,x);
}
尾插:
void SListPushBack(SListNode** pplist, SLTDateType x) {
assert(pplist);
SListNode* tmp = *pplist;//创建一个临时变量,用于找到尾结点
while (tmp->next)//寻找尾结点
{
tmp = tmp->next;
}
SListInsertAfter(tmp,x);
}
任意位置的删除
只需位置即可删除
逻辑图:
void SListEraseAfter(SListNode* pos) {
assert(pos);
SListNode* tmp = pos->next;//使用tmp保存待会需要释放的结点地址
pos->next = tmp->next;//给pos的next变量指向待删除结点的下一个结点
free(tmp);//释放需要删除的结点
}
任意位置的删除的复用
头删:
void SListPopFront(SListNode** pplist) {
assert(pplist);
assert(*pplist);//若链表为空,报错
SListEraseAfter(*pplist);
}
尾删:
void SListPopBack(SListNode** pplist) {
assert(pplist);
assert(*pplist);//检测单链表是否为空表
SListNode* tmp = *pplist;//不改变链表指针,创建一个变量
while (tmp->next->next)//寻找倒数第二个结点
{
tmp = tmp->next;
}
SListEraseAfter(tmp);
}
销毁单链表
遍历结点,在遍历的过程中挨个释放结点。
逻辑图:
void SListDestroy(SListNode* plist) {
while (plist)
{
SListNode* fe = plist;//保存待释放的结点
plist = plist->next;//更新循环调节,plist走向下个结点
free(fe);//释放结点。
}
}
附原码:
#include "SListNode.h"
SListNode* BuySListNode(SLTDateType x) {
SListNode* ret = (SListNode*)calloc(1, sizeof(SListNode));
ret->data = x;
return ret;
}
void SListPrint(SListNode* plist) {
SListNode* tmp = plist;
while (tmp)
{
printf("%d->", tmp->data);
tmp = tmp->next;
}
printf("NULL");
}
void SListPushBack(SListNode** pplist, SLTDateType x) {
assert(pplist);
SListNode* newNode = BuySListNode(x);
if (*pplist == NULL)
{
*pplist = newNode;
return;
}
SListNode* tmp = *pplist;
while (tmp->next)
{
tmp = tmp->next;
}
tmp->next = newNode;
}
void SListPushFront(SListNode** pplist, SLTDateType x) {
assert(pplist);
SListNode* newNode = BuySListNode(x);
if (*pplist == NULL)
{
*pplist = newNode;
return;
}
newNode->next = *pplist;
*pplist = newNode;
}
void SListPopBack(SListNode** pplist) {
assert(pplist);
assert(*pplist);
SListNode* tmp = *pplist;
while (tmp->next->next)
{
tmp = tmp->next;
}
SListNode* Del = tmp->next;
tmp->next = NULL;
free(Del);
}
void SListPopFront(SListNode** pplist) {
assert(pplist);
assert(*pplist);
SListNode* phead = *pplist;
*pplist = phead->next;
free(phead);
}
SListNode* SListFind(SListNode* plist, SLTDateType x) {
assert(plist);
while (plist->data != x)
{
plist = plist->next;
}
return plist;
}
void SListInsertAfter(SListNode* pos, SLTDateType x) {
assert(pos);
SListNode* newNode = BuySListNode(x);
newNode->next = pos->next;
pos->next = newNode;
}
void SListEraseAfter(SListNode* pos) {
assert(pos);
SListNode* tmp = pos->next;
pos->next = tmp->next;
free(tmp);
}
void SListDestroy(SListNode* plist) {
while (plist)
{
SListNode* fe = plist;
plist = plist->next;
free(fe);
}
}