【C语言】数据结构——无头单链表实例探究

💗个人主页💗
⭐个人专栏——数据结构学习⭐
💫点击关注🤩一起学习C语言💯💫

目录

  • 导读:
  • 1. 单链表
    • 1.1 什么是单链表
    • 1.2 优缺点
  • 2. 实现单链表基本功能
    • 2.1 定义结构体
    • 2.2 单链表打印
    • 2.3 销毁单链表
    • 2.4 动态申请一个结点
    • 2.5 单链表尾插
    • 2.6 单链表尾删
    • 2.7 单链表头插
    • 2.8 单链表头删
    • 2.9 单链表查找
    • 2.10 单链表任意插入
    • 2.11 单链表任意删除
  • 3. 代码整理
    • 3.1 SList.h声明函数
    • 3.2 SList.c定义函数
    • 3.3 study.c调用
  • 4. 博主有话说

导读:

在前面我们已经学习了顺序表,今天我们来学习链表的单链表,也是无头的单链表,这需要对一级指针和二级指针有充分的了解。

1. 单链表

1.1 什么是单链表

单链表是一种常见的数据结构,由一系列节点依次连接形成。
每个节点包含两部分信息:数据信息指向下一个节点的指针
单链表的第一个节点称为头节点,最后一个节点没有下一个节点,其指针指向空。
类似于火车,火车头连接后一个车厢,再由后面的车厢依次连接
在这里插入图片描述

图1.1

1.2 优缺点

单链表的优点:

  1. 动态性:单链表的长度可以动态增长,不需要预先指定长度;
  2. 内存利用率高:链表中每个节点只需要存储下一个节点的地址,不需要像数组那样存储固定大小的位置,因此可以更加灵活地利用内存;
  3. 插入和删除操作方便:由于只需要改变链表节点中的指针,可以很方便地在链表中插入和删除节点。

单链表的缺点:

  1. 随机访问困难:由于必须从头节点开始遍历整个链表才能访问任意位置上的节点,因此随机访问效率较低;
  2. 存储空间浪费:由于链表节点中需要保存指向下一个节点的指针,因此需要额外的存储空间;
  3. 不支持反向遍历:由于链表节点只存储了指向下一个节点的指针,因此无法反向遍历链表。

2. 实现单链表基本功能

我们需要创建两个 C文件: study.c 和 SList.c,以及一个 头文件: SList.h。
头文件来声明函数,一个C文件来定义函数,另外一个C文件来用于主函数main()进行测试。

2.1 定义结构体

typedef是类型定义的意思。typedef struct 是为了使用这个结构体方便。

若struct SeqList {}这样来定义结构体的话。在申请SeqList 的变量时,需要这样写,struct SList n;
若用typedef,可以这样写,typedef struct SList{}SL; 。在申请变量时就可以这样写,SL n;
区别就在于使用时,是否可以省去struct这个关键字。

SList.h声明函数

//给int类型起一个别名——SLNDataType
typedef int SLNDataType;
typedef struct SListNode
{SLNDataType val;struct SListNode* next;
}SLNode;

2.2 单链表打印

SeqList.h声明函数

// 单链表打印
void SLTPrint(SLNode* phead);

SList.c定义函数

//打印结构体
void SLTPrint(SLNode* phead)
{SLNode* cur = phead;//指向头节点while (cur != NULL){printf("%d-> ", cur->val);cur = cur->next;}printf("NULL\n");
}

2.3 销毁单链表

动态开辟的空间用完之后都需要释放,以防后面出现问题。
SList.h声明函数

//单链表销毁
void SLTDestroy(SLNode** pphead);

SList.c定义函数

//单链表销毁
void SLTDestroy(SLNode** pphead)
{assert(pphead);SLNode* cur = *pphead;SLNode* prev = NULL;while (cur != NULL){prev = cur->next;free(cur);cur = prev;}*pphead = NULL;
}

2.4 动态申请一个结点

无论在链表头部、尾部还是任意位置插入一个节点,都需要开辟一个节点,每个插入函数里都要写开辟节点的函数会重复,为了方便,我们单独定义一个函数用来开辟新节点,每次只需调用即可。
SList.c定义函数

