前言:
在之前我们实现了顺序表及其通讯录的实现,在实现的过程中我们可以发现空间会有一些浪费,比如说:中间,头部的插入或者删除,我们需要移动数据,时间复杂度为O(N),当空间不够时,我们需要使用realloc动态开辟内存,可能需要申请新的空间,拷贝数据,释放旧的空间,增容的新空间也会有一定的浪费。这些问题我们可以通过链表去解决!接下来就让我们来了解一下链表及其链表的实现。
主要常用的链表分为不带头单向不循环(单链表)和带头双向循环(双链表)
这里我们只介绍单链表
1.链表的概念:
链表一种线性的数据结构,通过指针将一个个零散的内存块连接起来,链表的每个内存块称为结点。在逻辑结构上是线性的,在物理结构不一定是线性的。链表是由一个一个的节点组成,我们可以画图来理解一下链表!
我们可以发现每一个节点都由数据和指向下一个节点的指针两部分组成 ,这个时候我们就可以定义一个结构体来存放这两个数据!
2.实现链表所需文件
这里和我们实现顺序表一样需要三个文件
头文件:SListnode.h 声明链表结构 链表对应的接口声明
源文件:SListnode.c 实现函数 test.c 测试函数
3.链表结构的定义
链表的结构就是上述所说的数据+指向下一个节点的指针,这里就画图解释了。
3.单链表的尾插和头插
3.1尾插
尾插就是在原链表的后面插入一个新的值,假设我们想要插入4,那我们需要建立一个newnode来存放我们的4,那就要申请一个节点的空间,这个节点存储的值就是4,那如何将他们连接起来呢?我们可以来画图分析一下!
当我们不管是进行尾插还是头插或者指定插入,我们都要向内存申请一块空间,这个时候我们就可以封装一个申请空间的函数,当想要使用申请空间的时候直接调用函数即可!
接下来我们来实现一下尾插
尾插代码:
slistnode* SLTBuynode(SListnodeType x)
{
slistnode* newnode = (slistnode*)malloc(sizeof(slistnode));
if (newnode == NULL)
{
perror("malloc");
exit(1);
}
newnode->data = x;
newnode->next = NULL;
return newnode;
}
//尾插
void SLTPushBack(slistnode** pphead, SListnodeType x)
{
assert(pphead);
slistnode* newnode = SLTBuynode(x);
//判空
if (*pphead == NULL)
{
*pphead = newnode;
}
else
{
slistnode* ptail = *pphead;
while (ptail->next)
{
ptail = ptail->next;
}
//ptail指向尾节点
ptail->next = newnode;
}
}
3.2头插
头插就是将newnode放在最前面,也要把*pphead放在newnode的位置!
代码实现
//头插
void SLTPushFront(slistnode** pphead, SListnodeType x)
{
assert(pphead);
slistnode* newnode = SLTBuynode(x);
//判空
if (*pphead == NULL)
{
*pphead = newnode;
}
else
{
//不为空
newnode->next = *pphead;
*pphead = newnode;
}
}
4.单链表的打印
打印非常简单,我们只需要把链表遍历一遍,链表为结束,其余直接打印即可!
//链表的打印
void SLTprint(slistnode* phead)
{
slistnode* pcur = phead;
while (pcur)
{
printf("%d->", pcur->data);
pcur = pcur->next;
}
printf("NULL\n");
}
5.单链表的尾删和头删
5.1尾删
尾删首先我们要找到尾节点,因为我们要释放掉尾节点,也要把尾节点的前驱节点的指向下一节点的地址设为NULL,不然就成为野指针了!
实现代码:
//尾删
void SLTPopBack(slistnode** pphead)
{
assert(pphead);
//判断是否只有一个有效节点
if ((*pphead) == NULL)
{
free(*pphead);
*pphead = NULL;
}
else
{
slistnode* ptail = *pphead;
slistnode* prev = *pphead;
//找尾
while (ptail->next)
{
prev = ptail;
ptail = ptail->next;
}
//找到尾节点
free(ptail);
ptail == NULL;
prev->next = NULL;
}
}
5.2头删
要先将第一个有效节点的下一个节点的地址存储起来,不然会找不到第二个有效的节点,也要注意断言确保有第一个有效节点!
实现头删
//头删
void SLTPopFront(slistnode** pphead)
{
assert(pphead && *pphead);
slistnode* next = (*pphead)->next;
free(*pphead);
*pphead = next;
}
6.单链表的查找
链表的查找就是遍历一遍链表,找到返回,没有找到返回空!
代码实现:
//查找
slistnode* SLTFind(slistnode* phead, SListnodeType x)
{
slistnode* pcur = phead;
while (pcur)
{
if (pcur->data == x)
{
return pcur;
}
pcur = pcur->next;
}
//没有找到
return NULL;
}
7.单链表在指定位置之前和指定位置之后插入数据
7.1在指定位置之前位置插入数据
这里注意要注意传的地址不为NULL,链表也不可以为空以及pos也不能为空
代码实现:
//在指定位置之前插入
void SLTInsert(slistnode** pphead, slistnode* pos, SListnodeType x)
{
assert(pphead && *pphead);
assert(pos);
slistnode* newnode = SLTBuynode(x);
//如果pos=第一个有效节点 使用头插即可
if (pos == *pphead)
{
SLTPopFront(pphead, x);
}
else
{
//找pos的前驱节点
slistnode* pcur = *pphead;
while (pcur->next != pos)
{
pcur = pcur->next;
}
//找到了
newnode->next = pos;
pcur->next = newnode;
}
}
7.2在指定位置之后插入数据
这里注意也要进行断言!
代码实现:
//在指定位置之后插入
void SLTInsertafter(slistnode** pphead, slistnode* pos, SListnodeType x)
{
assert(pphead && *pphead);
assert(pos);
slistnode* newnode = SLTBuynode(x);
slistnode* next = pos->next;
pos->next = newnode;
newnode->next = next;
}
8.单链表在指定位置删除
思路:首先断言进行判断,保证pphead,*pphead,pos的有效性,接下来要找pos的前驱节点,也要存储pos的下一个节点,然后将pos的前驱节点和pos的后一个节点连接起来,然后释放pos!
接下来给大家画图分析:
代码实现:
//在指定位置删除
void SLTErase(slistnode** pphead, slistnode* pos)
{
assert(pphead && *pphead);
assert(pos);
if (pos == *pphead)
{
SLTPopFront(pphead);
}
else
{
slistnode* perv = *pphead;
slistnode* next = pos->next;
while (perv->next != pos)
{
perv = perv->next;
}
//找到了
perv->next = next;
free(pos);
pos = NULL;
}
}
9.单链表删除pos之后的节点
10.单链表的销毁
当我们销毁链表,就是将一个一个的节点销毁掉!最后不要忘记要把*pphead设置为NULL
//链表的销毁
void SListDestroy(slistnode** pphead)
{
assert(pphead && *pphead);
slistnode* pcur = *pphead;
while (pcur)
{
slistnode* next = pcur->next;
free(pcur);
pcur = next;
}
//销毁完成
*pphead = NULL;
}
让我们来调试观察一下
11.整体码源
SListnode.h
#include<stdio.h>
#include<stdlib.h>
#include<assert.h>
//链表结构定义
typedef int SListnodeType;
typedef struct SListnode
{
SListnodeType data;
struct SListnode* next;
}slistnode;
//打印
void SLTprint(slistnode* phead);
//尾插
void SLTPushBack(slistnode** pphead, SListnodeType x);
//头插
void SLTPushFront(slistnode** pphead, SListnodeType x);
//尾删
void SLTPopBack(slistnode** pphead);
//头删
void SLTPopFront(slistnode** pphead);
//查找
slistnode* SLTFind(slistnode* phead, SListnodeType x);
//在指定位置之前插入
void SLTInsert(slistnode** pphead, slistnode* pos, SListnodeType x);
//在指定位置之后插入
void SLTInsertafter(slistnode** pphead, slistnode* pos, SListnodeType x);
//在指定位置删除
void SLTErase(slistnode** pphead, slistnode* pos);
//在指定位置之后删除
void SLTEraseafter(slistnode* pos);
//链表的销毁
void SListDestroy(slistnode** pphead);
SListnode.c
#include"SListnode.h"
//链表的打印
void SLTprint(slistnode* phead)
{
slistnode* pcur = phead;
while (pcur)
{
printf("%d->", pcur->data);
pcur = pcur->next;
}
printf("NULL\n");
}
slistnode* SLTBuynode(SListnodeType x)
{
slistnode* newnode = (slistnode*)malloc(sizeof(slistnode));
if (newnode == NULL)
{
perror("malloc");
exit(1);
}
newnode->data = x;
newnode->next = NULL;
return newnode;
}
//尾插
void SLTPushBack(slistnode** pphead, SListnodeType x)
{
assert(pphead);
slistnode* newnode = SLTBuynode(x);
//判空
if (*pphead == NULL)
{
*pphead = newnode;
}
else
{
slistnode* ptail = *pphead;
while (ptail->next)
{
ptail = ptail->next;
}
//ptail指向尾节点
ptail->next = newnode;
}
}
//头插
void SLTPushFront(slistnode** pphead, SListnodeType x)
{
assert(pphead);
slistnode* newnode = SLTBuynode(x);
//判空
if (*pphead == NULL)
{
*pphead = newnode;
}
else
{
//不为空
newnode->next = *pphead;
*pphead = newnode;
}
}
//尾删
void SLTPopBack(slistnode** pphead)
{
assert(pphead);
//判断是否只有一个有效节点
if ((*pphead) == NULL)
{
free(*pphead);
*pphead = NULL;
}
else
{
slistnode* ptail = *pphead;
slistnode* prev = *pphead;
//找尾
while (ptail->next)
{
prev = ptail;
ptail = ptail->next;
}
//找到尾节点
free(ptail);
ptail = NULL;
prev->next = NULL;
}
}
//头删
void SLTPopFront(slistnode** pphead)
{
assert(pphead && *pphead);
slistnode* next = (*pphead)->next;
free(*pphead);
*pphead = next;
}
//查找
slistnode* SLTFind(slistnode* phead, SListnodeType x)
{
slistnode* pcur = phead;
while (pcur)
{
if (pcur->data == x)
{
return pcur;
}
pcur = pcur->next;
}
//没有找到
return NULL;
}
//在指定位置之前插入
void SLTInsert(slistnode** pphead, slistnode* pos, SListnodeType x)
{
assert(pphead && *pphead);
assert(pos);
slistnode* newnode = SLTBuynode(x);
//如果pos=第一个有效节点 使用头插即可
if (pos == *pphead)
{
SLTPopFront(pphead, x);
}
else
{
//找pos的前驱节点
slistnode* pcur = *pphead;
while (pcur->next != pos)
{
pcur = pcur->next;
}
//找到了
newnode->next = pos;
pcur->next = newnode;
}
}
//在指定位置之后插入
void SLTInsertafter(slistnode** pphead, slistnode* pos, SListnodeType x)
{
assert(pphead && *pphead);
assert(pos);
slistnode* newnode = SLTBuynode(x);
slistnode* next = pos->next;
pos->next = newnode;
newnode->next = next;
}
//在指定位置删除
void SLTErase(slistnode** pphead, slistnode* pos)
{
assert(pphead && *pphead);
assert(pos);
if (pos == *pphead)
{
SLTPopFront(pphead);
}
else
{
slistnode* perv = *pphead;
slistnode* next = pos->next;
while (perv->next != pos)
{
perv = perv->next;
}
//找到了
perv->next = next;
free(pos);
pos = NULL;
}
}
//在指定位置之后删除
void SLTEraseafter(slistnode* pos)
{
assert(pos && pos->next);
slistnode* del = pos->next;
pos->next = pos->next->next;
free(del);
del = NULL;
}
//链表的销毁
void SListDestroy(slistnode** pphead)
{
assert(pphead && *pphead);
slistnode* pcur = *pphead;
while (pcur)
{
slistnode* next = pcur->next;
free(pcur);
pcur = next;
}
//销毁完成
*pphead = NULL;
}
test.c
#include"SListnode.h"
void slistnodetest()
{
slistnode* plist = NULL;
SLTPushBack(&plist, 1);
SLTPushBack(&plist, 2);
SLTPushBack(&plist, 3);
SLTPushBack(&plist, 4);
SListDestroy(&plist);
//SLTprint(plist);
/*SLTPushFront(&plist, 1);
SLTPushFront(&plist, 2);*/
//SLTPopBack(&plist);
//SLTPopFront(&plist);
//SLTprint(plist);
//slistnode* find = SLTFind(plist, 2);
//if (find == NULL)
//{
// printf("没有找到!\n");
//}
//else
// printf("找到了!\n");
//SLTInsert(&plist, find, 9);
//SLTInsertafter(&plist, find, 5);
//SLTErase(&plist, find);
//SLTEraseafter(find);
//SLTprint(plist);
}
int main()
{
slistnodetest();
return 0;
}
11.结语
这篇代码主要写了单链表的实现,接下来会实现单链表的通讯录项目,也会去实现双链表,都是有整体码源的,希望可以帮助到大家,大佬互三!!!