文章目录
前言
上文我们介绍了如何创建一个顺序表,但是它有许多的缺陷比如:
1.空间不足时,需要扩容容易造成空间的浪费,尤其是异地扩容。
2.当我们在头部或者中部插入时候要不听的便利顺序表,非常的繁琐。
今天本文将介绍另一种储存方式***单链表。***
一、单链表
1.1 单链表介绍
单链表就是定义一个结构体里面存放的是一个有效数据与改结构体自身的便利
直接上图:
我们希望可以创建出像这样一样的储存方式
每个地址都是系统随机选取的,但是他们又可以像链子一样通过地址连接起来,这就叫做单链表
1.2 单链表最小结构体
typedef SLTdataType;
typedef struct SLTlistNode
{
//我们想要存入链表的数据
int data;
//指向下一个数据地址的指针空间
struct SLTlistNode* next;
}SLTnode;
这里不会的小伙伴可以去看看我之前的文章,结构体及自定义类型,单链表对我们结构体与指针方面的知识要求非常的严格。
结构体中两个元素,一个是我们想要存入的数据,另一个是链表中下一个结构体的地址。
本文后面结构体变量缩写为SLTnode。
1.3 开辟空间函数(malloc)
既然是链表,肯定有保存的属性,我们只能将空间开辟到堆上或者定义一些全局变量、静态变量,显然后者大不可为。所以我们只能将他开辟在堆上,就是我们所熟知的就是malloc函数。
malloc如何使用我之前的文章 动态内存开辟 中有详细的解释。
buySLTnode(int n)
{
//开辟结构体大小的空间
SLTnode* newnode = (SLTnode*)malloc(sizeof(SLTnode));
//判断是否开辟成功
if (newnode == NULL)
{
perror("malloc newnode");
exit(-1);
}
//赋初值初值是上面int n中传进来的数,由我们自己输入
newnode->data = n;
newnode->next = NULL;
return newnode;
}
1.4 打印链表
为了方便打印,这里选择创建一个打印函数
//传入链表首元素地址
void Print(SLTnode* phead)
{
//接收
SLTnode* cur = phead;
while (cur->next!=NULL)
{
printf("%d->", cur->data);
cur = cur->next;
}
//每个节点拥有两个空间,链表的最后一个节点所连接的空间设为NULL
printf("NULL\n");
}
1.5 开辟空间以及连接链表(重点)
动态开辟空间并连接他们
有实在看不明白的句,私信,在线即回哦!!!!!!!
Createslist(int n)
{
//设置双指针 * phead 与* ptail
SLTnode* phead = NULL;
SLTnode* ptail = NULL;
int i = 0;
for (i = 0; i < n; i++)
{
//传入n个空间以开辟的空间数量。
SLTnode* newnode = buySLTnode(i);
if (phead == NULL)
{
//给予phead指针开辟的初始位置,到时候开辟连接完成后做返回用,因为当出开辟函数后空间会销毁所以必须得存在一个回传的地址。
phead = newnode;
//ptail指针在这里做连接作用。
ptail = newnode;
}
else
{
//讲新建空间的地址给与ptail指针的接受next用以接受地址,一直便利
ptail->next = newnode;
ptail = newnode;
}
. }
//传回开辟空间的地址
return phead;
}
1.6 单链表的尾插
这里与我们上文顺序表中插入有所差异,因为要改变结构体的长度与数值,使用了二级指针。
void pushback(SLTnode_** pplist, SLTdataType n)
{
//开辟要尾插数据的空间(内存里的随机值,不固定,无规律)
SLTnode* newnode = buySLTnode(n);
//检测传入的链表是否为空,为空则直接赋值
if (*pplist == NULL)
{
*pplist = newnode;
}
else
{
SLTnode* phead = *pplist;
//向链表末尾便利,寻找最后的NULL地址,准备插入
while (phead->next)
{
phead = phead->next;
}
phead->next = newnode;
}
}
1.7 单链表的尾删
void popback(SLTnode** pplist)
{
assert(pplist);
SLTnode* phead =*pplist;
SLTnode* ptail = NULL;
while (phead->next)
{
ptail = phead;
phead = phead->next;
}
ptail->next = NULL;
free(phead);
}
1.8 单链表的头插
void pushfront(SLTnode** pplist, SLTdataType n)
{
SLTnode* phead = pplist;
//创建头插的空间
SLTnode* newnode = buySLTnode(n);
//将原有链表初始位置给与新建链表的next
newnode->next =phead->data;
//更改单链表的初始地址
*pplist = newnode;
}
1.9 单链表的头删
在头删与尾删内都要进行链表是否为空的判断很容易出错。
void popfront(SLTnode** pplist)
{
//检验链表是否为空
assert(pplist);
//获取单链表中第二个数据的地址,用以改变链表首地址
SLTnode* phead = (*pplist)->next;
//释放原来链表的首地址空间
free(*pplist);
*pplist = phead;
}
二、简单单链表全程序
/主函数
#include"zzzzzz.h"
void test()
{
SLTnode* plist = Createslist(5);
Print(plist);
popback(&plist);
Print(plist);
pushback(&plist, 10);
Print(plist);
pushfront(&plist, 8);
pushfront(&plist, 7);
pushfront(&plist, 6);
Print(plist);
popfront(&plist);
Print(plist);
}
int main()
{
test();
return 0;
}
//test.c
buySLTnode(int n)
{
//开辟结构体大小的空间
SLTnode* newnode = (SLTnode*)malloc(sizeof(SLTnode));
//判断是否开辟成功
if (newnode == NULL)
{
perror("malloc newnode");
exit(-1);
}
//赋初值初值是上面int n中传进来的数,由我们自己输入
newnode->data = n;
newnode->next = NULL;
return newnode;
}
//传入链表首元素地址
void Print(SLTnode* phead)
{
//接收
SLTnode* cur = phead;
while (cur->next!=NULL)
{
printf("%d->", cur->data);
cur = cur->next;
}
//每个节点拥有两个空间,链表的最后一个节点所连接的空间设为NULL
printf("NULL\n");
}
Createslist(int n)
{
//设置双指针 * phead 与* ptail
SLTnode* phead = NULL;
SLTnode* ptail = NULL;
int i = 0;
for (i = 0; i < n; i++)
{
//传入n个空间以开辟的空间数量。
SLTnode* newnode = buySLTnode(i);
if (phead == NULL)
{
//给予phead指针开辟的初始位置,到时候开辟连接完成后做返回用,因为当出开辟函数后空间会销毁所以必须得存在一个回传的地址。
phead = newnode;
//ptail指针在这里做连接作用。
ptail = newnode;
}
else
{
//讲新建空间的地址给与ptail指针的接受next用以接受地址,一直便利
ptail->next = newnode;
ptail = newnode;
}
. }
//传回开辟空间的地址
return phead;
}
void pushback(SLTnode_** pplist, SLTdataType n)
{
//开辟要尾插数据的空间(内存里的随机值,不固定,无规律)
SLTnode* newnode = buySLTnode(n);
//检测传入的链表是否为空,为空则直接赋值
if (*pplist == NULL)
{
*pplist = newnode;
}
else
{
SLTnode* phead = *pplist;
//向链表末尾便利,寻找最后的NULL地址,准备插入
while (phead->next)
{
phead = phead->next;
}
phead->next = newnode;
}
}
void popback(SLTnode** pplist)
{
assert(pplist);
SLTnode* phead =*pplist;
SLTnode* ptail = NULL;
while (phead->next)
{
ptail = phead;
phead = phead->next;
}
ptail->next = NULL;
free(phead);
}
void pushfront(SLTnode** pplist, SLTdataType n)
{
SLTnode* phead = pplist;
//创建头插的空间
SLTnode* newnode = buySLTnode(n);
//将原有链表初始位置给与新建链表的next
newnode->next =phead->data;
//更改单链表的初始地址
*pplist = newnode;
}
void popfront(SLTnode** pplist)
{
//检验链表是否为空
assert(pplist);
//获取单链表中第二个数据的地址,用以改变链表首地址
SLTnode* phead = (*pplist)->next;
//释放原来链表的首地址空间
free(*pplist);
*pplist = phead;
}
//声明区
#define _CRT_SECURE_NO_WARNINGS 1
#include<string.h>
#include<stdio.h>
#include<assert.h>
#include <stdlib.h>
typedef SLTdataType;
typedef struct SLTlistNode
{
int data;
struct SLTlistNode* next;
}SLTnode;
//开辟空间函数
buySLTnode(int n);
//开辟指定空间函数
Createslist(int x);
//打印链表
void Print(SLTnode* phead);
//尾插
void pushback(SLTnode* plist,SLTdataType n);
//尾删
void popback(SLTnode** pplist);
//头插
void pushfront(SLTnode** pplist, SLTdataType n);
//头删
void popfront(SLTnode** pplist);
三、实机演示结果
后续回补齐单链表的查找与更改函数
点赞收藏不迷路,关注必回,求求大佬给个关注吧,一起加油!!!!冲