一.前言
嗨嗨嗨,我们又见面了。前面我们已经学习了关于数据结构中的顺序表,今天我们来学习数据结构中的单链表。废话不多说让我们直接开始吧。
二.正文
1.1链表的概念
链表是一种物理存储结构上非连续、非顺序的存储结构,数据元素的逻辑顺序是通过链表中的指针链接次序实现的。
链表的结构跟火车的车厢相似,淡季时车次的车厢会相应减少,旺季时车厢会额外增加几节。只需要将火车里的某节车厢去掉/加上,不会影响其他车厢,每节车厢都是独立存在的。
车厢是独立存在的,且每节车厢都有车门。想象一下这样的场景,假设每节车厢的车门都是锁上的状态,需要不同的钥匙才能解锁,每次只能携带一把钥匙的情况下如何从车头走到车尾?
最简单的做法:每节车厢里都放下一把车厢的钥匙。
在链表里,每节“车厢”是什么样的呢?
与顺序表不同的是,链表里的每节“车厢”都是独立申请下来的空间,我们称之为“节点/结点”
节点的组成主要有两部分:当前节点要保存的数据和保存下一个节点的地址。
图中指针变量plist保存的是第一个节点的地址,我们称plist此时“指向”第一个节点,如果我们希望plist“指向”第二个节点时,只需要修改plist保存的内容为0x0012FFA0。
为什么还需要指针变量来保存下一个节点的位置?
链表中每个节点都是独立申请的(即需要插入数据时采取申请一块节点的空间),我们需要通过指针变量来保存下一个节点位置才能从当前节点找到下一个节点。
1.2单链表的实现
SList.h(用来提前声明函数存在)
#pragma once
#include<stdio.h>
#include<assert.h>
#include<stdlib.h>
typedef int SLTDataType;
typedef struct SListNode//节点的结构由数据data和下一个节点的地址next组成
{
SLTDataType data;
struct SList* next;
}SLTNode;
void SLTPrint();//打印链表
void SLTPushBack();//尾插
void SLTPushFront();//头插
void SLTPopBack();//尾删
void SLTPopFront();//头删
SLTNode* SLTFind();//查找
void SLTInsertBefore();//在指定位置之前插入数据
void SLTInsertAfter();//在指定位置之后插入数据
void SLTErase();//在指定位置删除节点
void SLTEraseAfter();//在指定位置之后删除节点
void SLTDestroy();//销毁链表
SList.c(用来实现函数功能)
#include"SList.h"
SLTNode* SLTBuyNode(SLTDataType x)//创造节点
{
SLTNode* newnode = (SLTNode*)malloc(sizeof(SLTNode));
if (newnode == NULL)
{
perror("malloc fail!");
//return;
exit(1);
}
else
{
newnode->next = NULL;
newnode->data = x;
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)//疑惑点为什么不是*pphead->next
{
ptail = ptail->next;
}
/* while((*pphead)->next)
{
}*/
ptail->next = newnode;
}
}
void SLTPrint(SLTNode* phead)//链表打印
{
SLTNode* pcur = phead;
while (pcur)
{
printf("%d->", pcur->data);
pcur=pcur->next;
}
printf("NULL\n");
}
void SLTPushFront(SLTNode** pphead,SLTDataType x)//头插
{
assert(pphead);
SLTNode* newnode = SLTBuyNode(x);
newnode->next = *pphead;
*pphead = newnode;
}
void SLTPopBack(SLTNode** pphead)//尾删
{
assert(pphead && *pphead);
if ((*pphead)->next==NULL)
{
free(*pphead);
*pphead = NULL;
}
else
{
SLTNode* ptail;
SLTNode* pcur;
ptail = pcur = *pphead;
while (ptail->next)
{
pcur = ptail;
ptail = ptail->next;
}
free(ptail);
ptail = NULL;
pcur->next=NULL;
}
}
void SLTPopFront(SLTNode** pphead)//头删
{
assert(pphead && *pphead);
SLTNode* pcur=*pphead;
*pphead = (*pphead)->next;
free(pcur);
}
SLTNode* SLTFind(SLTNode* phead, SLTDataType x)//查找
{
SLTNode* pcur = phead;
while (pcur)
{
if (pcur->data == x)
{
return pcur;
}
pcur = pcur->next;
}
return NULL;
}
void SLTInsertBefore(SLTNode** pphead,SLTNode* pos,SLTDataType x)//在指定位置前插入节点
{
assert(pphead && *pphead);
assert(pos);
SLTNode* newnode = SLTBuyNode( x);
//先找到目标位置的前一个节点
SLTNode* prev = *pphead;
if (pos==*pphead)
{
SLTPopFront(pphead);
}
else
{
while (prev->next != pos)
{
prev = prev->next;
}
newnode->next = pos;
prev->next = newnode;
}
}
void SLTInsertAfter(SLTNode* pos, SLTDataType x)//在指定位置之后插入节点
{
assert(pos);
SLTNode* newnode = SLTBuyNode(x);
newnode->next = pos->next;
pos->next = newnode;
}
void SLTErase(SLTNode** pphead,SLTNode* pos)//删除指定位置的节点
{
assert(pphead && *pphead);
assert(pos);
SLTNode* prev = *pphead;
if (pos == *pphead)
{
SLTPopFront(pphead);
}
else
{
while (prev->next != pos)
{
prev = prev->next;
}
SLTNode* next = pos->next;
prev->next = next;
free(pos);
pos = NULL;
}
}
void SLTEraseAfter(SLTNode* pos)//删除指定位置之后的节点
{
assert(pos&&pos->next);
SLTNode* next = pos->next;
pos->next = next->next;
free(next);
next = NULL;
}
void SLTDestroy(SLTNode** pphead)//销毁链表
{
assert(pphead&&*pphead);
SLTNode* next = *pphead;
SLTNode* pcur = *pphead;
while (pcur)
{
next = (pcur)->next;
free(pcur);
// pcur = NULL;
pcur = next;
//pcur = next;
}
*pphead = NULL;
}
test.c(用来测试所写代码功能是否正确)
#include"SList.h"
//void test1()//测试尾插和尾删
//{
// SLTNode* plist = NULL;
// SLTPushBack(&plist, 1);
// SLTPushBack(&plist, 2);
// SLTPushBack(&plist, 3);
// SLTPushBack(&plist, 4);
// SLTPopBack(&plist);
// SLTPrint(plist);
//}
//void test2()//测试头插
//{
// SLTNode* plist;
// //plist = NULL;
// SLTPushFront(&plist, 4);
// SLTPushFront(&plist, 3);
// SLTPushFront(&plist, 2);
// SLTPushFront(&plist, 1);
// SLTPrint(plist);
//}
//void test3()//测试头删
//{
// SLTNode* plist =NULL;
// SLTPushBack(&plist, 1);
// SLTPushBack(&plist, 2);
// SLTPushBack(&plist, 3);
// SLTPushBack(&plist, 4);
// SLTPopFront(&plist);
// SLTPrint(plist);
//}
//void test4()//测试查找
//{
// SLTNode* plist = NULL;
// SLTPushBack(&plist, 1);
// SLTPushBack(&plist, 2);
// SLTPushBack(&plist, 3);
// SLTPushBack(&plist, 4);
// SLTNode* find = SLTFind(plist, 4);
//}
//void test5()//测试在指定位置前插入节点
//{
// SLTNode* plist = NULL;
// SLTPushBack(&plist, 1);
// SLTPushBack(&plist, 2);
// SLTPushBack(&plist, 3);
// SLTPushBack(&plist, 4);
// SLTNode* find = SLTFind(plist, 3);
// SLTInsertBefore(&plist, find, 5);
// SLTPrint(plist);
//}
//void test6()//测试在指定位置之后插入节点
//{
// SLTNode* plist = NULL;
// SLTPushBack(&plist, 1);
// SLTPushBack(&plist, 2);
// SLTPushBack(&plist, 3);
// SLTPushBack(&plist, 4);
// SLTNode* find = SLTFind(plist, 4);
// SLTInsertAfter(find, 5);
// SLTPrint(plist);
//}
//test7()//测试在指定位置删除节点
//{
// SLTNode* plist = NULL;
// SLTPushBack(&plist, 1);
// SLTPushBack(&plist, 2);
// SLTPushBack(&plist, 3);
// SLTPushBack(&plist, 4);
// SLTNode* find = SLTFind(plist, 1);
// SLTErase(&plist, find);
// SLTPrint(plist);
//}
//void test8()//测试在指定位置之后删除节点
//{
// SLTNode* plist = NULL;
// SLTPushBack(&plist, 1);
// SLTPushBack(&plist, 2);
// SLTPushBack(&plist, 3);
// SLTPushBack(&plist, 4);
// SLTNode* find = SLTFind(plist, 1);
// SLTEraseAfter(find);
// SLTPrint(plist);
//}
void test9()//测试销毁链表
{
SLTNode* plist = NULL;
SLTPushBack(&plist, 1);
SLTPushBack(&plist, 2);
//SLTPushBack(&plist, 3);
//SLTPushBack(&plist, 4);
SLTDestroy(&plist);
SLTPrint(plist);
}
int main()
{
//test1();
//test2();
//test3();
//test4();
//test5();
//test6();
//test7();
//test8();
test9();
return 0;
}
值得注意的是:上面的test.c只是本人在写单链表的时候测试所写函数功能能否跑得起来而所写的,大家也可以按自己的习惯来测试函数功能,上面代码仅供参考。
三.结文
今天的分享就到此结束了,集帅、集美们咱们下期不见不散~