开篇备忘录: “过我嶙峋 拥我九春”
正文开始
问题与思考
前面我们了解了顺序表
链表就可以很好解决以上的问题
首先定义链表类型,和顺序表一样, 定义一个头文件,两个源文件,在头文件中进行链表的定义和函数的声明,以及包含需要的库函数文件
在头文件中定义链表结构体
typedef int SLTDatatype;
//定义结点的结构
typedef struct SListNode
{
SLTDatatype data;
struct SListNode* next;
}SLTNode;
void SLPrint(SLTNode* phead)
{
SLTNode* pcur = phead;
while (pcur != NULL)
{
printf("%d ", pcur->data);
pcur = pcur->next;
}
printf("\n");
}
传入一个形参, 这个形参指向了链表首地址,定义临时变量pcur指向phead, 这是一种良好的习惯, 不直接改变phead, 只要pcur->next不为NULL,就打印pcur->data 的值,然后让pcur指向下一个结点。
这里先手动创建一些结点, 后面实现了插入函数, 就不需要手动创建了
SLTNode* node1 = (SLTNode*)malloc(sizeof(SLTNode));
SLTNode* node2 = (SLTNode*)malloc(sizeof(SLTNode));
SLTNode* node3 = (SLTNode*)malloc(sizeof(SLTNode));
SLTNode* node4 = (SLTNode*)malloc(sizeof(SLTNode));
node1->data = 1;
node2->data = 2;
node3->data = 3;
node4->data = 4;
node1->next = node2;
node2->next = node3;
node3->next = node4;
node4->next = NULL;
SLTNode* plist = node1;
SLPrint(plist);
打印结果
尾插
首先这里需要进行传实参, 因为phead的值我可能会改变, 而不是去改变一个临时拷贝的变量, 比如说开始phead所指向的为NULL, 我需要插入一个结点而让phead指向这个结点, 这里需要断言你给我传递的链表首结点地址的地址不可以为NULL, 分为两种情况, 一开始就是空链表和非空链表, 再插入之前都需要开辟一个结点, 直接可以封装成函数.
SLTNode* SLTBuyNode(SLTDatatype x)
{
SLTNode* newnode = (SLTNode*)malloc(sizeof(SLTNode));
if (newnode == NULL)
{
perror("malloc fail");
exit(1);
}
newnode->data = x;
newnode->next = NULL;
return newnode;
}
//尾插
void SLPushBack(SLTNode** pphead, SLTDatatype x)
{
assert(pphead);
//空链表和非空链表
SLTNode* newnode = SLTBuyNode(x);
if (*pphead == NULL)//如果链表为空
{
*pphead = newnode;
}
else
{
SLTNode* ptail = *pphead;//寻找最后一个节点
while (ptail->next)//分为两种情况,否则这里会对NULL解引用
{
ptail = ptail->next;
}
ptail->next = newnode;//找到之后让它的指针指向新的结点
}
}
头插
这里会相对简单, 因为头插无论是否为空链表, 代码都可以插入, 让新节点的指针指向头结点, 再让头指针指向新的结点,此时头结点就是新节点
void SLPushFront(SLTNode** pphead, SLTDatatype x)
{
assert(pphead);
SLTNode* newnode = SLTBuyNode(x);
newnode->next = *pphead;
*pphead = newnode;
}
测试一下
SLTNode* plist = NULL;
SLPushBack(&plist, 1);
SLPrint(plist);
SLPushFront(&plist, 2);
SLPrint(plist);
SLPushFront(&plist, 3);
SLPrint(plist);
运行结果:
尾删
这里还是分两种情况, 看代码注释
void SLPopBack(SLTNode** pphead)
{
assert(pphead && *pphead);//这里链表的地址不能为空,链表也不能为空,否则无结点删除
if ((*pphead)->next == NULL)
{
free(*pphead);
*pphead = NULL;
}
else
{
SLTNode* prev = *pphead;//寻找尾结点的前一个结点
SLTNode* ptail = *pphead;
while (ptail->next)//如果删除头结点,这里终止循环
{
prev = ptail;
ptail = ptail->next;
}
free(ptail);
ptail = NULL;
prev->next = NULL;//让尾节点的前一个结点指针指向NULL
//这里就会非法访问
}
}
头删
这里只需要先保存头结点的下一个结点的位置,然后释放掉头结点, 让保存的结点成为新的头结点
void SLPopFront(SLTNode** pphead)
{
assert(pphead && *pphead);
SLTNode* next = (*pphead)->next;
free(*pphead);
*pphead = next;
}
代码测试:
SLTNode* plist = NULL;
SLPushBack(&plist, 1);
SLPrint(plist);
SLPushFront(&plist, 2);
SLPrint(plist);
SLPushFront(&plist, 3);
SLPrint(plist);
SLPopBack(&plist);
SLPrint(plist);
SLPopFront(&plist);
SLPrint(plist);
下面附上完整代码:
头文件:
#pragma once
#include
#include
#include
typedef int SLTDatatype;
//定义结点的结构
typedef struct SListNode
{
SLTDatatype data;
struct SListNode* next;
}SLTNode;
//打印
void SLPrint(SLTNode* phead);
//尾插
void SLPushBack(SLTNode** pphead, SLTDatatype x);
//头插
void SLPushFront(SLTNode** pphead, SLTDatatype x);
//尾删
void SLPopBack(SLTNode** pphead);
//头删
void SLPopFront(SLTNode** pphead);
函数的实现文件:
#define _CRT_SECURE_NO_WARNINGS 1
#include"SList.h"
void SLPrint(SLTNode* phead)
{
SLTNode* pcur = phead;
while (pcur != NULL)
{
printf("%d ", pcur->data);
pcur = pcur->next;
}
printf("\n");
}
SLTNode* SLTBuyNode(SLTDatatype x)
{
SLTNode* newnode = (SLTNode*)malloc(sizeof(SLTNode));
if (newnode == NULL)
{
perror("malloc fail");
exit(1);
}
newnode->data = x;
newnode->next = NULL;
return newnode;
}
//尾插
void SLPushBack(SLTNode** pphead, SLTDatatype x)
{
assert(pphead);
//空链表和非空链表
SLTNode* newnode = SLTBuyNode(x);
if (*pphead == NULL)
{
*pphead = newnode;
}
else
{
SLTNode* ptail = *pphead;
while (ptail->next)
{
ptail = ptail->next;
}
ptail->next = newnode;
}
}
//头插
void SLPushFront(SLTNode** pphead, SLTDatatype x)
{
assert(pphead);
SLTNode* newnode = SLTBuyNode(x);
newnode->next = *pphead;
*pphead = newnode;
}
//尾删
void SLPopBack(SLTNode** pphead)
{
assert(pphead && *pphead);
if ((*pphead)->next == NULL)
{
free(*pphead);
*pphead = NULL;
}
else
{
SLTNode* prev = *pphead;
SLTNode* ptail = *pphead;
while (ptail->next)
{
prev = ptail;
ptail = ptail->next;
}
free(ptail);
ptail = NULL;
prev->next = NULL;
}
}
//头删
void SLPopFront(SLTNode** pphead)
{
assert(pphead && *pphead);
SLTNode* next = (*pphead)->next;
free(*pphead);
*pphead = next;
}
链表是一种数据结构,由一系列节点组成,每个节点包含数据和指向下一个节点的指针。链表的插入、删除操作相对于数组来说更为灵活。
头插法和尾插法是链表的两种常见插入操作。头插法是将新节点插入到链表的头部,即新节点的next指针指向原来的头节点,然后更新头节点指针;而尾插法是将新节点插入到链表的尾部,即原来的尾节点的next指针指向新节点,然后更新尾节点指针。
头删和尾删是链表的两种常见删除操作。头删是将链表的头节点删除,即更新头节点指针为原头节点的next指针;尾删是将链表的尾节点删除,即更新尾节点指针为倒数第二个节点,并将倒数第二个节点的next指针置为空。
本文分享先到这, 如果认为文章有帮助的话,感谢点赞收藏关注! ! !
完