SLNode* CreateNode(SLNDataType x)
{//让指针newnode指向malloc开辟的新空间SLNode* newnode = (SLNode*)malloc(sizeof(SLNode));if (newnode == NULL)//开辟失败则返回错误信息{perror("malloc fail");exit(-1);}//对结构体成员解引用,改变其值newnode->val = x;//让next指向空newnode->next = NULL;return newnode;
}

2.5 单链表尾插

思路:
新建一个节点,让链表最后一个节点的next指向新节点
在这里插入图片描述

SList.h声明函数

// 单链表尾插
void SLTPushBack(SLNode** pphead, SLNDataType x);

SList.c定义函数
如果这个链表中没有任何节点,只需让头部指针plist直接指向newnode。
需要注意的一点是,plist是一级指针,我们想改变plist,就要用二级指针来接收plist的地址,这样才能改变plist的指向

// 单链表尾插
void SLTPushBack(SLNode** pphead, SLNDataType x)
{assert(pphead);SLNode* newnode = CreateNode(x);//如果开头为空,则直接指向CreateNode()函数开辟的空间,完成尾插if (*pphead == NULL){*pphead = newnode;//改变外部结构体指针Node*,要用Node**}else{//找尾SLNode* tail = *pphead;//如果结构体成员next指向的不是空指针while (tail->next != NULL){//让tail指向下一个节点tail = tail->next;}//再让tail节点的next指向新开辟的空间,完成尾插tail->next = newnode;}
}

在这里插入图片描述

study.c调用

//测试尾插和尾删
void TestSLT1()
{SLNode* plist = NULL;SLTPushBack(&plist, 1);SLTPushBack(&plist, 2);SLTPushBack(&plist, 3);SLTPushBack(&plist, 4);SLTPrint(plist);SLTDestroy(&plist);
}int main()
{TestSLT1();return 0;
}

在这里插入图片描述

2.6 单链表尾删

找到倒数第二个节点,让其next指向NULL,用free()释放那个最后一个节点。
如果只有一个节点,直接释放头节点即可。
在这里插入图片描述

SList.h声明函数

// 单链表尾删
void SLTPopBack(SLNode** pphead);

SList.c定义函数

// 单链表尾删
void SLTPopBack(SLNode** pphead)
{assert(pphead);assert(*pphead);//只有一个节点时if ((*pphead)->next == NULL){//直接释放free(*pphead);*pphead = NULL;}//多个节点else{//tail指向开头SLNode* tail = *pphead;//再定义一个空指针SLNode* prev = NULL;//next成员指向的下一节点不为空while (tail->next != NULL){//让prev指向tail所指向的空间prev = tail;//tail指向下一个节点tail = tail->next;}//循环结束,tail指向的为空,释放空间free(tail);//再让prev指向的结构体内的next成员指向NULL,完成尾删prev->next = NULL;}
}

在这里插入图片描述

study.c调用

//测试尾插和尾删
void TestSLT1()
{SLNode* plist = NULL;SLTPushBack(&plist, 1);SLTPushBack(&plist, 2);SLTPushBack(&plist, 3);SLTPushBack(&plist, 4);SLTPrint(plist);SLTPopBack(&plist);SLTPrint(plist);SLTDestroy(&plist);SLTPrint(plist);
}
int main()
{TestSLT1();return 0;
}

在这里插入图片描述

2.7 单链表头插

让头节点plist指向新开辟的节点,再让新开辟节点的next指向之前的第一个节点。
在这里插入图片描述

SList.h声明函数

//单链表头插
void SLTPushFront(SLNode** pphead, SLNDataType x);

SList.c定义函数

// 单链表头插
void SLTPushFront(SLNode** pphead, SLNDataType x)
{assert(pphead);//让* newnode指向CreateNode()函数开辟的新空间SLNode* newnode = CreateNode(x);//让新开辟的节点内的next成员指向链表开头的节点newnode->next = *pphead;//再重新让之前的头节点指向新开辟的节点,完成头插*pphead = newnode;
}

在这里插入图片描述

study.c调用

