当前位置: 首页 > news >正文

【数据结构】第五站:带头双向循环链表

目录

一、链表的八种结构

二、带头双向循环链表的实现

1.链表的定义

2.链表的接口定义

3.接口的具体实现

三、带头双向循环链表的完整代码

四、顺序表和链表的区别


一、链表的八种结构

我们已经知道链表可以有以下三种分法

 

 

而这三种结构又可以排列组合,形成八种结构

其中我们最常见的就是无头单向非循环链表和带头双向循环链表

1.无头单向非循环链表:结构简单,一般不会单独用来存数据。实际中更多是作为其他数据结构的子结构,如哈希桶、图的邻接表等等。
2. 带头双向循环链表:结构最复杂,一般用在单独存储数据。实际中使用的链表数据结构,都是带头双向循环链表。另外这个结构虽然结构复杂,但是使用代码实现以后会发现结构会带来很多优势,实现反而简单了

二、带头双向循环链表的实现

1.链表的定义

对于这个链表,我们需要使用两个指针来控制,一个是前驱指针,一个是后继指针

typedef int LTDateType;
typedef struct ListNode
{struct ListNode* prev;struct ListNode* next;LTDateType data;
}ListNode;

2.链表的接口定义

如下代码所示,是我们需要实现的接口

//链表的初始化
ListNode* ListCreat();
//链表的打印
void ListPrint(ListNode* phead);
//链表的尾插
void ListPushBack(ListNode* phead, LTDateType x);
//链表的头插
void ListPushFront(ListNode* phead, LTDateType x);
//链表是否为空
bool ListEmpty(ListNode* phead);
//链表的尾删
void ListPopBack(ListNode* phead);
//链表的头删
void ListPopFront(ListNode* phead);
//链表的查找
ListNode* ListFind(ListNode* phead, LTDateType x);
//链表在pos前面的插入
void ListInsert(ListNode* pos, LTDateType x);
//链表在pos位置处的删除
void ListErase(ListNode* pos);
//链表的销毁
void ListDestroy(ListNode* phead);

3.接口的具体实现

1.链表的初始化

ListNode* BuyListNode(LTDateType x)
{ListNode* newnode = (ListNode*)malloc(sizeof(ListNode));if (newnode == NULL){perror("malloc");return NULL;}newnode->data = x;newnode->next = NULL;newnode->prev = NULL;return newnode;
}
//链表的初始化
ListNode* ListCreat()
{ListNode* phead = BuyListNode(-1);phead->next = phead;phead->prev = phead;return phead;
}

如上代码所示,对于这个初始化,他与单链表不同,单链表其实是没有必要去专门做一个初始化函数的。但同时我们需要传二级指针来解决一些问题

对于双向带头循环链表,我们就有必要设置一个初始化函数了。

我们有两种思路去构建这个函数

一种是在主函数中直接定义一个指针,然后通过传这个指针的地址去构建。但是这样我们就需要传二级指针了。

另外一种是我们可以利用返回值,这样我们就直接避免了传参数了。

2.链表的打印

//链表的打印
void ListPrint(ListNode* phead)
{assert(phead);ListNode* cur = phead->next;printf("<=head=>");while (cur != phead){printf("%d<=>", cur->data);cur = cur->next;}printf("\n");
}

如上代码所示,链表的打印与单链表的打印是一样的,没有什么太大的区别,我们可以利用printf来更加形象的表达双向循环的意思

3.链表的尾插

//链表的尾插
void ListPushBack(ListNode* phead, LTDateType x)
{assert(phead);ListNode* newnode = BuyListNode(x);ListNode* tail = phead->prev;tail->next = newnode;newnode->prev = tail;newnode->next = phead;phead->prev = newnode;
}

对于尾插就比单链表简单多了,因为有了头节点,我们就不需要处理是否为空的状态了

4.链表的头插

//链表的头插
void ListPushFront(ListNode* phead, LTDateType x)
{assert(phead);ListNode* newnode = BuyListNode(x);ListNode* first = phead->next;phead->next = newnode;newnode->prev = phead;newnode->next = first;first->prev = newnode;
}

头插和尾插基本上是非常类似的,这也得益于这种链表结构基本是无死角的

5.链表的头删尾删以及是否为空

