房产汽车网站模板/赣州网站建设公司
文章目录
- 1. 快排主框架
- 2. 快排的不同实现
- 2. 1 hoare版本
- 2. 2 挖坑法
- 2. 3 lomuto前后指针法
- 2. 4 快排的非递归版本
- 3. 快排优化
- 3. 1 快排性能的关键点分析:
- 3. 1 三路划分
- 3. 2 introsort自省排序
1. 快排主框架
快速排序是Hoare于1962年提出的一种二叉树结构的交换排序方法。
其基本思想为:任取待排序元素序列中的某元素作为基准值,按照该排序码将待排序集合分割成两子序列,左子序列中所有元素均小于基准值,右子序列中所有元素均大于基准值,然后最左右子序列重复该过程,直到所有元素都排列在相应位置上为止。
简单地说,就是将数组分成左右两个部分,左部分都大于(或小于)中间的基准值,右部分都小于(或大于)中间的基准值,然后不断重复上述过程,直到数组完全有序。
//快排主框架
void QuickSort(int* a, int left, int right)
{if (left >= right)return;int mid = PartSort(a, left, right);QuickSort(a, left, mid - 1);QuickSort(a, mid + 1, right);
}
那么显然快排最关键的就是PartSort
这个函数怎么实现了,这个函数要实现:找到基准值并将数据按照大小划分到基准值的两侧。
- 时间复杂度:
O(nlogn)
- 空间复杂度:
O(logn)
2. 快排的不同实现
这里的PartSort
函数的实现有很多种,这里介绍3种。
2. 1 hoare版本
算法思路
- 创建左右指针,确定基准值
- 从右向左找出比基准值小的数据,从左向右找出比基准值大的数据,左右指针数据交换,进入下次循环
- 跳出循环后,交换
right
和key
提问1:为什么跳出循环后right位置的值一定不大于key?
当 left > right
时,即right
走到left
的左侧,而left
扫描过的数据均不大于key
,因此right
此时指向的数据一定不大于key
提问2:当left==right
时要不要跳出循环?
不能,因为此时不知道left
和right
同时指向的这个值的大小,必须让right
或者left
额外多走一步。
我们通过这三个场景来分析提问1并详细解释hoare版本的思路:
首先我们选定第一个元素为基准值(实际上选择基准值还有其他更好地方式,但这里先简化),然后将left=1,right=numsSize-1
,也就是除了第一个元素外,数组的头和尾。
注:以升序为例。
场景一:
left<key,left++
;right > key
,right
不动。
left++,left++
,left
指向7。
left
和right
交换数据,数组变为:
int a[]={6,1,2,3,9,7};
left++,right++
,left==right
,right
指向9。
此时right
大于key
,肯定不能直接交换,必须再进行一次交换,让left > right
,而因为此时right
已经走到了left
的左边,left
的左边一定小于key
。
将right
与key
交换,第一次快排结束,此时数组为:
int a[]={3,1,2,6,9,7};
并将right
返回,right
就是基准值。
这里调用时,PartSort
的返回值就是mid
,left
和right
依然是传入的left
和right
,
QuickSort(a, left, mid - 1);
QuickSort(a, mid + 1, right);
接下来就是继续递归,left
和right
(在递归传参时改变)最终会在循环之前就left >= right
,递归停止。
场景二:
第一次快排在交换right
与key
之前的结果是这样的:
int a[]={6,1,2,3,6,7};
left
与right
相遇在6。
且此时left == right
,right
指向3。right
位置的值不大于key
。
场景三:
第一次快排在交换right
与key
之前的结果是这样的:
left
与right
相遇在4。
int a[]={6,1,2,3,4,7}; //实际上的步骤和场景二是一样的,只是right位置的值不一样
且此时left == right
,right
指向3。right
位置的值不大于key
。
为什么不用left
与right
交换?
我们来看这个数组:
int a[]={6,1,2,3,7};
left
和right
在7相遇,left++
,right--
之后,left
指向的位置就已经越界了。
但是right
就不会有这个问题,因为我们选中的基准值在最左边,即使在第二个数字相遇,right--
之后也不会越界。
代码:
// 快速排序Hoare版本
int PartSort1(int* a, int left, int right)
{int keyi = left; //设定key为最左边的值left++;while (left <= right){while (left <= right && a[left] < a[keyi])left++;while (left <= right && a[right] > a[keyi])right--;if(left <= right) //注意这里要加这个判断,因为上面的两个循环可能是因为left>right结束的Swap(&a[left++], &a[right--]);}Swap(&a[right], &a[keyi]); //将right的值与keyi交换return right;
}
2. 2 挖坑法
思路:
创建左右指针。首先从右向左找出比基准小的数据,找到后立即放入左边坑中,当前位置变为新的"坑",然后从左向右找出比基准大的数据,找到后立即放入右边坑中,当前位置变为新的"坑",结束循环后将最开始存储的分界值放入当前的"坑"中,返回当前"坑"下标(即分界值下标)。
int a[]={6,1,2,7,9,3,4,5,10,8};
我们以这个数组为例,分析挖坑法的步骤:
首先将6挖成坑,并将6存成key
。
int hole = left;
int key = a[left];
先从右边开始遍历,到5时a[right] > key
,停下遍历,将right
的值放到坑中,并将right
的位置挖成坑:
a[hole] = a[right]; //把right的值放进坑中
hole = right; //把right挖成新的坑
再从左边遍历,到7时,停下遍历,将left
的值放到坑中,并将left
的位置挖成坑。
重复上述过程,直到left > right
,把最开始的key
放进现在的坑中。
挖坑法实际上和hoare版本的原理差不多,只不过是挖坑填坑这个动作代替了交换,并没有本质的变化。
代码:
// 快速排序挖坑法
int PartSort2(int* a, int left, int right)
{int hole = left;int key = a[left];while (left < right){//右边找while (left < right && a[right] >= key)right--;a[hole] = a[right]; //赋值与挖坑hole = right;while (left < right && a[left] <= key)left++;a[hole] = a[left];hole = left;}a[hole] = key; //把key放进坑里return hole; //最后的坑就是基准值的位置
}
2. 3 lomuto前后指针法
创建前后指针,从左往右找比基准值小的进行交换,使得小的都排在基准值的左边。
前指针prev
在开始的时候指向left+1
,后指针cur
指向left
也就是基准值。
prev
开始遍历,如果发现cur
的值小于key
,就把prev++
,再cur
和prev
指向的值交换,当遍历完成后,把prev
和left
指向的值交换。这样结果就是比基准值小的值都会在prev
的左边,那么比基准值大的值就会都在prev
右边,划分就完成了。
我们以这个数组为例,简单说明一下前后指针法的步骤:
int a[]={6,1,2,7,9,3,4,5,10,8};
一开始,prev
指向6,cur
指向1,key
为6。
cur
向后遍历,发现2比6小,prev++
,并与cur
交换,但这是我们会发现prev
和cur
其实是一样的,那么交换就没有意义了,还会浪费性能,可以在这里添加一个判断。此时prev
和cur
都指向1。
然后cur
继续遍历到2,和1一样。此时prev
和cur
都指向2。
接着cur
继续遍历,直到cur
指向3,prev++
,然后与3进行交换,因为prev++
之后指向的值是cur
遍历过的,所以一定是大于key
的。此时数组为:
int a[]={6,1,2,3,9,7,4,5,10,8};
prev
指向3,cur
指向7。
…………
可以发现,在这个过程中比基准值小的值不断地被交换到prev
及prev
的左边,那么最终把基准值与prev
交换,就可以实现划分了。
// 快速排序前后指针法
int PartSort3(int* a, int left, int right)
{int prev = left;int cur = left + 1;int key = a[left];while (cur <= right){if (a[cur] < key && ++prev != cur) //先++,再比较Swap(&a[cur], &a[prev]); //Swap函数自行实现即可cur++;}Swap(&a[left], &a[prev]); //把基准值放到prev的位置return prev;
}
2. 4 快排的非递归版本
上面的三种方法都是通过递归实现的,那么有没有办法不使用递归实现快速排序呢?
当然有,只不过需要借助数据结构——栈。
用left
和right
进行基准值的计算并划分,然后把本应递归的区间的新的left
和right
入栈,在入栈或出栈时判断left
和right
的大小是否合适,一直运行到栈为空,快速排序就完成了。
void QuickSortNonR(int* a, int left, int right)
{Stack st;StackInit(&st);Stack* tmp = &st; //以上均为创建栈StackPush(tmp, right); //将初始的left和right入栈,可以方便StackPush(tmp, left);while (!StackEmpty(tmp)){//出栈得到本次循环的left和rightint lefti = StackTop(tmp);StackPop(tmp);int righti = StackTop(tmp);StackPop(tmp);//采用前后指针法得到基准值并划分,也可以使用其他的办法int keyi = lefti;int prev = lefti;int cur = lefti + 1;while (cur <= righti){if (a[cur] < a[keyi] && prev++ != cur)Swap(&a[cur], &a[prev]);cur++;}Swap(&a[prev], &a[keyi]);keyi = prev;//入栈前判断if (keyi - 1 > lefti){StackPush(tmp, keyi - 1);StackPush(tmp, lefti);}if (keyi + 1 < righti){StackPush(tmp, righti);StackPush(tmp, keyi + 1);}}//销毁栈StackDestroy(tmp);
}
3. 快排优化
3. 1 快排性能的关键点分析:
决定快排性能的关键点是每次单趟排序后,key
对数组的分割,如果每次选key
基本二分居中,那么快排的递归树就是颗均匀的满二叉树,性能最佳。但是实践中虽然不可能每次都是二分居中,但是性能也还是可控的。但是如果出现每次选到最小值/最大值,划分为0个和N-1的子问题时,时间复杂度为O(N^2),数组序列有序时就会出现这样的问题,我们可以用三数取中(选取3个数,把大小在中间那个作为基准值)或者随机选key(使用随机数选基准值,这两种办法实际的效率提升不是很高,不单独介绍了)解决这个问题,但是现在还是有一些场景没解决,比如数组中有大量重复数据时,比如以下代码:
// 数组中有多个跟key相等的值
int a[] = { 6,1,7,6,6,6,4,9 };
int a[] = { 3,2,3,3,3,3,2,3 };
// 数组中全是相同的值
int a[] = { 2,2,2,2,2,2,2,2 };
这种时候快排的效率就会急剧下降,完全不如其他的较快的排序算法(如堆排序)。
3. 1 三路划分
当面对有大量跟key
相同的值时,三路划分的核心思想有点类似hoare的左右指针和lomuto的前后指针的结合。核心思想是把数组中的数据分为三段【比key小的值】【跟key相等的值】【比key大的值】,所以叫做三路划分算法。结合步骤,理解一下实现思想:
key
默认取left
位置的值。left
指向区间最左边,right
指向区间最后边,cur
指向left+1
位置。cur
遇到比key
小的值后跟left
位置交换,换到左边,left++,cur++
。cur
遇到比key
大的值后跟right
位置交换,换到右边,right--
。cur
遇到跟key
相等的值后,cur++
。- 直到
cur > right
结束
int a[]={6,1,7,6,6,6,4,9}; //前
int a[]={1,4,6,6,6,6,9,7}; //后int b[]={6,6,6,6,6,6,6,6}; //前
int b[]={6,6,6,6,6,6,6,6}; //后
代码:
void QuickSortTreeWay(int* a, int left, int right)
{if (left >= right)return;int begin = left;int cur = left + 1;int end = right;int key = a[left];while (cur <= right){if (a[cur] > key){//把大于key的数放到右边Swap(&a[cur], &a[end--]); //注意由于不确定被换过来的值的大小,cur不能直接++}else if (a[cur] == key){cur++; //等于key的先不管}else{Swap(&a[cur++], &a[begin++]); //小于key的放到begin的左边}}//递归时,begin-end的数不需要再调整QuickSortTreeWay(a, left, begin - 1);QuickSortTreeWay(a, end + 1, right);
}
3. 2 introsort自省排序
introsort是由David Musser在1997年设计的排序算法,C++ sgi STLsort中就是用的introspectivesort(内省排序)思想实现的。
内省排序可以认为不受数据分布的影响,无论什么原因划分不均匀导致递归深度太深,它都会转换为堆排painkiller,而堆排不受数据分布影响,具体可以看下面代码。
三路划分针对有大量重复数据时效率很好,其他场景就一般,而自省排序在任何场景都有优异的性能。
introsort是introspective sort采用了缩写,他的名字其实表达了它的实现思路,也就是进行自我侦测和反省,快排递归深度太深(sgi stl中使用的是深度为2倍排序元素数量的对数值)那就说明在这种数据序列下,选key
出现了问题,性能在快速退化,那么就不要再进行快排分割递归了,改换为堆排序进行排序。
代码:(堆排序与插入排序不再赘述)
void IntroSort(int* a, int left, int right, int depth, int defaultDepth)
{if (left >= right)return;// 数组⻓度小于16的小数组,换为插入排序,简单递归次数if (right - left + 1 < 16){InsertSort(a + left, right - left + 1);return;}// 当深度超过2*logN时改⽤堆排序if (depth > defaultDepth){HeapSort(a + left, right - left + 1);return;}depth++; //深度++,方便判断int begin = left;int end = right;// 随机选key,相较于直接将第一个元素作为key,有一定的优势int randi = left + (rand() % (right - left + 1));Swap(&a[left], &a[randi]); //把选中的key放到left上,和之前的快排尽可能保持相似//前后指针法int prev = left;int cur = prev + 1;int keyi = left;while (cur <= right){if (a[cur] < a[keyi] && ++prev != cur){Swap(&a[prev],&a[cur]);}++cur;}Swap(&a[prev], &a[keyi]);keyi = prev;// [begin, keyi-1] keyi [keyi+1, end]IntroSort(a, begin, keyi - 1, depth, defaultDepth);IntroSort(a, keyi + 1, end, depth, defaultDepth);
}
void QuickSort(int* a, int left, int right)
{int depth = 0;int logn = 0;int N = right - left + 1;//计算lognfor (int i = 1; i < N; i *= 2){logn++;}// introspective sort -- 自省排序IntroSort(a, left, right, depth, logn * 2);
}int* sortArray(int* nums, int numsSize, int* returnSize) {srand(time(0));QuickSort(nums, 0, numsSize - 1);*returnSize = numsSize;return nums;
}
谢谢你的阅读,喜欢的话来个点赞收藏评论关注吧!
我会持续更新更多优质文章
相关文章:

【数据结构初阶】排序算法(中)快速排序专题
文章目录 1. 快排主框架2. 快排的不同实现2. 1 hoare版本2. 2 挖坑法2. 3 lomuto前后指针法2. 4 快排的非递归版本 3. 快排优化3. 1 快排性能的关键点分析:3. 1 三路划分3. 2 introsort自省排序 1. 快排主框架 快速排序是Hoare于1962年提出的一种二叉树结构的交换排序方法。 其…...

Redis缓存双写一致性笔记(上)
Redis缓存双写一致性是指在将数据同时写入缓存(如Redis)和数据库(如MySQL)时,确保两者中的数据保持一致性。在分布式系统中,缓存通常用于提高数据读取的速度和减轻数据库的压力。然而,当数据更新…...

PCB基础
一、简介 PCB:printed circuit board,印刷电路板 主要作用:传输信号、物理支撑、提供电源、散热 二、分类 2.1 按基材分类 陶瓷基板:包括氧化铝、氮化铝、碳化硅基板等,具有优异的导热性,适用于高温和高…...

PostgreSQL 17:新特性与性能优化深度解析
目录 引言核心新特性 块级别增量备份与恢复逻辑复制槽同步参数SQL/JSON的JSON_TABLE命令PL/pgSQL支持数组%TYPE和%ROWTYPE 性能优化 IO合并读取性能参数真空处理过程的内存管理改进写前日志(WAL)锁的改进 升级建议结语 引言 PostgreSQL 17版本于2024年…...

[Linux#58][HTTP] 自己构建服务器 | 实现网页分离 | 设计思路
目录 一. 最简单的HTTP服务器 二.服务器 2.0 Protocol.hpp httpServer.hpp 子进程的创建和退出 子进程退出的意义 父进程关闭连接套接字 httpServer.cc argc (argument count) argv (argument vector) 三.服务器和网页分离 思考与补充: 一. 最简单的HTT…...

7.MySQL内置函数
目录 日期函数时间函数字符串函数数学函数其他函数 日期函数 函数名称描述current_date()当前日期current_time()当前时间current_timesamp()当前时间戳date(datetime)返回datetime参数的日期部分date_add(date, interval d_value_tyep)在date中添加日期函数或时间。interval后…...

如何快速自定义一个Spring Boot Starter!!
目录 引言: 一. 我们先创建一个starter模块 二. 创建一个自动配置类 三. 测试启动 引言: 在我们项目中,可能经常用到别人的第三方依赖,又是引入依赖,又要自定义配置,非常繁琐,当我们另一个项…...

【音视频】ffmpeg其他常用过滤器filter实现(6-4)
最近一直在研究ffmpeg的过滤器使用,发现挺有意思的,这里列举几个个人感觉比较有用的过滤器filter,如下是代码实现,同样适用于命令行操作: 1、视频模糊:通过boxblur可以将画面进行模糊处理,第1个…...

云栖3天,云原生+ AI 多场联动,新产品、新体验、新探索
云栖3天,云原生 AI 20场主题分享,三展互动,为开发者带来全新视听盛宴 2024.9.19-9.21 云栖大会 即将上演“云原生AI”的全球盛会 展现最新的云计算技术发展与 AI技术融合之下的 “新探索” 一起来云栖小镇 见证3天的云原生AI 前沿探索…...

jackson对于对象序列化的时候默认空值和手动传入的null的不同处理
Jackson 在序列化对象时如何处理默认的空值和手动传入的 null,其实归结于它的序列化机制和注解配置。默认情况下,Jackson 不区分 手动设置的 null 和 对象中字段的默认空值,但可以通过配置来改变其行为。具体细节如下: 1. 默认行为…...

L8打卡学习笔记
🍨 本文为🔗365天深度学习训练营 中的学习记录博客🍖 原作者:K同学啊 SVM与集成学习 SVMSVM线性模型SVM非线性模型SVM常用参数 集成学习随机森林导入数据查看数据信息数据分析随机森林模型预测结果结果分析 个人总结 SVM 超平面&…...

VBA解除Excel工作表保护
Excel工作表保护解除 工作表保护后无法编辑内容,可能是密码忘记,不可暴力破解隐私 1 打开需的Excel 2 Alt F11 打开代码编辑,点击任意代码编辑项,将如下代码复制,并运行。 Public Sub GetWorkbookPassword()Dim w1 A…...

bash: unzip: 未找到命令,sudo: nano:找不到命令
在 Ubuntu/Debian 系统上 打开终端并运行以下命令: sudo apt update sudo apt install unzip在 CentOS/RHEL 系统上 打开终端并运行以下命令: sudo yum install unzip在 macOS 上 如果您使用的是 macOS,可以使用 Homebrew 安装 unzip&#…...

tauri开发配置文件和文件夹访问路径问题
文件夹没权限:Unhandled Promise Rejection: path not allowed on the configured scope: /Users/song/Library/Application Support/com.pakeplus.app/assets/default.png 没有文件夹,需要先创建:Unhandled Promise Rejection: path: /Users…...

【web安全】——信息收集
一、收集域名信息 1.1域名注册信息 工具:站长之家 whois查询 SEO综合查询 1.2子域名收集 原理:字典爆破,通过字典中的各种字符串与主域名拼接,尝试访问。 站长之家 直接查询子域名 ip138.com https://phpinfo.me/domain/ …...

赵长鹏今日获释,下一步会做什么?币安透露2024年加密货币牛市的投资策略!
中国时间2024年9月28日,加密货币行业的风云人物赵长鹏(Changpeng Zhao,简称CZ)终于从监狱获释。他因在担任币安首席执行官期间未能有效执行反洗钱(AML)计划而被判刑四个月。赵长鹏的获释引发了广泛关注,不仅因为他是全…...

SpringMVC之ContextHolder
员工不必为自己的弱点而太多的忧虑,而是要大大地发挥自己的优点,使自己充满自信,以此来解决自己的压抑问题。我自己就有许多地方是弱项,常被家人取笑小学生水平,若我全力以赴去提升那些弱的方面,也许我就做…...

什么是SQL注入?
SQL注入是一种安全漏洞,攻击者通过在应用程序的输入字段中插入恶意SQL代码,从而操控数据库。此类攻击通常利用应用程序未对用户输入进行适当验证和清理的弱点。 工作原理: 输入字段:攻击者在登录表单或搜索框等输入区域插入恶意…...

混合密码系统——用对称密钥提高速度,用公钥密码保护会话密钥
混合密码系统(Hybrid Cryptosystem)是一种结合了多种密码学技术和算法的加密方案,旨在充分利用不同密码算法的优势,以提供更强大的安全性、更高的效率或更好的功能特性。以下是对混合密码系统的详细解释: 组成要素 对…...

Three.js粒子系统与特效
目录 粒子系统基础常见粒子系统特效粒子系统基础 基础的粒子系统 使用THREE.ParticleSystem和THREE.ParticleBasicMaterial实现: // 导入Three.js库 import * as THREE from three...

Tableau数据可视化入门
目录 一、实验名称 二、实验目的 三、实验原理 四、实验环境 五、实验步骤 1、Tableau界面引导 2、数据来源 3、数据预处理操作 4、制作中国各个地区的利润图表 4.1条形图 4.2气泡图 5、制作填充地球图 一、实验名称: 实验一:Tableau数据可视…...

Linux云计算 |【第四阶段】RDBMS1-DAY2
主要内容: 常用函数(函数分类1:单行、分组;函数分类2:字符、数学、日期、流程控制)、分组查询group by、连接查询 一、常用函数 1. 按使用方式分类 ① 单行函数 单行函数(Scalar Functions&…...

后台监控中的云边下控耗时、边缘采集耗时 、云边下控量
云边下控耗时:指云端控制边缘设备的时间,从云端下发指令到边缘设备响应完成的时间。该指标反映了云端控制边缘设备的效率和响应速度。 边缘采集耗时:指边缘设备采集数据到云端处理完成的时间,包括数据采集、传输、处理等环节。该…...

【学习笔记】手写 Tomcat 四
目录 一、Read 方法返回 -1 的问题 二、JDBC 优化 1. 创建配置文件 2. 创建工具类 3. 简化 JDBC 的步骤 三、修改密码 优化返回数据 创建修改密码的页面 注意 测试 四、优化响应动态资源 1. 创建 LoginServlet 类 2. 把登录功能的代码放到 LoginServlet 类 3. 创…...

探索基因奥秘:汇智生物如何利用组蛋白甲基化修饰测序技术革新农业植物基因组研究?
引言: 随着生物医学技术的不断进步,我们对生命奥秘的探索越来越深入。在众多的生物技术中,表观组学分析技术逐渐成为研究的热点。本文将带您走进汇智生物,了解他们如何利用DNA亲和纯化测序技术(DAP-seq)推…...

二叉搜索树的介绍、模拟实现二叉搜索树、leetcode---根据二叉树创建字符串、leetcode---二叉树的最近公共祖先等的介绍
文章目录 前言一、二叉搜索树的介绍二、模拟实现二叉搜索树三、leetcode---根据二叉树创建字符串四、leetcode---二叉树的最近公共祖先总结 前言 二叉搜索树的介绍、模拟实现二叉搜索树、leetcode—根据二叉树创建字符串、leetcode—二叉树的最近公共祖先等的介绍 一、二叉搜索…...

人工智能的基本概念与发展历程
一、人工智能的基本概念与发展历程 人工智能是研究、开发用于模拟、延伸和扩展人的智能的理论、方法、技术及应用系统的一门技术科学。它涵盖了机器人技术、语言识别、图像识别、自然语言处理和专家系统等众多领域。自20世纪30年代数理逻辑的形式化和智能可计算思想开始构建计…...

【IPV6从入门到起飞】5-6 IPV6+Home Assistant(ESPHome+ESP-cam)实时监控
5-6 IPV6Home Assistant[ESPHomeESP-cam]实时监控 1、背景2、ESPHome 安装2-1 ESPHome 简述2-2 安装 3、创建ESP32-CAM设备4、编辑yaml配置4-1 找到合适的配置4-2 修改配置4-3 验证配置4-4 编译项目 5、烧录固件6、绑定设备7、效果实现 1、背景 在前面我们已经实现了数据采集与…...

生成式AI的未来
随着生成式AI技术的不断进步,关于其未来发展方向的讨论也愈发激烈。究竟生成式AI的未来是在对话系统(Chat)中展现智慧,还是在自主代理(Agent)中体现能力?这一问题不仅涉及技术实现的可能性&…...

实用好软-----电脑端 从视频中导出音频的方便工具
最近想从一个视频中导出个音乐,百度找很多没有合适的工具。最终找到了一款很方便 而且操作超级简单的工具。打开这个工具后只需要把需要导出音乐的视频拖进窗口里就会自动导出音乐mp3。方便小巧,而且音频效果还是不错的。 一些视频转换成音频文件&#x…...