//测试头插和头删
void TestSLT2()
{SLNode* plist = NULL;SLTPushFront(&plist, 10);SLTPushFront(&plist, 20);SLTPushFront(&plist, 30);SLTPushFront(&plist, 40);SLTPrint(plist);SLTDestroy(&plist);
}
int main()
{TestSLT2();return 0;
}

在这里插入图片描述

2.8 单链表头删

让plist指向第二个节点,释放第一个节点。
在这里插入图片描述

SList.h声明函数

// 单链表头删
void SListPopFront(SLNode** pphead);

SList.c定义函数

// 单链表头删
void SListPopFront(SLNode** pphead)
{assert(*pphead);//tail指向开头SLNode* tail = *pphead;//让头节点指针指向下一个节点*pphead = (*pphead)->next;//把第一个节点空间释放,完成头删free(tail);tail = NULL;
}

study.c调用

//测试头插和头删
void TestSLT2()
{SLNode* plist = NULL;SLTPushFront(&plist, 10);SLTPushFront(&plist, 20);SLTPushFront(&plist, 30);SLTPushFront(&plist, 40);SLTPrint(plist);SLTPopFront(&plist);SLTPrint(plist);SLTPopFront(&plist);SLTPrint(plist);SLTDestroy(&plist);
}
int main()
{TestSLT2();return 0;
}

在这里插入图片描述

2.9 单链表查找

想要查找链表里的val里是否存入有一个值,遍历链表,查看每个节点的val值,找到则返回该节点的地址,找不到返回-1,具体的作用我们到后面应用。
在这里插入图片描述

SList.h声明函数

// 单链表查找
SLNode* SListFind(SLNode* pphead, SLNDataType x);

SList.c定义函数

SLNode* SListFind(SLNode* phead, SLNDataType x)
{SLNode* cur = phead;while (cur){if (cur->val == x){return cur;}else{cur = cur->next;}}return NULL;
}

2.10 单链表任意插入

单链表的插入不止是头插和尾插,可以在任意位置插入。
比如我们在链表中一个数值前插入节点,就可以利用单链表查找来找到这个数,返回其节点的位置,然后在该位置插入节点。
在这里插入图片描述

如果pos位置刚好在第一个节点,就是头插,直接调用之前的头插函数即可

SList.h声明函数

void SLTInsert(SLNode** pphead, SLNode* pos, SLNDataType x);

SList.c定义函数

void SLTInsert(SLNode** pphead, SLNode* pos, SLNDataType x)
{assert(pphead);assert(pos);assert(*pphead);//单节点if (*pphead == pos){SLTPushFront(pphead, x);}//多节点else{SLNode* tail = *pphead;while (tail->next != pos){tail = tail->next;}SLNode* newnode = CreateNode(x);tail->next = newnode;newnode->next = pos;}
}

在这里插入图片描述

study.c调用

void TestSLT3()
{SLNode* plist = NULL;SLTPushBack(&plist, 10);SLTPushBack(&plist, 20);SLTPushBack(&plist, 30);SLTPushBack(&plist, 40);SLTPrint(plist);SLNode* pos = SListFind(plist, 30);if (pos != NULL){SLTInsert(&plist, pos, 3);SLTPrint(plist);}SLTDestroy(&plist);
}
int main()
{TestSLT3();return 0;
}

在这里插入图片描述

2.11 单链表任意删除

和任意插入差不多,如果pos位置在头部就是头删,直接调用即可。
SList.h声明函数

//单链表任意位置删除
void SLTErase(SLNode** pphead, SLNode* pos);

SList.c定义函数

//单链表任意位置删除
void SLTErase(SLNode** pphead, SLNode* pos)
{assert(pphead);assert(pos);assert(*pphead);SLNode* tail = *pphead;if (*pphead == pos){SLTPopFront(pphead);}else{while (tail->next != pos){tail = tail->next;}tail->next = pos->next;free(pos);pos = NULL;}
}

study.c调用

