C语言之单链表的实现以及链表的介绍

news2024/11/26 16:59:22

一、为什么会存在链表

因为我们常用的顺序表会存在以下的一些问题:

1. 中间/头部的插入删除,时间复杂度为O(N)
2. 增容需要申请新空间,拷贝数据,释放旧空间。会有不小的消耗。
3. 增容一般是呈2倍的增长,势必会有一定的空间浪费。例如当前容量为100,满了以后增容到
200,我们再继续插入了5个数据,后面没有数据插入了,那么就浪费了95个数据空间。

针对以上顺序表中存在的问题,有人就设计出了链表这一结构。下面我将就链表中结构最简单的单链表做一个详细的介绍。

二、链表的介绍

2.1链表的概念和结构

概念:链表是一种物理存储结构上非连续、非顺序的存储结构,数据元素的逻辑顺序是通过链表
中的指针链接次序实现的 。

结构:链表逻辑图和物理图的结合

 从上图我们可以看出:链表的每一个结点都包含数据域和指针域,头结点存储的是第一个节点的地址,最后一个节点的指针域为空指针。从逻辑图上看,就好像每个结点间都有箭头指向下一个结点,从物理图上来看,就是因为每个结点都存储了下一个结点的地址。在链表的结构中需要注意的是:

1.从上图可以看出,链式结构在逻辑上是连续的,但是在物理上不一定连续。

2.现实中的结点一般都是在堆上申请出来的。

3.从堆上申请的空间,是按照一定策略来分配的,两次申请的空间可能连续也可能不连续。

2.2链表的分类

1.单向或双向

2.带头或不带头

3.循环或非循环

虽然有这么多的链表的结构,但是我们实际中最常用还是两种结构:

三、单链表的实现

见以下代码:


#pragma once
#include <stdio.h>
#include <stdlib.h>
typedef int SLTDateType;
typedef struct SListNode
{
	SLTDateType data;
	struct SListNode* next;
}SLTNode;

//打印链表的数据域的内容
void SListPrint(SLTNode* phead);

//尾插
void SListPushBack(SLTNode** pphead, SLTDateType x);

//头插
void SListPushFront(SLTNode** pphead, SLTDateType x);

//尾删
void SListPopBack(SLTNode** pphead);

//头删
void SListPopFront(SLTNode** pphead);

//查找/修改结点的值
SLTNode* SListFind(SLTNode* phead, SLTDateType x);

//在pos位置之前插入一个结点
void SListInsert(SLTNode** pphead, SLTNode* pos, SLTDateType x);

//在pos位置之后插入一个结点
void SListInsertAfter(SLTNode** pphead, SLTNode* pos, SLTDateType x);

//删除pos结点
void SListErase(SLTNode** pphead, SLTNode* pos);

//删pos的后一个结点
void SListEraseAfter(SLTNode* pos);

//销毁链表
void SListDestrory(SLTNode** pphead);
#include "Slist.h"

//创建一个新结点
SLTNode* BuyListNode(SLTDateType x)
{
    SLTNode* newnode = (SLTNode*)malloc(sizeof(SLTNode));
    if (newnode == NULL)
    {
        printf("malloc fail\n");
        exit(-1);
    }
    newnode->data = x;
    newnode->next = NULL;
    return newnode;
}

//打印链表的数据域的内容
void SListPrint(SLTNode* phead)
{
    SLTNode* cur = phead;
    while (cur != NULL)
    {
        printf("%d->", cur->data);
        cur = cur->next;
    }
    printf("NULL\n");
}

//尾插
void SListPushBack(SLTNode** pphead, SLTDateType x)
{
    SLTNode* newnode = BuyListNode(x);
    if (*pphead == NULL)
    {
        *pphead = newnode;
    }
    else
    {
        SLTNode* tail = *pphead;
        while (tail->next != NULL)
        {
            tail = tail->next;//找到最后一个结点
        }
        tail->next = newnode;
    }
    
}

//头插
void SListPushFront(SLTNode** pphead, SLTDateType x)
{
    SLTNode* newnode = BuyListNode(x);
    newnode->next = *pphead;
    *pphead = newnode;
}