//链表是否为空
bool ListEmpty(ListNode* phead)
{assert(phead);return phead->next == phead;
}
//链表的尾删
void ListPopBack(ListNode* phead)
{assert(phead);assert(!ListEmpty(phead));ListNode* tail = phead->prev;ListNode* tailPrev = tail->prev;phead->prev = tailPrev;tailPrev->next = phead;free(tail);tail = NULL;
}
//链表的头删
void ListPopFront(ListNode* phead)
{assert(phead);assert(!ListEmpty(phead));ListNode* first = phead->next;ListNode* firstNext = first->next;phead->next = firstNext;firstNext->prev = phead;free(first);first = NULL;
}

对于头删和尾删其实也是一致的思路,我们只需要改变结点即可,值得注意的是,我们需要判断链表是否为空, 如果为空肯定不可以删除

6.链表的查找

//链表的查找
ListNode* ListFind(ListNode* phead,LTDateType x)
{assert(phead);assert(!ListEmpty(phead));ListNode* cur = phead->next;while (cur != phead){if (cur->data == x){return cur;}cur = cur->next;}return NULL;
}

这个也是比较简单的,他的思路与单链表的是一样的

7.链表在pos位置之前插入

//链表在pos前面的插入
void ListInsert(ListNode* pos, LTDateType x)
{assert(pos);ListNode* newnode = BuyListNode(x);ListNode* prev = pos->prev;prev->next = newnode;newnode->prev = prev;newnode->next = pos;pos->prev = newnode;
}

这个的话,我们更能体会到他相对于单链表的优势了。我们只需要pos位置即可。然后思路和头插尾插是一致的

8.链表在pos位置的删除

//链表在pos位置处的删除
void ListErase(ListNode* pos)
{assert(pos);ListNode* prev = pos->prev;ListNode* next = pos->next;prev->next = next;next->prev = prev;free(pos);pos = NULL;
}

对于删除,也是与头删尾删思路一致的

9.链表的销毁

//链表的销毁
void ListDestroy(ListNode* phead)
{assert(phead);ListNode* cur = phead->next;while (cur != phead){ListNode* next = cur->next;free(cur);cur = next;}free(phead);phead = NULL;
}

对于链表的销毁思路与单链表也是一致的,只需要循环遍历即可

三、带头双向循环链表的完整代码

List.h

#pragma once
#include<stdio.h>
#include<malloc.h>
#include<assert.h>
#include<stdbool.h>
typedef int LTDateType;
typedef struct ListNode
{struct ListNode* prev;struct ListNode* next;LTDateType data;
}ListNode;//链表的初始化
ListNode* ListCreat();
//链表的打印
void ListPrint(ListNode* phead);
//链表的尾插
void ListPushBack(ListNode* phead, LTDateType x);
//链表的头插
void ListPushFront(ListNode* phead, LTDateType x);
//链表是否为空
bool ListEmpty(ListNode* phead);
//链表的尾删
void ListPopBack(ListNode* phead);
//链表的头删
void ListPopFront(ListNode* phead);
//链表的查找
ListNode* ListFind(ListNode* phead, LTDateType x);
//链表在pos前面的插入
void ListInsert(ListNode* pos, LTDateType x);
//链表在pos位置处的删除
void ListErase(ListNode* pos);
//链表的销毁
void ListDestroy(ListNode* phead);

List.c

#define _CRT_SECURE_NO_WARNINGS 1
#include"List.h"
ListNode* BuyListNode(LTDateType x)
{ListNode* newnode = (ListNode*)malloc(sizeof(ListNode));if (newnode == NULL){perror("malloc");return NULL;}newnode->data = x;newnode->next = NULL;newnode->prev = NULL;return newnode;
}
//链表的初始化
ListNode* ListCreat()
{ListNode* phead = BuyListNode(-1);phead->next = phead;phead->prev = phead;return phead;
}
//链表的打印
void ListPrint(ListNode* phead)
{assert(phead);ListNode* cur = phead->next;printf("<=head=>");while (cur != phead){printf("%d<=>", cur->data);cur = cur->next;}printf("\n");
}
//链表的尾插
void ListPushBack(ListNode* phead, LTDateType x)
{assert(phead);ListNode* newnode = BuyListNode(x);ListNode* tail = phead->prev;tail->next = newnode;newnode->prev = tail;newnode->next = phead;phead->prev = newnode;
}
//链表的头插
void ListPushFront(ListNode* phead, LTDateType x)
{assert(phead);ListNode* newnode = BuyListNode(x);ListNode* first = phead->next;phead->next = newnode;newnode->prev = phead;newnode->next = first;first->prev = newnode;
}
//链表是否为空
bool ListEmpty(ListNode* phead)
{assert(phead);return phead->next == phead;
}
//链表的尾删
void ListPopBack(ListNode* phead)
{assert(phead);assert(!ListEmpty(phead));ListNode* tail = phead->prev;ListNode* tailPrev = tail->prev;phead->prev = tailPrev;tailPrev->next = phead;free(tail);tail = NULL;
}
//链表的头删
void ListPopFront(ListNode* phead)
{assert(phead);assert(!ListEmpty(phead));ListNode* first = phead->next;ListNode* firstNext = first->next;phead->next = firstNext;firstNext->prev = phead;free(first);first = NULL;
}
//链表的查找
ListNode* ListFind(ListNode* phead,LTDateType x)
{assert(phead);assert(!ListEmpty(phead));ListNode* cur = phead->next;while (cur != phead){if (cur->data == x){return cur;}cur = cur->next;}return NULL;
}
//链表在pos前面的插入
void ListInsert(ListNode* pos, LTDateType x)
{assert(pos);ListNode* newnode = BuyListNode(x);ListNode* prev = pos->prev;prev->next = newnode;newnode->prev = prev;newnode->next = pos;pos->prev = newnode;
}
//链表在pos位置处的删除
void ListErase(ListNode* pos)
{assert(pos);ListNode* prev = pos->prev;ListNode* next = pos->next;prev->next = next;next->prev = prev;free(pos);pos = NULL;
}
//链表的销毁
void ListDestroy(ListNode* phead)
{assert(phead);ListNode* cur = phead->next;while (cur != phead){ListNode* next = cur->next;free(cur);cur = next;}free(phead);phead = NULL;
}

Test.c

#define _CRT_SECURE_NO_WARNINGS 1#include"List.h"void TestList1()
{ListNode* plist = ListCreat();ListPushBack(plist, 1);ListPushBack(plist, 2);ListPushBack(plist, 3);ListPushBack(plist, 4);ListPushBack(plist, 5);ListPrint(plist);ListPushFront(plist, 6);ListPushFront(plist, 7);ListPushFront(plist, 8);ListPushFront(plist, 9);ListPushFront(plist, 10);ListPrint(plist);ListPopBack(plist);ListPopBack(plist);ListPopBack(plist);ListPopBack(plist);ListPrint(plist);ListPopFront(plist);ListPopFront(plist);ListPopFront(plist);ListPopFront(plist);ListPrint(plist);}
void TestList2()
{ListNode* plist = ListCreat();ListPushBack(plist, 1);ListPushBack(plist, 2);ListPushBack(plist, 3);ListPushBack(plist, 4);ListPushBack(plist, 5);ListPrint(plist);ListNode* pos = ListFind(plist, 3);(pos->data) *= 10;ListPrint(plist);ListInsert(pos, 20);ListPrint(plist);ListErase(pos);ListPrint(plist);}
int main()
{//TestList1();TestList2();return 0;
}

四、顺序表和链表的区别

不同点顺序表链表
存储空间上物理上一定连续逻辑上连续,但物理上不一定连续
随机访问支持,O(1)不支持,O(N)
任意位置插入或删除元素可能需要搬移元素,效率低,O(N)只需要改变指针指向
插入动态顺序表,空间不够时需要扩容没有容量的概念
应用场景元素高效存储+频繁访问任意位置插入和删除频繁
缓存利用率

如上表所示,是顺序表和链表的区别

在这里我们需要注意缓存利用率这个东西。

对于我们的顺序表/链表,想要实现他的数据访问、遍历、修改等

这些数据是由cpu来进行访问的。

也就是这些访问的指令都是cpu执行的。从而访问打印等操作。这时候cpu就需要去访问内存

而cpu其实是不能直接访问内存的,这是因为cpu太快了,内存太慢了

为了解决这个问题,就有了三级缓存,即cpu高速缓存这个东西。

也就是说cpu会先访问缓存,最高级的缓存再次缓存其实就是命中。

在计算机中有一个局部性原理,在访问某个位置的数据的时候,大概率要访问后面的数据。因此他就会将这个数据后面的数据也顺便加载进去。

这时候由于顺序表是连续的,后面的都直接被命中了。所以他的缓存利用率高

而链表他访问某个数据的时候也会加载一长段,但是后面的不一定会用。所以就会浪费,也会导致缓存污染。所以缓存利用率低。也就是他不仅没有起到正向作用,反而起到了反向作用。


本节内容到此位置

如果对你有帮助的话,不要忘记点赞加收藏哦!!!

相关文章:

【数据结构】第五站:带头双向循环链表

目录 一、链表的八种结构 二、带头双向循环链表的实现 1.链表的定义 2.链表的接口定义 3.接口的具体实现 三、带头双向循环链表的完整代码 四、顺序表和链表的区别 一、链表的八种结构 我们已经知道链表可以有以下三种分法 而这三种结构又可以排列组合&#xff0c;形成八…...

Springboot生成二维码

Springboot生成二维码整合 我们使用两种方式&#xff0c;去生成二维码&#xff0c;但是其实&#xff0c;二维码的生成基础&#xff0c;都是zxing包&#xff0c;这是Google开源的一个包&#xff0c;第一种是使用原始的zxing方式去实现&#xff0c;第二种是使用hutool来实现&…...

“独裁者”何小鹏,再造小鹏汽车

文丨智能相对论 作者丨沈浪 如果没有何小鹏&#xff0c;小鹏汽车将失去灵魂。 2014年&#xff0c;夏珩、何涛等人在广州组建小鹏汽车&#xff08;当时还叫“橙子汽车”&#xff09;&#xff0c;何小鹏还只是股权投资人。 夏珩、何涛原任职于广汽&#xff0c;负责新能源汽车…...

数据结构 | 泛型 | 擦除机制| 泛型的上界

目录 ​编辑 1.泛型 1.1Object类引出泛型概念 2.泛型语法 2.1泛型编写代码 3.泛型的机制 3.1擦除机制 4.泛型的上界 4.1泛型上界的语法 4.2泛型上界的使用 5.泛型方法 5.1泛型方法语法 5.2泛型方法的使用 1.泛型 一般的类和方法中&#xff0c;只能使用具体的代码…...

C++拷贝构造函数(复制构造函数)详解

拷贝和复制是一个意思&#xff0c;对应的英文单词都是copy。对于计算机来说&#xff0c;拷贝是指用一份原有的、已经存在的数据创建出一份新的数据&#xff0c;最终的结果是多了一份相同的数据。例如&#xff0c;将 Word 文档拷贝到U盘去复印店打印&#xff0c;将 D 盘的图片拷…...

python学习——多线程

python学习——多线程概念python中线程的开发线程的启动线程的退出和传参threading的属性和方法threading实例的属性和方法多线程daemon线程和non-demone线程daemon线程的应用场景线程的jointhreading.local类线程的延迟执行&#xff1a;Timer线程同步Event 事件Lock ——锁加锁…...

SAP 系统中过账码or记账码

SAP中过账码和记账码是指同一个事物。 在实际业务中&#xff0c;记账码就是只有“借”和“贷”&#xff0c; 而SAP中Posting Code肩负着更多的任务&#xff1a; 1&#xff09;界定科目类型&#xff0c; 2&#xff09;借贷方向&#xff0c; 3&#xff09;凭证输入时画面上的字…...

【FreeRTOS(一)】FreeRTOS新手入门——初识FreeRTOS

初识FreeRTOS一、实时操作系统概述1、概念2、RTOS的必要性3、RTOS与裸机的区别4、FreeRTOS的特点二、FreeRTOS的架构三、FreeRTOS的代码架构一、实时操作系统概述 1、概念 RTOS&#xff1a;根据各个任务的要求&#xff0c;进行资源&#xff08;包括存储器、外设等&#xff09…...

Python中 __init__的通俗解释是什么?

__init__是Python中的一个特殊方法&#xff0c;用于在创建对象时初始化对象的属性。通俗来讲&#xff0c;它就像是一个构造函数&#xff0c;当我们创建一个类的实例时&#xff0c;__init__方法会被自动调用&#xff0c;用于初始化对象的属性。 举个例子&#xff0c;如果我们定义…...

网友真实面试总结出的自动化测试面试题库

目录 常规问题 手工测试部 自动化测试 自动化测试面试题2&#xff1a;selenium篇 常规问题 1、如何快速深入的了解移动互联网领域的应用 &#xff08;答案&#xff1a;看http协议 restful api知识 json加1分&#xff09; 2、对xx应用自己会花多久可以在业务上从入门到精通&…...

2023 年最佳 C++ IDE

文章目录前言1. Visual Studio2. Code::Blocks3. CLion4. Eclipse CDT&#xff08;C/C 开发工具&#xff09;5. CodeLite6. Apache NetBeans7. Qt Creator8. Dev C9. C Builder10. Xcode11. GNAT Programming Studio12. Kite总结前言 要跟踪极佳 IDE&#xff08;集成开发环境&…...

在Ubuntu上使用VSCode编译MySQL Connector/C连接库

首先下载并解压MySQL Connector/C源码&#xff0c;然后执行以下步骤&#xff1a; 1、安装MySQL Connector/C依赖&#xff1a;在终端中输入以下命令来安装MySQL Connector/C的依赖项&#xff1a; sudo apt-get install build-essential cmake 2、下载并解压MySQL Connector/C源…...

单声道数字音频放大器AD87589

AD87589是一种集成音频系统解决方案&#xff0c;嵌入数字音频处理、功率级放大器和立体声2Vrms线路驱动器。 AD87589具有可编程转换速率控制的输出缓冲器&#xff0c;可直接驱动一个&#xff08;单声道&#xff09;或两个&#xff08;立体声&#xff09;扬声器。此外&#xff0…...

网络的UDP协议和TCP协议

协议&#xff1a;数据在网络中的传输规则&#xff0c;常见的协议有 UDP协议和TCP协议 协议&#xff1a;计算机网络中&#xff0c;连接和通信的规则被称为网络通信协议 UDP协议&#xff1a;用户数据报协议&#xff0c;是面向无连接通信协议&#xff0c;速度快&#xff0c;有大小…...

【JaveEE】多线程之阻塞队列(BlockingQueue)

目录 1.了解阻塞队列 2.生产者消费者模型又是什么&#xff1f; 2.1生产者消费者模型的优点 2.1.1降低服务器与服务器之间耦合度 2.1.2“削峰填谷”平衡消费者和生产的处理能力 3.标准库中的阻塞队列&#xff08;BlockingQueue&#xff09; 3.1基于标准库&#xff08;Bloc…...

分布式ELK日志监控系统环境搭建

文章目录1.1为什么需要监控项目日志1.2ELK日志监控系统介绍1.3ELK的工作流程1.4ELK环境搭建1.4.1Elasticsearch的安装1.4.2Kibana的安装1.4.3Logstash的安装1.4.4数据源配置1.4.5日志监测测试1.4.6日志数据可视化展示1.1为什么需要监控项目日志 项目日志是记录项目运行过程中产…...

【数据结构刷题集】链表经典习题

&#x1f63d;PREFACE&#x1f381;欢迎各位→点赞&#x1f44d; 收藏⭐ 评论&#x1f4dd;&#x1f4e2;系列专栏&#xff1a;数据结构刷题集&#x1f50a;本专栏涉及到题目是数据结构专栏的补充与应用&#xff0c;只更新相关题目&#xff0c;旨在帮助提高代码熟练度&#x…...

JavaSE(3.27) 异常

学习不要眼高手低&#xff0c;学习是一点点积累的。即使你现在很菜&#xff0c;坚持学一个学期不会差的&#xff01;只要花时间学习&#xff0c;每天都是进步的&#xff0c;这些进步可能你现在看不到&#xff0c;但是不要小瞧了积累效应&#xff0c;30天&#xff0c;60天&#…...

【看门狗】我说的是定时器不是狗啊

单片机在运行中死机了&#xff0c;你或许只能按2下电源键&#xff08;重启&#xff09;或1下复位键。 这里简单说一下重启和复位&#xff1a; 从RESET引脚复位&#xff0c;只有MCU复位。而外设看情况&#xff0c;有的可能会有MCU同步复位或者重新初始化。也有可能一些保持复位…...

24万字智慧城市顶层设计及智慧应用解决方案

本资料来源公开网络&#xff0c;仅供个人学习&#xff0c;请勿商用&#xff0c;如有侵权请联系删除。部分资料内容&#xff1a; 4.8 机房消防系统 4.8.1消防系统概况 根据本工程机房消防系统的特殊要求&#xff0c;整个消防系统由火灾报警系统、消防联动系统和气体灭火系统三部…...