//单链表任意位置插入和删除
void TestSLT3()
{SLNode* plist = NULL;SLTPushBack(&plist, 10);SLTPushBack(&plist, 20);SLTPushBack(&plist, 30);SLTPushBack(&plist, 40);SLTPrint(plist);SLNode* pos = SListFind(plist, 30);if (pos != NULL){SLTErase(&plist, pos);}SLTPrint(plist);SLTDestroy(&plist);
}int main()
{TestSLT3();return 0;
}

在这里插入图片描述

3. 代码整理

3.1 SList.h声明函数

#pragma once
#include <assert.h>
#include <stdio.h>
#include <stdlib.h>// 动态申请一个结点
typedef int SLNDataType;
typedef struct SListNode
{SLNDataType val;struct SListNode* next;
}SLNode;// 单链表打印
void SLTPrint(SLNode* phead);
//单链表销毁
void SLTDestroy(SLNode** pphead);
// 单链表尾插
void SLTPushBack(SLNode** pphead, SLNDataType x);
//单链表头插
void SLTPushFront(SLNode** pphead, SLNDataType x);
// 单链表尾删
void SLTPopBack(SLNode** pphead);
// 单链表头删
void SLTPopFront(SLNode** pphead);
// 单链表查找
SLNode* SListFind(SLNode* pphead, SLNDataType x);// 单链表任意位置插入
void SLTInsert(SLNode** pphead, SLNode* pos, SLNDataType x);
//单链表任意位置删除
void SLTErase(SLNode** pphead, SLNode* pos);void SLTInsertAfter(SLNode* pos, SLNDataType x);
void SLTEraseAfter(SLNode* pos);

3.2 SList.c定义函数