//尾删
void SListPopBack(SLTNode** pphead)
{
    if (*pphead == NULL)
    {
        return;//链表为空直接返回
    }
    if ((*pphead)->next == NULL)//头结点就是尾节点
    {
        free(*pphead);
        *pphead = NULL;
    }
    else
    {
        SLTNode* tail = *pphead;
        SLTNode* prev = NULL;//最后会走到倒数第二个结点
        while (tail->next != NULL)
        {
            prev = tail;
            tail = tail->next;
        }
        free(tail);
        tail = NULL;
        prev->next = NULL;
    }
}

//头删
void SListPopFront(SLTNode** pphead)
{
    if (*pphead == NULL)//链表为空直接返回
    {
        return;
    }
    SLTNode* next = (*pphead)->next;
    free(*pphead);
    *pphead = next;//头结点指向原来的第二个结点
}

//查找x出现的结点
SLTNode* SListFind(SLTNode* phead, SLTDateType x)
{
    SLTNode* cur = phead;
    while (cur)
    {
        if (cur->data == x)
        {
            return cur;
        }
        else
        {
            cur = cur->next;
        }
    }
    return NULL;
}

//在pos位置之前插入一个结点
void SListInsert(SLTNode** pphead, SLTNode* pos, SLTDateType x)
{
    SLTNode* newnode = BuyListNode(x);
    if (*pphead == pos)//头结点就是pos的位置
    {
        //头插
        newnode->next = *pphead;
        *pphead = newnode;
    }
    //找到pos前一个位置
    else
    {
        SLTNode* posPrev = *pphead;
        while (posPrev->next != pos)
        {
            posPrev = posPrev->next;
        }
        posPrev->next = newnode;
        newnode->next = pos;
    }
}

//在pos位置之后插入一个结点
void SListInsertAfter(SLTNode** pphead, SLTNode* pos, SLTDateType x)
{
    SLTNode* newnode = BuyListNode(x);
    //以下两行代码的顺序不能调换,若调换会形成类似互指的问题
    newnode->next = pos->next;
    pos->next = newnode;
}

//删除pos结点
void SListErase(SLTNode** pphead, SLTNode* pos)
{
    if (*pphead == pos)//头删
    {
        SListPopFront(pphead);
    }
    else
    {
        SLTNode* prev = *pphead;
        while (prev->next != pos)
        {
            prev = prev->next;
        }
        prev->next = pos->next;
        free(pos);
    }
}

//删除pos结点后的一个结点
void SListEraseAfter(SLTNode* pos)
{
    SLTNode* next = pos->next;
    pos->next = next->next;
    free(next);
    next = NULL;
}

//销毁链表
void SListDestrory(SLTNode** pphead)
{
    SLTNode* cur = *pphead;
    while (cur)
    {
        SLTNode* next = cur->next;
        free(cur);
        cur = next;
    }
    *pphead = NULL;
}
#include "Slist.h"


void TestSList()
{
    SLTNode* plist = NULL;
    //尾插结点
    SListPushBack(&plist, 1);
    SListPushBack(&plist, 2);
    SListPushBack(&plist, 3);
    SListPushBack(&plist, 4);
    SListPrint(plist);

    //头插结点
    SListPushFront(&plist, 1);
    SListPushFront(&plist, 2);
    SListPushFront(&plist, 3);
    SListPushFront(&plist, 4);
    SListPrint(plist);

    //尾删结点
    SListPopBack(&plist);
    SListPopBack(&plist);
    SListPrint(plist);

    //头删结点
    SListPopFront(&plist);
    SListPrint(plist);

    //查找数值2出现的结点
    SLTNode* pos = SListFind(plist, 2);
    int i = 1;
    while (pos)
    {
        printf("第%d个pos结点:%p->%d\n", i++, pos, pos->data);
        pos = SListFind(pos->next, 2);
    }

    //在pos2结点前插入一个结点
    SLTNode* pos2 = SListFind(plist, 1);
    if (pos2)
    {
        SListInsert(&plist, pos2, 30);
    }
    SListPrint(plist);

    //在pos3结点后插入一个结点
    SLTNode* pos3 = SListFind(plist, 3);
    if (pos3)
    {
        SListInsertAfter(&plist, pos3, 20);
    }
    SListPrint(plist);

    //删除pos4结点
    SLTNode* pos4 = SListFind(plist, 2);
    SListErase(&plist, pos4);
    SListPrint(plist);

    //删除pos5结点的后一个结点
    SLTNode* pos5 = SListFind(plist, 3);
    SListEraseAfter(pos5);
    SListPrint(plist);

    //销毁链表
    SListDestrory(&plist);
}

