目录
链表是什么?
链表是一种物理存储结构上非连续、非顺序的存储结构,数据元素的逻辑顺序是通过链表 中的指针链接次序实现的 。
顺序表依靠连续的空间、连续的存储以找到数据;
链表依靠 节点(如下,一个大框就是一个节点) 之间的环环相扣的指针以找到数据。
- ⭐一个关键:链表的实际头指针 必须有一个指针指向头节点!
无头+单向+非循环链表增删查改实现
(👆链表的其中一类)
- 0.头文件定义
// slist.h
typedef int SLTDateType;
typedef struct SListNode
{
SLTDateType data;
struct SListNode* next;
}SListNode;
- 1. 动态申请一个节点BuySListNode,将传过来的数据 x 储存在节点中
SListNode* BuySListNode(SLTDateType x)
{
SListNode* newSList = (SListNode*)malloc(sizeof(SListNode));
if (!newSList)
{
perror("BuySListNode()");
exit(-1);
}
newSList->data = x;
newSList->next = NULL;
return newSList;
}
int main()
{
SListNode* p1 = BuySListNode(1);
SListNode* p2 = BuySListNode(2);
SListNode* p3 = BuySListNode(3);
SListNode* p4 = BuySListNode(4);
SListNode* p5 = BuySListNode(5);
p1->next = p2;
p2->next = p3;
p3->next = p4;
p4->next = NULL;
}
- 2. 创建一个单链表
头节点很重要!!!!
// 创建一个单链表
SListNode* CreateSList(SLTDateType x)
{
SListNode* phead = NULL, * ptail = NULL;
//phead是头节点指针
//ptail是尾节点指针
for (int i = 0; i <= x; i++)
{
//先创建一个节点
SListNode* newSList = BuySListNode(i + 10);
if (!phead)//头节点很重要!!!!!如果头节点为NULL则需要换“新头”
{
phead = ptail = newSList;
}
else
{
ptail->next = newSList;
ptail = ptail->next;
}
}
return phead;
}
- 3. 打印
依此打印,当找到一个节点中存储的指针为NULL时则找到了尾节点
// 单链表打印
void SListPrint(SListNode* plist)
{
for (int i = 0; plist->next; i++)
{
printf("[ %d | %p ]\n", plist->data, plist);
plist = plist->next;
}
printf("[ %d | NULL ]\n", plist->data);
}
- 4. 单链表尾插PushBack
(1)找尾节点(注意:如果是空链表,则头就是尾,尾插后头节点的指针将要被修改)
(2)函数中通过传参修改变量的内容,只能通过传此变量的地址(指针),再对这个指针解引用去修改。(例如:改变一级指针变量存储的数据内容就得给函数传一级指针变量的地址,也就是二级指针)
(3)找到尾节点后将其指向新开辟的节点即可
// 尾插 找 尾(“原尾”)
void SListPushBack(SListNode** pplist, SLTDateType x)
{
SListNode* newSList = BuySListNode(x);
//找到未插入前的 原单链表 的 尾ptail(注意:找尾勿丢头)
//SListNode* phead = plist;
SListNode* ptail = *pplist;
//如果在空指针之后进行尾插 → 单链表 头 的指针内容将要被修改
if (!ptail)//如果patil为NULL
{
*pplist = newSList;
//只有对某变量的指针进行解引用才能修改此变量中的值
}
else
{
while (ptail->next)
{
ptail = ptail->next;
}
ptail->next = newSList;
}
}
- 5. 单链表的头插PushFront
(1)改新创建的节点指向的地址为原头节点的地址
(2)换头
// 头插找 头(“原头”)
void SListPushFront(SListNode** pplist, SLTDateType x)
{
SListNode* newSList = BuySListNode(x);
newSList->next = *pplist;
*pplist = newSList;
}
- 6. 尾删PopBack
(1)判断一下,有的删才删
(2)找到尾节点,尾节点地址释放,尾节点前面一个节点指向NULL(所以需要一个变量记录前一个节点的地址)
// 单链表的尾删
void SListPopBack(SListNode** pplist)
{
assert(*pplist);
SListNode* ptail = *pplist;
SListNode* prev = *pplist;
while (ptail->next)
//当ptail->next为NULL则意味着ptail指向最后一个节点
{
prev = ptail;
ptail = ptail->next;
}
free(ptail);
//这个ptail变量没必要置空,只存在函数内,出函数作用域就会被销毁
prev->next = NULL;
}
ps.可能被访问的指针变量一定要置空,不可能被访问的置不置空都可以
- 7. 头删PopFront
(1)判断能不能删
(2)记录原头的地址 nowphead 和 即将要成为新头的下一个头节点 newhead 的地址
(3)原头节点的地址释放,其中存储的地址置空
(4)换新头
// 单链表头删
void SListPopFront(SListNode** pplist)
{
assert(*pplist);
SListNode* nowphead = *pplist;
SListNode* newphead = (*pplist)->next;
(*pplist)->next = NULL;
free(*pplist);
*pplist = newphead;//不然就没头了
}
- 8. 查找
依此查找节点,找一个核对一次数据
SListNode* SListFind(SListNode* plist, SLTDateType x)
{
for (SListNode* ptail = plist; ptail; ptail = ptail->next)
{
if (x == ptail->data)
{
return ptail;
}
}
printf("未找到!\n");
return NULL;
}
- 9. 单链表在pos位置之后插入x
(1)首先需要判断pos位置的有效性
(2)找到pos后一个节点的地址
(3)插入
void SListInsertAfter(SListNode* pos, SLTDateType x)
{
assert(pos);
SListNode* next = pos->next;
SListNode* newSList = BuySListNode(x);
pos->next = newSList;
newSList->next = next;
}
- 分析思考为什么不在pos位置之前插入?
(1)如果pos是头节点的位置 → 头插 → 头插会改变头节点的指针需要传二级指针
(2)非头插 → 需要找到 pos 前一个节点的指针 → 找到后插入
// 分析思考为什么不在pos位置之前插入?
void SListInsertFront(SListNode** pphead, SListNode* pos, SLTDateType x)
{
//如果pos是头指针的位置,则是头插
if (*pphead == pos)
SListPushFront(pphead, x);//复用头插函数
//非头插
else
{
SListNode* prev = *pphead;
while (prev->next != pos)
{
prev = prev->next;
}
SListNode* newSList = BuySListNode(x);
prev->next = newSList;
newSList->next = pos;
}
}
void TestSList()
{
SListNode* p1 = BuySListNode(1);
SListNode* p2 = BuySListNode(2);
SListNode* p3 = BuySListNode(3);
SListNode* p4 = BuySListNode(4);
SListNode* p5 = BuySListNode(5);
p1->next = p2;
p2->next = p3;
p3->next = p4;
p4->next = NULL;
SListInsertAfter(p3, 5);
SListEraseAfter(p3);
SListPrint(p1);
printf("插入后:\n");
SListInsertFront(&p1, p3, 7);
SListPrint(p1);
}
int main()
{
TestSList();
return 0;
}
输出:
- 10.单链表删除pos位置之后的值
(1)确认pos位置的有效性
(2)找到pos后两个的位置并记录
(3)释放pos后一个空间,并指向被记录的地址
void SListEraseAfter(SListNode* pos)
{
assert(pos);
if (!pos->next)
{
printf("其后已无可删除的数据\n");
exit(-1);
}
SListNode* next = pos->next->next;
free(pos->next);
pos->next = NULL;
pos->next = next;
}
- 分析思考为什么不删除pos位置?
(1)同样的,如果pos是头节点的位置 → 头删 → 头删会改变头节点的指针需要传二级指针
(2)非头删 → 需要找到 pos 前一个节点的指针 → 找到后删除
void SListEraseFront(SListNode** pphead, SListNode* pos)
{
if (*pphead == pos)
SListPopFront(pphead);
else
{
SListNode* prev = *pphead;
while (prev->next != pos)
{
prev = prev->next;
}
prev->next = pos->next;
free(pos);
}
}
- 11. 单链表的销毁
申请多少次空间就需要释放多少次
一个一个释放
// 单链表的销毁
void SListDestroy(SListNode* plist)
{
//申请了多少次空间就要释放多少次!!
assert(plist);
SListNode* cur = plist;
while (cur)
{
SListNode* next = cur->next;
free(cur);
cur = next;
}
}
———————————————————————————————————@fantasy_13_7———END——————————