前言
📚作者简介:爱编程的小马,正在学习C/C++,Linux及MySQL。
📚本文收录于初阶数据结构系列,本专栏主要是针对时间、空间复杂度,顺序表和链表、栈和队列、二叉树以及各类排序算法,持续更新!
📚相关专栏C++及Linux正在发展,敬请期待!
目录
[1. 链表](#1. 链表)
[1.1 链表的定义](#1.1 链表的定义)
[1.2 链表与顺序表相比的好处](#1.2 链表与顺序表相比的好处)
[1.3 链表的结构表示](#1.3 链表的结构表示)
[1.3.1 链表的结构形式](#1.3.1 链表的结构形式)
[1.3.2 链表的结构性质](#1.3.2 链表的结构性质)
[1.4 单链表的实现](#1.4 单链表的实现)
[1.4.1 单链表的创建](#1.4.1 单链表的创建)
[1.4.2 单链表的打印](#1.4.2 单链表的打印)
[1.4.3 单链表的动态内存申请](#1.4.3 单链表的动态内存申请)
[1.4.4 单链表的头插](#1.4.4 单链表的头插)
[1.4.5 单链表的尾插](#1.4.5 单链表的尾插)
[1.4.6 单链表的头删](#1.4.6 单链表的头删)
[1.4.7 单链表的尾删](#1.4.7 单链表的尾删)
[1.4.8 单链表的查找](#1.4.8 单链表的查找)
[1.4.9 单链表的任意位置插入的前插](#1.4.9 单链表的任意位置插入的前插)
[1.4.10 单链表任意位置的删除](#1.4.10 单链表任意位置的删除)
[2.1 test.c测试函数代码](#2.1 test.c测试函数代码)
[2.2 SList.h函数声明代码](#2.2 SList.h函数声明代码)
[2.3 SList.c函数实现代码](#2.3 SList.c函数实现代码)
1. 链表
1.1 链表的定义
概念:链表是一种物理存储结构上非连续、非顺序的存储结构,数据元素的逻辑顺序是通过链表中的指针链接次序所决定的
本文只介绍最简单的链表结构:单链表
1.2 链表与顺序表相比的好处
1、顺序表从中间插入/头部插入,时间复杂度是O(N),因为要一次往后挪动,但是单链表是O(1),大大节省了程序运行时间。
2、 顺序表每次需要增容,到后期增容很大的时候,需要拷贝数据、开辟新空间、释放旧空间。会有不小的损耗。链表直接开辟一个结构体大小的空间即可。
3、增容一般是两倍,但是我就想多插入几个仅此而已,会造成空间的大规模浪费。链表同样更加简单且占用空间小。
1.3 链表的结构表示
首先要给大家介绍一下,就是链表中的结点是一个结构体,结点中一个变量是存储数据的,另一个变量是存储结构体地址的,上一个结点存下一个结点的地址,从而链接起来。
1.3.1 链表的结构形式
1.3.2 链表的结构性质
1、从上图可以看出,链表在逻辑上是连续的,在物理地址上是不连续的
2、现实的结点是动态内存在堆区申请出来的
3、堆上申请的空间,是按照一定的规律来的,有些可能相同,有些可能不同。
1.4 单链表的实现
1.4.1 单链表的创建
上文我们提到了,结点是一个结构体,第一个结构体变量是存储数据的,第二个是存储下一个链表的地址的
cs
typedef int SListDataType;
typedef struct SListNode
{
SListDataType data;
struct SListNode* next;
}SLTNode;
1.4.2 单链表的打印
cs
void PrintSList(SLTNode* phead)
{
SLTNode* cur = phead;
while (cur != NULL)
{
printf("%d->", cur->data);
cur = cur->next;
}
printf("NULL");
}
为什么是这样子打印的?给大家说一下思想:
1、单链表定义了最后一个链表的地址为空指针,所以我们就定义了一个cur来遍历整个链表
2、每找到一个数据我们就打印,然后遍历链表指针cur就往后走一步, 怎么走?是不是next中存放了下一个结点的地址,那么把cur管理的结构体中next的地址赋值给cur是不是相当于向后走了一步。
1.4.3 单链表的动态内存申请
cs
SLTNode* BuySLTNode(SListDataType x)
{
SLTNode* newnode = (SLTNode*)malloc(sizeof(SLTNode));
if (newnode == NULL)
{
perror("malloc fail");
return NULL;
}
newnode->data = x;
newnode->next = NULL;
return newnode;
}
比方说,我想申请一块动态内存空间,里面存储x的值,那么这个时候,就通过malloc在堆上申请一块空间,交给newnode管理,这时候把newnode中data的值赋值为x,newnode中next的值赋值为NULL后返回这块空间的地址。这是不是就很好的开辟了一个结点。如果开辟失败了就返回空指针。
1.4.4 单链表的头插
cs
void SListPushFront(SLTNode** pphead, SListDataType x)
{
SLTNode* newnode = BuySLTNode(x);
assert(newnode);
newnode->next = *pphead;
*pphead = newnode;
}
这里我们用newnode来管理开辟的新结点,如果开辟了失败了就不用往下了,开辟成功了往下走,我画个图来帮助大家理解上面代码的意思
1.4.5 单链表的尾插
我先给大家介绍一下,尾插有两种情况,第一种空链表,第二种,非空链表
先分析第一种情况,如果是空链表,是不是直接把newnode的地址给pphead是不是就可以了,
第二种情况,我给大家画个图一起来分析
cs
void SListPushBack(SLTNode** pphead, SListDataType x)
{
SLTNode* newnode = BuySLTNode(x);
//空链表
if (*pphead == NULL)
{
*pphead = newnode;
return;
}
//非空链表
SLTNode* tail = *pphead;
while (tail->next != NULL)
{
tail = tail->next;
}
tail->next = newnode;
}
第二种情况,首先我们要尾插,是不是要先找尾部在哪里?尾部在哪里?是不是说tail->next是空指针,这个就是链表中最后一个元素了,找到了之后,就把tail->next存newnode的地址就可以。
1.4.6 单链表的头删
给大家说一下啊,如何删除单链表中的数?是不是只需要让上个结点存下下个结点的地址就好了。
那么头删就是让*pphead指向下下个结点,然后释放第一个结点就好了。
那么,应该这么做,看代码
cs
void SListPopFront(SLTNode** pphead)
{
assert(*pphead);
//链表只有一个值
SLTNode* cur = *pphead;
*pphead = cur->next;
free(cur);
cur = NULL;
}
画个图给大家理解一下:
1.4.7 单链表的尾删
尾删就更简单了,还是第一步,找尾,第二部,释放空间即可。
cs
void SListPopBack(SLTNode** pphead)
{
assert(*pphead);
if ((*pphead)->next == NULL)
{
free(*pphead);
*pphead = NULL;
}
else
{
SLTNode* cur = *pphead;
while (cur->next->next != NULL)
{
cur = cur->next;
}
free(cur->next);
cur->next = NULL;
}
}
1.4.8 单链表的查找
在单链表中查找一个数,找到了就返回这个结点的地址,没找到就返回空指针。
cs
SLTNode* SListFind(SLTNode* pphead, SListDataType x)
{
assert(pphead);
SLTNode* cur = pphead;
while (cur)
{
if (cur->data == x)
return cur;
else
cur = cur->next;
}
return NULL;
}
1.4.9 单链表的任意位置插入的前插
cs
void SListInsertbefore(SLTNode** pphead, SLTNode* pos, SListDataType x)
{
if (*pphead == NULL)
{
SListPushFront(pphead, x);
}
//在pos前插入
else
{
SLTNode* newnode = BuySLTNode(x);
SLTNode* cur = *pphead;
while (cur->next != pos)
{
cur = cur->next;
}
cur->next = newnode;
newnode->next = pos;
}
}
首先啊,如果pphead没有值,那么就相当于头插,如果链表中有值,画个图给大家理解
1.4.10 单链表任意位置的删除
cs
void SListErase(SLTNode** pphead, SLTNode* pos)
{
assert(*pphead);
if ((*pphead)->next == NULL)
{
free(*pphead);
*pphead = NULL;
}
else
{
SLTNode* cur = *pphead;
while (cur->next != pos)
{
cur = cur->next;
}
cur->next = pos->next;
free(pos);
pos = NULL;
}
}
2.单链表的完整代码
2.1 test.c测试函数代码
cs
#include "SList.h"
SLTNode* Phead = NULL;
void Test1()
{
//该函数测试头插和尾插
SListPushFront(&Phead, 1);
SListPushFront(&Phead, 2);
SListPushFront(&Phead, 3);
SListPushFront(&Phead, 4);
SListPushFront(&Phead, 5);
SListPushBack(&Phead, 2);
SListPushBack(&Phead, 3);
SListPushBack(&Phead, 4);
PrintSList(Phead);
}
void Test2()
{
//该函数测试头删和尾删
SListPushFront(&Phead, 1);
SListPopBack(&Phead);
PrintSList(Phead);
}
void Test3()
{
//查找
SListPushFront(&Phead, 1);
SListPushFront(&Phead, 2);
SListPushFront(&Phead, 3);
SListPushFront(&Phead, 4);
SLTNode* find = SListFind(Phead, 3);
if (find)
find->data = 30;
PrintSList(Phead);
}
void Test4()
{
//任意位置插入(前插)
SListPushFront(&Phead, 1);
SListPushFront(&Phead, 2);
SListPushFront(&Phead, 3);
SListPushFront(&Phead, 4);
SLTNode* find1 = SListFind(Phead, 3);
SLTNode* find2 = SListFind(Phead, 4);
if (find1)
{
SListInsertbefore(&Phead, find1, 40);
SListEraseafter(find2);
SListEraseafter(find1);
}
PrintSList(Phead);
}
int main()
{
Test1();
//Test2();
//Test3();
//Test4();
return 0;
}
2.2 SList.h函数声明代码
cs
#pragma once
#include<stdio.h>
#include<stdlib.h>
#include<assert.h>
typedef int SListDataType;
typedef struct SListNode
{
SListDataType data;
struct SListNode* next;
}SLTNode;
void PrintSList(SLTNode* phead);
//头插
void SListPushFront(SLTNode** pphead, SListDataType x);
//尾插
void SListPushBack(SLTNode** pphead, SListDataType x);
//头删
void SListPopFront(SLTNode** pphead);
//尾删
void SListPopBack(SLTNode** pphead);
//查找
SLTNode* SListFind(SLTNode* pphead, SListDataType x);
//在pos之后插入x
void SListInsertbefore(SLTNode** pphead,SLTNode* pos, SListDataType x);
//在pos之后插入x
void SListInsertafter(SLTNode* pos, SListDataType x);
//删除pos位置上的值
void SListErase(SLTNode** pphead, SLTNode* pos);
//删除pos之后的位置
void SListEraseafter(SLTNode* pos);
2.3 SList.c函数实现代码
cs
#include "SList.h"
void PrintSList(SLTNode* phead)
{
SLTNode* cur = phead;
while (cur != NULL)
{
printf("%d->", cur->data);
cur = cur->next;
}
printf("NULL");
}
SLTNode* BuySLTNode(SListDataType x)
{
SLTNode* newnode = (SLTNode*)malloc(sizeof(SLTNode));
if (newnode == NULL)
{
perror("malloc fail");
return NULL;
}
newnode->data = x;
newnode->next = NULL;
return newnode;
}
void SListPushFront(SLTNode** pphead, SListDataType x)
{
SLTNode* newnode = BuySLTNode(x);
assert(newnode);
newnode->next = *pphead;
*pphead = newnode;
}
void SListPushBack(SLTNode** pphead, SListDataType x)
{
SLTNode* newnode = BuySLTNode(x);
//空链表
if (*pphead == NULL)
{
*pphead = newnode;
return;
}
//非空链表
SLTNode* tail = *pphead;
while (tail->next != NULL)
{
tail = tail->next;
}
tail->next = newnode;
}
void SListPopFront(SLTNode** pphead)
{
assert(*pphead);
//链表只有一个值
SLTNode* cur = *pphead;
*pphead = cur->next;
free(cur);
cur = NULL;
}
void SListPopBack(SLTNode** pphead)
{
assert(*pphead);
if ((*pphead)->next == NULL)
{
free(*pphead);
*pphead = NULL;
}
else
{
SLTNode* cur = *pphead;
while (cur->next->next != NULL)
{
cur = cur->next;
}
free(cur->next);
cur->next = NULL;
}
}
SLTNode* SListFind(SLTNode* pphead, SListDataType x)
{
assert(pphead);
SLTNode* cur = pphead;
while (cur)
{
if (cur->data == x)
return cur;
else
cur = cur->next;
}
return NULL;
}
void SListInsertbefore(SLTNode** pphead, SLTNode* pos, SListDataType x)
{
if (*pphead == NULL)
{
SListPushFront(pphead, x);
}
//在pos前插入
else
{
SLTNode* newnode = BuySLTNode(x);
SLTNode* cur = *pphead;
while (cur->next != pos)
{
cur = cur->next;
}
cur->next = newnode;
newnode->next = pos;
}
}
void SListInsertafter(SLTNode* pos, SListDataType x)
{
assert(pos);
//只有一个
SLTNode* newnode = BuySLTNode(x);
newnode->next = pos->next;
pos->next = newnode;
}
void SListErase(SLTNode** pphead, SLTNode* pos)
{
assert(*pphead);
if ((*pphead)->next == NULL)
{
free(*pphead);
*pphead = NULL;
}
else
{
SLTNode* cur = *pphead;
while (cur->next != pos)
{
cur = cur->next;
}
cur->next = pos->next;
free(pos);
pos = NULL;
}
}
void SListEraseafter(SLTNode* pos)
{
assert(pos);
assert(pos->next);
SLTNode* cur = pos->next;
pos->next = cur->next;
free(cur);
cur = NULL;
}
总结
1、单链表其实不难,大家一定要搞清楚指针和结构体
2、一定要动手实践一下
3、其实数据结构就是围绕着数据的增删查改显示这几个点,所以一定要搞清楚每一个代码实现的逻辑。
如果这份博客对大家有帮助,希望各位给小马一个大大的点赞鼓励一下,如果喜欢,请收藏一下,谢谢大家!!!
制作不易,如果大家有什么疑问或给小马的意见,欢迎评论区留言。