int main()
{
    TestSList();
    return 0;

}

注:有人可能会不明白为什么有的参数传的是二级指针。当你需要对链表进行修改时,参数就需要传二级指针。如果需要对链表进行修改而你传参用的是一级指针,那么就相当于是形参重新开辟了一块空间来存放传过来的一级指针中的值。那么你在函数中对新开辟的空间中做修改就不会改变实参。如果需要在函数中对一级指针做修改,形参就需要传二级指针。

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

算法的特性和空间复杂度---数据结构

目录 前言&#xff1a; 1.算法 1.1算法的特性 1.2设计算法 2.空间复杂度 3.学习复杂度的意义 ❤博主CSDN&#xff1a;啊苏要学习 ▶专栏分类&#xff1a;数据结构◀ 学习数据结构是一件有趣的事情&#xff0c;希望读者能在我的博文切实感受到数据之间存在的关系&#xff…

【3dmax】常用的快捷键总结以及如何修改快捷键

&#x1f497; 未来的游戏开发程序媛&#xff0c;现在的努力学习菜鸡 &#x1f4a6;本专栏是我关于建模的笔记 &#x1f236;本篇是3dmax常用的快捷键总结以及如何修改快捷键 3dmax常用的快捷键总结以及如何修改快捷键 3dmax常用快捷键如何添加或修改快捷键 3dmax常用快捷键 视…

go pprof性能调优工具

go pprof 一、性能调优原则二、pprof1、pprof 功能简介2、pprof 排查实战前置工作a、CPUb、Heapc、goroutined、mutexe、block 3、pprof 的采样过程和原理a、cpub、heapc、goroutine && threadCreated、block && mutex 三、调优流程1、业务优化a、流程 2、基础…

2023.4.17-4.23 AI行业周刊(第146期):创业要趁早

最近有很多外部拓展培训的需求&#xff0c;联盟的共学课程培训&#xff0c;公司视觉软件的培训&#xff0c;行业课程的培训&#xff0c;每一项培训听起来简单&#xff0c;但是其实都需要大量的时间精力。 前两年也准备过一份《30天入门人工智能》的视频课程&#xff0c;总共31…

Ansible自动化部署工具|各个模块的使用

Ansible自动化部署工具|各个模块的使用 一、自动化运维工具—Ansible二、安装Ansible查询webserver组中主机的日期 三 Ansible常用模块(1) ansible命令行模块(2) command模块(3) shell模块(4) cron模块(5) user模块(6) grup模块(7) copy模块(8) file模块(9) ping模块(10) servi…

内网穿透NPS和宝塔Nginx配合使用,开启SSL访问本地局域网网络

并非为了教学&#xff0c;仅供自己记录&#xff0c;方便下次用。所以内容不会刻意花时间写的很细节详细。 1. 服务器NPS配置 NPS install安装后&#xff0c;配置文件会在其他位置&#xff0c;通过是 /etc/nps/nps.conf目录。 找到进行修改&#xff0c;主要修改的是http_proxy_p…

【flask】三种路由和各自的比较配置文件所有的字母必须大写if __name__的作用核心对象循环引用的几种解决方式--难Flask的经典错误

三种路由 方法1&#xff1a;装饰器 python C#, java 都可以用这种方式 from flask import Flask app Flask(__name__)app.route(/hello) def hello():return Hello world!app.run(debugTrue)方法2: 注册路由 php python from flask import Flask app Flask(__name__)//app…

【以太坊 Solidity】管理员读写权限/访问控制/角色控制

摘要 在 Solidity 语言的多继承中&#xff0c;若多个合约共同继承一个父合约&#xff0c;则这多个合约 共享 父合约中的变量和函数。 1.测试的智能合约 合约继承路线如下&#xff1a; #mermaid-svg-DtimeTjOch5CJh50 {font-family:"trebuchet ms",verdana,arial,s…

应用,auto,内联函数

6.引用&#xff1a; //指针 int main() {int a 0;int& b a;int& c b;int& d c;cout << &a << endl;cout << &b << endl;cout << &c << endl;cout << &d << endl;b;d;cout << a <<…

WEB攻防通用漏洞跨域CORS资源JSONP回调域名接管劫持

目录 一、同源策略&#xff08;SOC&#xff09; 二、跨域资源&#xff08;COSP&#xff09; 三、回调跨域&#xff08;JSOP&#xff09; 四、CORS资源跨域-敏感页面原码获取 五、JSONP 回调跨域-某牙个人信息泄露 六、子域名劫持接管 一、同源策略&#xff08;SOC&#x…

C#手麻系统源码, 基于前端Winform+后端WCF +sqlserver 开发

手麻系统源码&#xff0c;自动生成电子单据 基于C# 前端框架&#xff1a;Winform后端框架&#xff1a;WCF 数据库&#xff1a;sqlserver 开发的手术室麻醉临床信息系统源码&#xff0c;应用于医院手术室、麻醉科室的计算机软件系统。该系统针对整个围术期&#xff0c;对病人…

Buffer Pool介绍

Buffer Pool基本概念 Buffer Pool&#xff1a;缓冲池&#xff0c;简称BP。其作用是用来缓存表数据与索引数据&#xff0c;减少磁盘IO操作&#xff0c;提升效率 Buffer Pool由 缓存数据页(Page) 和 对缓存数据页进行描述的控制块 组成, 控制块中存储着对应缓存页的所属的 表空…

English Learning - L3 综合练习 1 VOA-Color 2023.04.26 周三

English Learning - L3 综合练习 1 VOA-Color 2023.04.26 周三 主题整体听一遍精听句子 1扩展 way of doing | way to do sth 句子 2扩展 Expression扩展 base 句子 3句子 4扩展 red-hot 句子 5句子 6扩展 fiery 句子 7句子 8句子 9句子 10句子 11扩展 born 句子 12句子 13句子…

Haar特征和级联分类器目标检测介绍及应用

文章目录 Haar特征和级联分类器目标检测介绍及应用1. Haar特征2. 级联分类器3. 实现步骤4.尝试训练自己的级联分类器4. 应用示例 Haar特征和级联分类器目标检测介绍及应用 Haar特征和级联分类器是一种经典的目标检测算法&#xff0c;适用于检测物体在图像中的位置、大小和姿态…

卷积神经网络算法解读

神经网络算法解读 机器学习流程&#xff1a; 数据获取特征工程建立模型评估与应用 图像数据是一个矩阵300 * 100 * 3 线性函数分类方法&#xff1a;分类&#xff08;wxb线性回归&#xff09; 损失函数&#xff1a;预测值与实际值之间的差异值 softmax分类器&#xff1a; 神…

(Python)Jupyter Notebook无法运行代码,且提示error和自动保存失败时如何操作?无法链接内核?

目录 一、报错情形 1、运行代码时跳转下一行&#xff0c;无法执行代码。 2、在修改文件名是提示失败&#xff0c;出现“error”字样。 二、深层原因 三、解决方案 四.建议使用原环境 Anaconda的Jupyter Notebook作为优秀的网页编辑器&#xff0c;非常适用于编写Python程序…

ubuntu16.04升级到20.04后报错 By not providing “FindEigen.cmake“

编译问题&#xff1a; CMake Error at modules/perception/lidar/CMakeLists.txt:14 (find_package): By not providing "FindEigen.cmake" in CMAKE_MODULE_PATH this project has asked CMake to find a package configuration file provided by "Eigen&…

黑马Redis入门到实战(基础篇)

Redis基础篇 Redis的类型和常见命令以及客户端使用 目录 1 .初识Redis 1 .1 .认识NoSQL 1 .1 .1 .结构化与非结构化 1 .1 .2 .关联和非关联 1 .1 .3 .查询方式 1 .1 .4 .事务 1 .1 .5 .总结 1 .2 .认识Redis 1 .3 .安装Redis 1 .3 .1 .依赖库 1 .3 .2 .上传安装包并解压 1 .3 .…

音视频八股文(8)-- h264 AnnexB三层结构

NALU(Network Abstract Layer Unit) ⾳视频编码在流媒体和⽹络领域占有重要地位&#xff1b;流媒体编解码流程⼤致如下图所示&#xff1a; H264简介 H.264从1999年开始&#xff0c;到2003年形成草案&#xff0c;最后在2007年定稿有待核实。在ITU的标准⾥称为H.264&#xff0c…

使用QtInstallerFramework制作安装包总结

一、linux下使用QtInstallerFramework制作安装包 4.0.1版本的QtInstallerFramework-linux-x64.run在linux中运行报错&#xff1a; libdbus-1.so.3 no version information availabe 换成3.0.4版本的就好了&#xff1a;https://download.qt.io/official_releases/qt-installer-…