链表是一种再=在物理上存储是非连续、非顺序的存储结构,数据元素的逻辑顺序是通过链表中的指针链接依次实现的。
链表有双链表和单链表。
单链表如图所示:
上一个结点保存自己的数据和下一个数据的地址,这就是单链表,通过这样的形式把所有结点链接起来。
双链表如图所示:
双链表可以通过指针访问后一个元素,也可以访问前一个元素。
1、自己编写的头文件SList.h
#pragma once
#include
#include
#include
#pragma warning(disable:4996)
typedef int SLTDateType;
typedef struct SListNode//把结点保存在结构体中
{
SLTDateType date;
struct SListNode* next;
}SListNode;
//函数声明
//打印链表函数
void PrintSListNode(SListNode* phead);
//链表尾插函数
void SLNodePushBack(SListNode** pphead, SLTDateType x);
//链表尾删函数
void SLNodePopBack(SListNode** pphead);
//链表头插函数
void SListPushFront(SListNode** pphead, SLTDateType x);
//链表头删函数
void SListNodePopFront(SListNode** pphead);
//链表中在x之后插入函数
void SListNodeInsertAfter(SListNode*pos, SLTDateType x);
//链表查找函数
SListNode* FindSListNode(SListNode* phead, SLTDateType pos);
//链表删除pos位置之后(中间删除)的函数
void SListNodeEraseaAfter(SListNode* pos);
```c
3、源文件SList.c
在SList.c中主要实现单链表的增删查改接口(函数),一定要包含自己写的头文件。
(1).动态申请结点函数:
在我们需要增加数据的时候才会申请结点,并将插入的值给新结点的date,
返回新结点的地址给其他结点(他的前一个或者后一个)保存
```c
SListNode* BuySListNode(SLTDateType x)//创建一个新的结点
{
SListNode* newNode = (SListNode*)malloc(sizeof(struct SListNode));//用malloc函数申请一个节点的大小,
if (newNode == NULL)
{
printf("malloc error\n");
exit(-1);
}
newNode->date = x;//将插入的值给新结点的date
newNode->next = NULL; //新结点的next为NULL
return newNode;//返回新结点的地址
}
(2).打印链表函数:
从链表的头到尾依次打印,cur==NULL时为链表的尾。
void PrintSListNode(SListNode* phead)
{
struct SListNode* cur = phead;
while (cur!= NULL)//从头开始打印,直到最后一个的next为空
{
printf("%d->", cur->date);
cur = cur->next;
}
printf("NULL");
}
(3)、链表尾插函数
有两种情况: 1 链表中 没有结点,没有结点,
此时 pphead==NULL;将增加的值给新结点,将新结点的地址给pphead如图:
2、有多个结点
插入时,首先要找到链表的尾,然后将尾(tail),tail->next=新结点的地址,
还是画图比较容易理解:
代码如下:
void SLNodePushBack(SListNode** pphead, SLTDateType x)
{
//尾插有两种情况 1 没有结点
// 2 有结点
struct SListNode* newNode = BuySListNode(x);//插入就需要创建新的结点
if (*pphead == NULL)
{
*pphead = newNode;//将新的结点地址给头(也是尾)
}
else
{
struct SListNode* tail = *pphead;
while (tail->next != NULL)
{
tail = tail->next;
}
tail->next = newNode;//把新的结点地址给尾
}
}
(3)链表的尾删函数:
尾删分三种情况:1 、没有结点
直接退出,不用删除
2 、只有一个结点
释放掉,然后让链表的头为空(*pphead=NULL)
3 、2个及以上
找到最后一个和他的前一个,然后释放掉最后一个,最后一个的前一个(perv),让perv->next为空
画图理解一下:
void SLNodePopBack(SListNode** pphead)
{
//尾删分三种情况:1 没有结点
// 2 只有一个结点
// 3 2个及以上
if (*pphead == NULL)
{
return;//没有结点直接退出函数
}
else if ((*pphead)->next == NULL)
{
free(*pphead);//有一个结点,释放掉,并让其指向空
*pphead = NULL;
}
else
{
struct SListNode* tail = *pphead;
struct SListNode* perv = NULL;
//有多个结点,先找到最后一个,然后释放掉,再让倒数第二个结点next为空
while (tail->next!= NULL)
{
perv = tail;
tail = tail->next;
}
free(tail);
perv->next = NULL;
}
}
(4)、链表的头插函数
头插就是将要添加的数据当作链表新的头,只要将原来头的地址给新结点的next,再将newNode的地址给*pphead作为新的头。
如图:
代码如下:
`void SListPushFront(struct SListNode** pphead, SLTDateType x)
{
struct SListNode* newNode = BuySListNode(x);
newNode->next = *pphead;
*pphead = newNode;//newNode是新的头
}`
(5)头删函数
将链表的第一个结点删除
分三种情况1 没有结点
如果链表为空,直接退出
// 2 、只有一个结点
// 3 、2个及以上
2和3可以兼容
有两种方法,第一种直接让第二个结点的地址作为头
第二种:先保存下一个结点的地址,再将第一个二结点释放,然后将第二个地址给到*pphead
画图理解一下:
void SListNodePopFront(SListNode** pphead)
{
//分三种情况1 没有结点
// 2 只有一个结点
// 3 2个及以上 2.3可以兼容
if (*pphead == NULL)
{
return;
}
else
{
//两种方法,第一种直接让第二个结点的地址作为头
//*pphead = (*pphead)->next;
//第二种如下:
SListNode* cur = (*pphead)->next;//保存第二个地址
free(*pphead);//释放第一个
*pphead = NULL;
*pphead= cur;//让第二个成为新的头
}
}
(6)、查找函数
将你要查找的值,与链表中所有的结点的date对比,如果有就返回他的地址
代码如下:
SListNode* FindSListNode(SListNode* phead, SLTDateType pos)
{
assert(phead);
SListNode* cur = phead;
while (cur!= NULL)
{
if (cur->date != pos)
{
cur = cur->next;
}
else
{
break;
}
}
if (cur!=NULL)
{
printf("找到了\n");
return cur;
}
else
{
printf("不存在\n");
return NULL;
}
}
(7)、中间插入函数
给定一个位置(pos),再他之后添加
1、先将pos下下一个的地址赋值给新结点(newNde)的next,再将新结点的地址赋值给pos的next,这样就链接起来了
如图所示:
void SListNodeInsertAfter(SListNode* pos, SLTDateType x)
{
assert(pos);
//SListNode* newNode = BuySListNode(x);
//SListNode* temp = pos->next;//先保存pos下一个结点的地址
//pos->next = newNode;//将新结点地址给到pnextNode->next
//newNode->next =pos->next->next;
SListNode*newNode= BuySListNode(x);
newNode->next = pos->next;
pos->next = newNode;
}
(8)、中间删除函数
给定一个位置(pos),紧跟之后的一个删除
将pos的下下一个节点的地址赋给pos->next
代码如下:
void SListNodeEraseaAfter(SListNode* pos)
{
SListNode* next = pos->next;//pos下一个地址
SListNode* nextnext = next->next;//pos下下一个结点地址
pos->next = nextnext;
free(next);//释放pos之后的第一个(next)
next = NULL;
}
测试代码在源文件main.c 中
代码如下:
#include"SList.h"
void text()
{
SListNode* ps = NULL;
SLNodePushBack(&ps, 1);
SLNodePushBack(&ps, 2);
SLNodePushBack(&ps, 3);
SLNodePushBack(&ps, 4);
SLNodePushBack(&ps, 5);
SLNodePushBack(&ps, 6);
SLNodePushBack(&ps, 7);
SLNodePushBack(&ps, 8);
SListPushFront(&ps, 10);
PrintSListNode(ps);
printf("\n");
SListNodePopFront(&ps);
PrintSListNode(ps);
printf("\n");
SListNode* pos = FindSListNode(ps, 5);
SListNodeInsertAfter(pos, 22);
PrintSListNode(ps);
printf("\n");
SListNode* pos2 = FindSListNode(ps, 2);
SListNodeEraseaAfter(pos2);
PrintSListNode(ps);
printf("\n");
}
int main()
{
text();//测试函数
return 0;
}
下边测试代码不一定与main.c中的代码相同
1、尾插,尾删测试如下图:
2、头删,头插函数如下图:
3、链表查找函数测试结果如下:
在链表中找5:
在链表中找7: