数据结构——单链表(不带头)【C】

单链表

1. 链表的概念以及结构

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

单链表结构:

在这里插入图片描述


注意:

  1. 从图上可以看出,单链表在逻辑结构上是连续的,但是物理结构上不一定连续。
  2. 在实际过程中我们单链表的节点都是从堆上申请出来的。
  3. 从堆上的空间是编译器分配出来的,因此申请的空间地址有可能连续,也有可能不连续。

2. 单链表的模拟实现

接口总览

typedefint SLDateType;typedefstructSListNode{ SLDateType date;structSListNode* next;}SLNode;//打印链表voidSLPrint(SLNode* plist);//尾插voidSLPushBack(SLNode** plist, SLDateType num);//头插voidSLPushFront(SLNode** plist, SLDateType num);//尾删voidSLPopBack(SLNode** plist);//头删voidSLPopFront(SLNode** plist);//查找链表中的元素 SLNode*SLFind(SLNode* plist, SLDateType num);//在任意位置插入元素voidSLInsert(SLNode** plist, SLNode* pos, SLDateType num);//删除任意位置的元素voidSLErase(SLNode** plist, SLNode* pos);//摧毁链表voidSLDestory(SLNode** plist);

2.1 单链表的尾插和尾删

2.1.1 尾插

voidSLTPushBack(SLNode** pphead, SLNDataType x){assert(pphead); SLNode* newnode =CreateNode(x);//判断空链表 if(*pphead ==NULL){*pphead = newnode;}else{// 找尾 SLNode* tail =*pphead;while(tail->next !=NULL){ tail = tail->next;} tail->next = newnode;}}

2.1.2 尾删

voidSLTPopBack(SLNode** pphead){assert(pphead);//assert(*pphead);// 温柔的检查//if (*pphead == NULL)// return;// 空assert(*pphead);// 1、一个节点// 2、一个以上节点if((*pphead)->next ==NULL){free(*pphead);*pphead =NULL;}else{ SLNode* tail =*pphead;while(tail->next->next !=NULL)// 找到尾结点的前驱结点 { tail = tail->next;}free(tail->next); tail->next =NULL;}}

2.2 头插和头删

2.2.1 头插

voidSLTPushFront(SLNode** pphead, SLNDataType x){assert(pphead); SLNode* newnode =CreateNode(x); newnode->next =*pphead;*pphead = newnode;}

2.2.2 头插的应用(链表的逆置)

反转链表
根据头插法将源链表的头结点,头插入新建的newNode 链表中。

在这里插入图片描述


在这里插入图片描述


在这里插入图片描述


在这里插入图片描述
structListNode*ReverseList(structListNode* head ){structListNode* cur = head ;structListNode* newNode =NULL;while(cur !=NULL){structListNode* next = cur ->next ; cur->next = newNode; newNode = cur; cur = next ;}return newNode ;}

2.2.3 头删

voidSLTPopFront(SLNode** pphead){assert(pphead);// 空assert(*pphead); SLNode* tmp =*pphead;*pphead =(*pphead)->next;free(tmp);}

2.3 查找链表中的元素

按值查找 时间复杂度为: O(N)

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

2.4 在任意位置插入元素

voidSLTInsert(SLNode** pphead, SLNode* pos, SLNDataType x){// 严格限定pos一定是链表里面的一个有效节点assert(pphead);assert(pos);assert(*pphead);if(*pphead == pos){// 头插SLTPushFront(pphead, x);}else{ SLNode* prev =*pphead;while(prev->next != pos){ prev = prev->next;} SLNode* newnode =CreateNode(x); prev->next = newnode; newnode->next = pos;}}

2.5 删除任意位置的元素

voidSLTErase(SLNode** pphead, SLNode* pos){assert(pphead);assert(*pphead);assert(pos);if(*pphead == pos){// 头删SLTPopFront(pphead);}else{ SLNode* prev =*pphead;while(prev->next != pos){ prev = prev->next;} prev->next = pos->next;free(pos); pos =NULL;}}

2.6 遍历链表 和 销毁链表

时间复杂度为 O(N)

voidSLTPrint(SLNode* phead){ SLNode* cur = phead;while(cur !=NULL){printf("%d->", cur->val); cur = cur->next;}printf("NULL\n");}
voidSLTDestroy(SLNode** pphead){assert(pphead); SLNode* cur =*pphead;while(cur){ SLNode* next = cur->next;free(cur); cur = next;}*pphead =NULL;}

3. 链表 VS 顺序表

3.1 比较存储结构

顺序表

  1. 优点:支持随机存储 ,存储密度高
  2. 缺点:大片连续空间分配不方便 ,更改容量不方便

链表

  1. 优点:离散的小空间分配方便,方便更改容量
  2. 缺点:不可以随机存取,存储密度底

3.2 从基本操作比较

一、创建
顺序表

  1. 需要分配大片连续的空间,若空间过小则之后不方便扩容 ,若空间过大则回造成空间的浪费
  2. 静态分配:数组容量不可改变
  3. 动态分配:容量可以改变单是存入数据需要移动大量的元素,时间代价高

链表

  1. 只需要分配结点即可,之后方便拓展
  2. 相较于顺序表灵活性高

二、销毁
顺序表

  1. 静态: 系统回收
  2. 动态: 需要手动“fee” 回收堆中的空间

链表

  1. 一次删除各个结点 “fee” 注意要将链表置空

三、 增加和删除
顺序表 :增删都需要移动元素,前移或是后移 时间复杂度为 O(N) 开销主要来自移动元素 (代价高)
链表:只需要插入和删除修改指针既可 时间复杂度为 O(N) ,开销主要来自查找目标元素 (代价底)
四、 查找
顺序表 : 按位查找时间复杂度为 O(1)、按值查找时间复杂度为 O(N)有序情况下为O(logn)
链表: 按位查找时间复杂度为 O(N)、按值查找时间复杂度为 O(N)都只能从头结点开始查找
注 : 顺序表的查找效率比链表高

链表——表长难以估计、经常要增加和删除元素 。
顺序表——表长可以估计、查询操作较多。

Read more

《Linux 进程管理进阶:会话、进程组与守护进程的底层逻辑与实践》

《Linux 进程管理进阶:会话、进程组与守护进程的底层逻辑与实践》

前引:在 Linux 的世界里,进程并非孤立存在。当我们执行一条命令、启动一个服务时,这些进程会以会话(Session)为 “社交圈”、进程组(Process Group)为 “小团体” 的形式组织起来;而那些在后台默默运行、不受终端影响的守护进程(Daemon),更是 Linux 系统稳定运行的 “幕后英雄”! 目录 【一】会话 【二】前/后台进程 前台切后台进程: 查看后台进程: 后台切前台进程: 暂停后台进程: 继续运行后台进程: 【三】进程组与守护进程 (1)查看进程组 (2)守护进程 (3)守护进程原理 (4)如何创建守护进程 【一】会话 “会话”可理解为一个区域,通常一个用户登录就是一个会话,

By Ne0inhk
Linux 进程间通信之命名管道(FIFO):跨进程通信的实用方案

Linux 进程间通信之命名管道(FIFO):跨进程通信的实用方案

🔥草莓熊Lotso:个人主页 ❄️个人专栏: 《C++知识分享》《Linux 入门到实践:零基础也能懂》 ✨生活是默默的坚持,毅力是永久的享受! 🎬 博主简介: 文章目录 * 前言: * 一. 命名管道核心概念:什么是 FIFO? * 1.1 命名管道的定义 * 1.2 命名管道的核心特性 * 1.3 命名管道和匿名管道的区别与联系 * 二. 命名管道的创建方式 * 2.1 命令行创建(mkfifo 命令) * 2.2 代码创建(mkfifo 函数) * 三. 命名管道的打开规则(关键!) * 四. 命名管道实战案例 * 4.1 案例 1:命名管道实现文件拷贝 * 4.1.

By Ne0inhk
大数据视角下的时序数据库选型:Apache IoTDB 核心竞争力拆解

大数据视角下的时序数据库选型:Apache IoTDB 核心竞争力拆解

前言 随着5G、物联网与工业互联网的深度融合,时序数据正以爆炸式速度增长——工业传感器的高频采集、智能电网的实时监测、车联网的动态反馈,每天都在产生PB级时序数据。据统计,2025年国内企业时序数据产生量同比增长超60%,这类数据具备的“三高两低”特性(高吞吐、高并发、高时序性、低价值密度、低查询复杂度),对数据库系统提出了严苛挑战。选择一款适配业务场景的时序数据库,直接决定了企业数据存储效率、分析成本与业务响应速度。本文将从大数据视角出发,拆解时序数据库选型的核心逻辑,通过对比国内外主流产品,深度解析Apache IoTDB的技术优势,为企业提供可落地的选型参考。 一、大数据场景下,时序数据库选型的6大核心维度 时序数据库的选型绝非“唯性能论”,在大数据视角下,需综合考量以下6个核心维度,才能匹配企业长期发展需求: 1. 海量数据写入性能 大数据场景下,每秒十万级甚至百万级的写入是常态,工业物联网中单集群每秒需处理千万条设备数据。数据库的写入吞吐量、端到端延迟直接决定业务能否实时采集数据,高基数场景下的性能稳定性尤为关键——若设备数量突破百万级后写入性能断崖式下跌,将直接

By Ne0inhk