[特殊字符] 智能合约中的数据是如何在区块链中保持一致的?

&#x1f9e0; 智能合约中的数据是如何在区块链中保持一致的&#xff1f; 为什么所有区块链节点都能得出相同结果&#xff1f;合约调用这么复杂&#xff0c;状态真能保持一致吗&#xff1f;本篇带你从底层视角理解“状态一致性”的真相。 一、智能合约的数据存储在哪里&#xf…...

内存分配函数malloc kmalloc vmalloc

内存分配函数malloc kmalloc vmalloc malloc实现步骤: 1)请求大小调整:首先,malloc 需要调整用户请求的大小,以适应内部数据结构(例如,可能需要存储额外的元数据)。通常,这包括对齐调整,确保分配的内存地址满足特定硬件要求(如对齐到8字节或16字节边界)。 2)空闲…...

Linux链表操作全解析

Linux C语言链表深度解析与实战技巧 一、链表基础概念与内核链表优势1.1 为什么使用链表&#xff1f;1.2 Linux 内核链表与用户态链表的区别 二、内核链表结构与宏解析常用宏/函数 三、内核链表的优点四、用户态链表示例五、双向循环链表在内核中的实现优势5.1 插入效率5.2 安全…...

C# 求圆面积的程序(Program to find area of a circle)

给定半径r&#xff0c;求圆的面积。圆的面积应精确到小数点后5位。 例子&#xff1a; 输入&#xff1a;r 5 输出&#xff1a;78.53982 解释&#xff1a;由于面积 PI * r * r 3.14159265358979323846 * 5 * 5 78.53982&#xff0c;因为我们只保留小数点后 5 位数字。 输…...

网站指纹识别

网站指纹识别 网站的最基本组成&#xff1a;服务器&#xff08;操作系统&#xff09;、中间件&#xff08;web容器&#xff09;、脚本语言、数据厍 为什么要了解这些&#xff1f;举个例子&#xff1a;发现了一个文件读取漏洞&#xff0c;我们需要读/etc/passwd&#xff0c;如…...

CVE-2020-17519源码分析与漏洞复现(Flink 任意文件读取)

漏洞概览 漏洞名称&#xff1a;Apache Flink REST API 任意文件读取漏洞CVE编号&#xff1a;CVE-2020-17519CVSS评分&#xff1a;7.5影响版本&#xff1a;Apache Flink 1.11.0、1.11.1、1.11.2修复版本&#xff1a;≥ 1.11.3 或 ≥ 1.12.0漏洞类型&#xff1a;路径遍历&#x…...

【 java 虚拟机知识 第一篇 】

目录 1.内存模型 1.1.JVM内存模型的介绍 1.2.堆和栈的区别 1.3.栈的存储细节 1.4.堆的部分 1.5.程序计数器的作用 1.6.方法区的内容 1.7.字符串池 1.8.引用类型 1.9.内存泄漏与内存溢出 1.10.会出现内存溢出的结构 1.内存模型 1.1.JVM内存模型的介绍 内存模型主要分…...

day36-多路IO复用

一、基本概念 &#xff08;服务器多客户端模型&#xff09; 定义&#xff1a;单线程或单进程同时监测若干个文件描述符是否可以执行IO操作的能力 作用&#xff1a;应用程序通常需要处理来自多条事件流中的事件&#xff0c;比如我现在用的电脑&#xff0c;需要同时处理键盘鼠标…...

用鸿蒙HarmonyOS5实现中国象棋小游戏的过程

下面是一个基于鸿蒙OS (HarmonyOS) 的中国象棋小游戏的实现代码。这个实现使用Java语言和鸿蒙的Ability框架。 1. 项目结构 /src/main/java/com/example/chinesechess/├── MainAbilitySlice.java // 主界面逻辑├── ChessView.java // 游戏视图和逻辑├──…...

Python实现简单音频数据压缩与解压算法

Python实现简单音频数据压缩与解压算法 引言 在音频数据处理中&#xff0c;压缩算法是降低存储成本和传输效率的关键技术。Python作为一门灵活且功能强大的编程语言&#xff0c;提供了丰富的库和工具来实现音频数据的压缩与解压。本文将通过一个简单的音频数据压缩与解压算法…...