#include "SList.h"//打印结构体
void SLTPrint(SLNode* phead)
{SLNode* cur = phead;//指向头节点while (cur != NULL){printf("%d-> ", cur->val);cur = cur->next;}printf("NULL\n");
}//单链表销毁
void SLTDestroy(SLNode** pphead)
{assert(pphead);SLNode* cur = *pphead;SLNode* prev = NULL;while (cur != NULL){prev = cur->next;free(cur);cur = prev;}*pphead = NULL;
}SLNode* CreateNode(SLNDataType x)
{//让指针newnode指向malloc开辟的新空间SLNode* newnode = (SLNode*)malloc(sizeof(SLNode));if (newnode == NULL)//开辟失败则返回错误信息{perror("malloc fail");exit(-1);}//对结构体成员解引用,改变其值newnode->val = x;//让next指向空newnode->next = NULL;return newnode;
}
// 单链表尾插
void SLTPushBack(SLNode** pphead, SLNDataType x)
{assert(pphead);SLNode* newnode = CreateNode(x);//如果开头为空,则直接指向CreateNode()函数开辟的空间,完成尾插if (*pphead == NULL){*pphead = newnode;//改变外部结构体指针Node*,要用Node**}else{//找尾SLNode* tail = *pphead;//如果结构体成员next指向的不是空指针while (tail->next != NULL){//让tail指向下一个节点tail = tail->next;}//再让tail节点的next指向新开辟的空间,完成尾插tail->next = newnode;}
}// 单链表尾删
void SLTPopBack(SLNode** pphead)
{assert(pphead);assert(*pphead);//只有一个节点时if ((*pphead)->next == NULL){//直接释放free(*pphead);*pphead = NULL;}//多个节点else{//tail指向开头SLNode* tail = *pphead;//再定义一个空指针SLNode* prev = NULL;//next成员指向的下一节点不为空while (tail->next != NULL){//让prev指向tail所指向的空间prev = tail;//tail指向下一个节点tail = tail->next;}//循环结束,tail指向的为空,释放空间free(tail);//再让prev指向的结构体内的next成员指向NULL,完成尾删prev->next = NULL;}}// 单链表头插
void SLTPushFront(SLNode** pphead, SLNDataType x)
{assert(pphead);//让* newnode指向CreateNode()函数开辟的新空间SLNode* newnode = CreateNode(x);//让新开辟的节点内的next成员指向链表开头的节点newnode->next = *pphead;//再重新让之前的头节点指向新开辟的节点,完成头插*pphead = newnode;
}// 单链表头删
void SLTPopFront(SLNode** pphead)
{assert(pphead);assert(*pphead);//tail指向开头SLNode* tail = *pphead;//让头节点指针指向下一个节点*pphead = (*pphead)->next;//把第一个节点空间释放,完成头删free(tail);tail = NULL;
}// 单链表查找
SLNode* SListFind(SLNode* phead, SLNDataType x)
{SLNode* cur = phead;while (cur){if (cur->val == x){return cur;}else{cur = cur->next;}}return NULL;
}// 单链表任意位置插入
void SLTInsert(SLNode** pphead, SLNode* pos, SLNDataType x)
{assert(pphead);assert(pos);assert(*pphead);//单节点if (*pphead == pos){SLTPushFront(pphead, x);}//多节点else{SLNode* tail = *pphead;while (tail->next != pos){tail = tail->next;}SLNode* newnode = CreateNode(x);tail->next = newnode;newnode->next = pos;}
}//单链表任意位置删除
void SLTErase(SLNode** pphead, SLNode* pos)
{assert(pphead);assert(pos);assert(*pphead);SLNode* tail = *pphead;if (*pphead == pos){SLTPopFront(pphead);}else{while (tail->next != pos){tail = tail->next;}tail->next = pos->next;free(pos);pos = NULL;}
}void SLTInsertAfter(SLNode* pos, SLNDataType x)
{assert(pos);SLNode* newnode = CreateNode(x);newnode->next = pos->next;pos->next = newnode;
}
void SLTEraseAfter(SLNode* pos)
{assert(pos);assert(pos->next);SLNode* tmp = pos->next;pos->next = pos->next->next;free(tmp);tmp = NULL;
}

3.3 study.c调用

#define _CRT_SECURE_NO_WARNINGS 
#include "SList.h"
//每个节点的地址没有关联,是随机的,东一个,西一个//想要改变int*,传的就要是int**//测试尾插和尾删
void TestSLT1()
{SLNode* plist = NULL;SLTPushBack(&plist, 1);SLTPushBack(&plist, 2);SLTPushBack(&plist, 3);SLTPushBack(&plist, 4);SLTPrint(plist);SLTPopBack(&plist);SLTPrint(plist);SLTDestroy(&plist);SLTPrint(plist);}
//测试头插和头删
void TestSLT2()
{SLNode* plist = NULL;SLTPushFront(&plist, 10);SLTPushFront(&plist, 20);SLTPushFront(&plist, 30);SLTPushFront(&plist, 40);SLTPrint(plist);SLTPopFront(&plist);SLTPrint(plist);SLTPopFront(&plist);SLTPrint(plist);SLTDestroy(&plist);}//单链表任意位置插入和删除
void TestSLT3()
{SLNode* plist = NULL;SLTPushBack(&plist, 10);SLTPushBack(&plist, 20);SLTPushBack(&plist, 30);SLTPushBack(&plist, 40);SLTPrint(plist);SLNode* pos = SListFind(plist, 30);/*if (pos != NULL){SLTInsert(&plist, pos, 3);SLTPrint(plist);}SLTDestroy(&plist);*/if (pos != NULL){SLTErase(&plist, pos);}SLTPrint(plist);SLTDestroy(&plist);
}
int main()
{//TestSLT1();//TestSLT2();TestSLT3();return 0;
}

4. 博主有话说

有关无头单链表的内容就分享到这里,更多有关内容关注博主,有问题可以留言和博主讨论。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/163962.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

大数据之LibrA数据库系统告警处理(ALM-12030 无合法license存在)

告警解释 系统在安装集群后和每天零点检查当前系统中是否存在合法的license文件&#xff0c;如果没有则产生该告警。 导入合法license文件时&#xff0c;告警恢复。 说明&#xff1a; 如果当前集群使用节点数小于等于10节点&#xff08;不包含管理节点&#xff09;&#xf…

一图搞懂傅里叶变换(FT)、DTFT、DFS和DFT之间的关系

自然界中的信号都是模拟信号&#xff0c;计算机无法处理&#xff0c;因此我们会基于奈奎斯特定理对模拟信号采样得到数字信号。 但是我们发现&#xff0c;即便是经过采样&#xff0c;在时域上得到了数字信号&#xff0c;而在频域上还是连续信号。 因此我们可以在时域中选取N点…

记录腾讯云重置密码之后ssh就连不上的踩坑

腾讯云轻量级服务器SSH连不上 解决方案在最后&#xff0c;点我跳转 问题背景&#xff1a; 首先ssh ubuntu用户我是能用xshell带上密钥正常连接的 其次我重置了root密码&#xff0c;自己改了一个root密码&#xff0c;因为我要用root账号使用ftp传输文件 然后重置密码之后&…

Redis 应用问题

1-缓存穿透 1.1-问题描述 Key 对应的数据在数据源并不存在&#xff0c;每次针对此 Key 的请求从缓存获取不到&#xff0c;请求都会压到数据源&#xff0c;从而可能压垮数据源。 比如&#xff1a;用一个不存在的用户ID 获取用户信息&#xff0c;不论缓存还是数据库都没有&…

2023年中国金融控股公司研究报告

第一章 行业概况 1.1 定义 金融控股公司这一术语最初源自美国&#xff0c;特别是在美国的《金融服务法案》关于银行控股公司组织结构的条文中&#xff0c;首次出现了“金融控股公司”&#xff08;Financial Holding Company&#xff09;这一法律术语&#xff0c;尽管法案中并…

Web服务器的搭建

网站需求&#xff1a; 1.基于域名www.openlab.com可以访问网站内容为 welcome to openlab!!! 2.给该公司创建三个网站目录分别显示学生信息&#xff0c;教学资料和缴费网站&#xff0c;基于www.openlab.com/student 网站访问学生信息&#xff0c;www.openlab.com/data网站访问教…

STM32创建工程步骤

以创建led工程为例&#xff1a; 新建一个led文件夹 新建一个以led命名的工程&#xff08;用keil_uVision5&#xff09;并添加三个组。 Library文件夹里放置库函数文件。 User&#xff1a; 点亮led灯的程序&#xff1b; 直接给寄存器赋值 调用库函数。 #include "stm…

CPP emplace_bake 和 push_back 的相同和区别

第一种情况&#xff1a; 是const T & 拿到的值&#xff0c;本身就不可改变&#xff0c;所以都一样是调用拷贝 第二种情况&#xff1a; 此时我们是主动调用的std::move&#xff0c;所以就都是一样的移动赋值。 第三种情况&#xff1a; 都是构造然后移动赋值然后再析构刚才…

jacoco和sonar

目录 jacoco 引入依赖 构建配置修改 单元测试 生成报告 查看报告 报告说明 1. Instructions 2. Branches 3. Cyclomatic Complexity 4. Lines 5. Methods 6. Classes sonar7.7 基础环境 需要下载软件 解压文件并配置 运行启动 jacoco 引入依赖 <dep…

Linux学习第36天:Linux RTC 驱动实验:时间是一条流淌的河

Linux版本号4.1.15 芯片I.MX6ULL 大叔学Linux 品人间百味 思文短情长 RTC就是实时时钟。 本笔记主要学习Linux RTC驱动试验&#xff0c;主要内容包括Linux内核RTC驱动简介、I.MX6U内部RTC分析、RTC时间查看与设置。因为Linux内核已经…

伐木猪小游戏

欢迎来到程序小院 伐木猪 玩法&#xff1a;控制小猪点击屏幕左右砍树&#xff0c;不能碰到树枝&#xff0c;考验手速与眼力&#xff0c;记录分数&#xff0c;快去挑战伐木吧^^。开始游戏https://www.ormcc.com/play/gameStart/199 html <script type"text/javascript…

STM32G030F6P6 芯片实验 (二)

STM32G030F6P6 芯片实验 (二) Hello World - GPIO LED 尝试了下, 从 0 开始建 MDK HAL M0plus Project, 成功点亮 LED了。 但是 ST-LINK跑着跑着, 码飞了! 不知飞哪去了。 只好拿 MX 建了个 MDK Base。 呼叫 SysTick HAL_Delay(), 切换 LED。 基本上都是一样的用法, 只是换…