八大排序超详解(动图+源码)

💓博主个人主页:不是笨小孩👀
⏩专栏分类:数据结构与算法👀 刷题专栏👀 C语言👀
🚚代码仓库:笨小孩的代码库👀
⏩社区:不是笨小孩👀
🌹欢迎大家三连关注,一起学习,一起进步!!💓

在这里插入图片描述

排序算法

  • 排序的概念
  • 插入排序
  • 希尔排序
  • 选择排序
  • 冒泡排序
  • 堆排序
  • 快速排序
  • 归并排序
  • 计数排序

排序的概念

排序:所谓排序,就是使一串记录,按照其中的某个或某些关键字的大小,递增或递减的排列起来的操作。
稳定性:假定在待排序的记录序列中,存在多个具有相同的关键字的记录,若经过排序,这些记录的相对次 序保持不变,即在原序列中,r[i]=r[j],且r[i]在r[j]之前,而在排序后的序列中,r[i]仍在r[j]之前,则称这种排 序算法是稳定的;否则称为不稳定的。
内部排序:数据元素全部放在内存中的排序。
外部排序:数据元素太多不能同时放在内存中,根据排序过程的要求不能在内外存之间移动数据的排序。

常见的排序算法:

在这里插入图片描述
除了这些排序以外,该有一个很奇怪的排序,计数排序,我们待会将,我们接下来,就从第一个排序开始:

插入排序

插入排序的思想很简单就是把待排序的记录按其关键码值的大小逐个插入到一个已经排好序的有序序列中,直到所有的记录插入完为止,得到一个新的有序序列 。插入排序可以理解为就是我们打扑克牌摸排的过程,摸一张排,依次比较然后将它插入的合适的位置。

我们看图:
在这里插入图片描述
这个排序很简单,根据图我们就可以把第一个数据当成有序的数据,然后后面的数据依次插入,直到将数据插入完,这样就有序了。

代码如下:

void InsertSort(int* arr, int n)
{for (int i = 0; i < n-1; i++){//end表示有序数据的最后一数的下标int end = i;//tmp保存需要插入的值int tmp = arr[end+1]; while (end >= 0){//依次比较如果比需要插入的数大,就往后移,否则就跳出循环if (arr[end] > tmp){arr[end + 1] = arr[end];end--;}else{break;}}//跳出循环后将需要插入的数据放到end后面的位置arr[end + 1] = tmp;}
}

总结:

  1. 元素集合越接近有序,直接插入排序算法的时间效率越高
  2. 时间复杂度:O(N^2)
  3. 空间复杂度:O(1),它是一种稳定的排序算法
  4. 稳定性:稳定

希尔排序

希尔排序法又称缩小增量法。希尔排序法的基本思想是:先选定一个整数gap,把待排序文件中所有记录分成gap个组,所有距离为gap的记录分在同一组内,并对每一组内的记录进行排序。然后,重复上述分组和排序的工作。当到达=1时,所有记录在统一组内排好序。

插入排序第一步我们需要预排序
预排序后插入排序就很快了,直接使用插入排序就可以了。但是当我们的gap=1是,希尔排序就相当于插入排序了。这里gap可以取很多值,但是要保证最后一次gap=1.

在这里插入图片描述

代码如下:

void ShellSort(int* arr, int n)
{int gap = n;//要进行多趟排序while (gap > 1){//+1是为了保证gap最后一次等于1gap = gap / 3 + 1;for (int i = 0; i < n - gap; i++){//每次分别排gap组数据,每组间隔gap个数据,一共gap组int end = i;int tmp = arr[i + gap];while (end >= 0){if (arr[end] > tmp){arr[end + gap] = arr[end];end -= gap;}else{break;}}arr[end + gap] = tmp;}}
}

总结:

  1. 希尔排序是对直接插入排序的优化。
  2. 当gap > 1时都是预排序,目的是让数组更接近于有序。当gap == 1时,数组已经接近有序的了,这样就 会很快。这样整体而言,可以达到优化的效果。我们实现后可以进行性能测试的对比。
  3. 希尔排序的时间复杂度不好计算,因为gap的取值方法很多,导致很难去计算,因此在好些树中给出的 希尔排序的时间复杂度都不固定:我们记住大约就等于O(N^1.3)
  4. 稳定性:不稳定

选择排序

选择排序是每一次从待排序的数据元素中选出最小(或最大)的一个元素,存放在序列的起始位置,直到全部待排序的数据元素排完 。

在这里插入图片描述

我们这里实现的是依次找大的,然后放到最后面,和图不太一样,但是思想都一样。

代码如下:

void SelectSort(int* arr, int n)
{int end = n - 1;while (end>0){//每次初始化最大在0处,防止maxi到已经在排好序的位置int maxi = 0;for (int i = 0; i <= end; i++){if (arr[i] > arr[maxi]){maxi = i;}}//找到后和最后一个数据交换Swap(&arr[maxi], &arr[end]);end--;}
}

选择排序我们这里可以优化一下,就是每次选出最小的和最大的,然后最小的放到左边,最大的放到右边,然后接着找剩余数据的最大最小,直到结束。

代码如下:

void SelectSort(int* arr, int n)
{int begin = 0;int end = n - 1;while (begin < end){int maxi = begin;int mini = begin;//依次找大和找小for (int i = begin; i <= end; i++){if (arr[mini] > arr[i]){mini = i;}if (arr[maxi] < arr[i]){maxi = i;}}//找到后将大的数据放到后面Swap(&arr[maxi], &arr[end]);//防止最小的数据在最后面被换走了,及时修正if (mini == end){mini = maxi;}Swap(&arr[mini], &arr[begin]);begin++;end--;}
}

总结:

  1. 直接选择排序思考非常好理解,但是效率不是很好。实际中很少使用
  2. 时间复杂度:O(N^2)
  3. 空间复杂度:O(1)
  4. 稳定性:不稳定

冒泡排序

冒泡排序大多数人应该都知道,它的基本思想就是依次比较,将大的数据冒到最后然后重复前面的过程,就可以完成排序。

在这里插入图片描述
代码如下:

void BubbleSort(int* arr, int n)
{for (int i = 0; i < n - 1; i++){int flag = 1;for (int j = 1; j < n - i; j++){if (arr[j] < arr[j - 1]){Swap(arr + j, arr + j - 1);flag = 0;}}if (flag == 1){break;}}
}

总结:

  1. 冒泡排序是一种非常容易理解的排序
  2. 时间复杂度:O(N^2)
  3. 空间复杂度:O(1)
  4. 稳定性:稳定

堆排序

堆排序前面已经讲过一次了,这里就不做过多的解释了,想要详细了解请戳。
这里是引用

总结:

  1. 堆排序使用堆来选数,效率就高了很多。
  2. 时间复杂度:O(N*logN)
  3. 空间复杂度:O(1)
  4. 稳定性:不稳定

快速排序

快速排序是Hoare于1962年提出的一种二叉树结构的交换排序方法,其基本思想为:任取待排序元素序列中
的某元素作为基准值,按照该排序码将待排序集合分割成两子序列,左子序列中所有元素均小于基准值,右
子序列中所有元素均大于基准值,然后最左右子序列重复该过程,直到所有元素都排列在相应位置上为止。

我们每次可以将数组划分为两部分,keyi是那个选出来的数的最终的下标,然后第一次排序后就是[left,keyi-1],keyi,[keyi+1,right],我们每一趟要保证的是keyi左边的数据逗比key小,右边的都比它大,然后左区间重复这个操作,右区间也重复这个操作,这就有点像二叉树的前序遍历,直到每个区间只剩下一个值,或者区间不存在时,我们结束递归。

快排的整体框架:

void QuickSort(int* arr, int left, int right)
{if (left >= right){return;}int keyi = partSort(arr,left,right);QuickSort(arr,left, keyi - 1);QuickSort(arr,keyi + 1, right);}

这里的partSort就是我们的单趟排序,我们讲三种方法:

  1. hoare版本
    在这里插入图片描述

我们需要两个指针,一个从左边开始走,一个从右边开始走,再定义一个key,和keyi,keyi保存key的小标,如果左边左key就右边先走,右边左key就左边先走,,然后左边找比key大的数,右边找比key小的数,找到后交换,然后接着走,直到相遇,然后把相遇的位置和key交换一下。

为什么左边做key右边先走呢?

因为这样可以保证相遇的位置一定是比key小等于的数,相遇无非就是两种情况,L遇到R,R遇到L,如果是L遇到R,我们让右边先走,R停下的位置一定是比key小的数,如果是R遇L,假设数组中的数都比key大,所以key遇到L是就是等于key,所以我们左边做key让右边先走,是可以保证相遇位置一定比key小的。

代码如下:

int partSort1(int* arr, int left, int right)
{int keyi = left;while (left < right){//右边找小while (left < right && arr[right] >= arr[keyi]){right--;}//左边找大while (left < right && arr[left] <= arr[keyi]){left++;}Swap(&arr[left], &arr[right]);}Swap(&arr[left], &arr[keyi]);return left;
}
  1. 挖坑法
    在这里插入图片描述

我们还是将左边做key,然后保存它的值,然后它就是一个坑,还是两个指针,由于左边有一个坑,所以右边就要找小的数来填这个坑,然后将右边的那个位置变成新的坑,然后左边找大,找到后接着填坑,更新坑的位置,L和R一定有一个是坑,所以,当他们相遇时,那个位置一定是坑,然后将key放进去即可。

代码如下:

int partSort2(int* arr, int left, int right)
{int hole = arr[left];int keyi = left;while (left < right){while (left < right && arr[right] >= hole){right--;}arr[keyi] = arr[right];keyi = right;while (left < right && arr[left] <= hole){left++;}arr[keyi] = arr[left];keyi = left;}arr[keyi] = hole;return keyi;
}
  1. 前后指针法
    在这里插入图片描述

定义两个指针一个prev一个cur,cur用来遍历数组,还是用左边的值来做key,然后将cur找到比key小的值就和++prev位置的数交换直到遍历结束,然后再把prev位置的值可key交换即可。

代码如下:

int partSort3(int* arr, int left, int right)
{int keyi = left;int cur = left + 1;int prev = left;while (cur <= right){if (arr[cur] < arr[keyi]&&++prev!=cur){Swap(&arr[prev], &arr[cur]);}cur++;}Swap(&arr[prev], &arr[keyi]);return prev;
}

快速排序的非递归版本

非递归的话,我们用队列和栈都是可以的,但是想要模仿递归的路径的话我们就要使用栈,我们先把数组的整个区间放到栈里面,然后在进行一趟排序后,我们把排出来的左区间和右区间入栈,由于先走左边,所以就要先把右边的区间压栈,然后依次进行,只要区间存在,我们就压,只要栈不为空,就代表一直有区间未处理。所以我们就一直重复操作,当然单趟排序的话,用上面的那种方法都可以。

代码如下:

void QuickSortNonR(int* arr, int left, int right)
{Stack st;StackInit(&st);StackPush(&st,right);StackPush(&st, left);while (!StackEmpty(&st)){int begin = StackTop(&st);StackPop(&st);int end = StackTop(&st);StackPop(&st);int keyi = partSort1(arr, begin, end);if (keyi + 1 < end){StackPush(&st, end);StackPush(&st, keyi + 1);}if (keyi - 1 > begin){StackPush(&st, keyi - 1);StackPush(&st, begin);}}
}

快速排序,还可以优化,他的效率和选的key的关系很大,所以我们有种方法叫做三数取中,左边的值、右边的值、中间的值,然都找到这三个数中间的数,把他换到左边,就可以了。

总结:

  1. 快速排序整体的综合性能和使用场景都是比较好的,所以才敢叫快速排序
  2. 时间复杂度:O(N*logN)
  3. 空间复杂度:O(logN)
  4. 稳定性:不稳定

归并排序

归并排序(MERGE-SORT)是建立在归并操作上的一种有效的排序算法,该算法是采用分治法(Divide and Conquer)的一个非常典型的应用。将已有序的子序列合并,得到完全有序的序列;即先使每个子序列有序,再使子序列段间有序。若将两个有序表合并成一个有序表,称为二路归并。

归并排序需要将区间分为两部分,然后两部分都要有序才能归并,那左右区间又可以分割,以此类推,当区间只有一个数的时候就可以认为有序,这时我们可以走一个类似与二叉树后序遍历的思路,我们想归并左右区间,但是左右区间都无序,我们就递归左边让左边有序,在递归右边让右边有序,最后再左右归并,就可以排好了。

在这里插入图片描述

我们这里就需要一个数组来保存我们归并的值,我们取两段区间的值依次比较,拿小的尾插到tmp数组中,等归并完再拷回原数组,即可。

代码如下:

void _MergeSort(int* arr, int left, int right,int* tmp)
{if (left >= right){return;}//分割区间int mid = (left + right) / 2;_MergeSort(arr, left, mid, tmp);_MergeSort(arr, mid+1, right, tmp);int begin1 = left, end1 = mid;int begin2 = mid + 1, end2 = right;int k = left;while (begin1 <= end1 && begin2 <= end2){//选小的来尾插if (arr[begin1] <= arr[begin2]){tmp[k++] = arr[begin1++];}else{tmp[k++] = arr[begin2++];}}//不管哪个没有拷贝完,因为区间是有序地,直接尾插就可以while (begin1 <= end1){tmp[k++] = arr[begin1++];}while (begin2 <= end2){tmp[k++] = arr[begin2++];}//拷贝回原数组memcpy(arr + left, tmp + left, (right - left + 1) * sizeof(int));}
void MergeSort(int* arr, int left, int right)
{int* tmp = (int*)malloc(sizeof(int) * (right - left + 1));//不能在这个函数中递归,不然每次都要开辟数组_MergeSort(arr, left, right,  tmp);free(tmp);
}

归并排序的非递归版本

我们会发现归并排序用队列和栈都用不了,但是我们可以使用循环来解决它,首先我们需要一个gap来记录每组归并的数据有几个,然后控制区间,来进行归并。
但是在归并中,会存在很多的越界问题,比如end1越界了,或者begin1越界了,但是这两种情况我们都很好处理,等处理到这种错误时我们可以看成只剩下一组数据,就可以不用动,放在原数就好,等待下一轮归,直接break跳出就可以,还有一种情况是end2越界了,这时还有一部分数据需要归并,那我们就调整end2为n-1就可以了。

在这里插入图片描述

代码如下:

void MergeSortNonR(int* arr, int n)
{int* tmp = (int*)malloc(sizeof(int) * n);int gap = 1;//gap表示每组数据的长度while (gap < n){for (int i = 0; i < n; i += 2 * gap){//控制区间int begin1 = i, end1 = i + gap - 1;int begin2 = i + gap, end2 = i + 2 * gap - 1;int k = i;//越界即使调整或退出if (end1 >= n || begin2 >= n){break;}if (end2 >= n){end2 = n - 1;}while (begin1 <= end1 && begin2 <= end2){if (arr[begin1] <= arr[begin2]){tmp[k++] = arr[begin1++];}else{tmp[k++] = arr[begin2++];}}while (begin1 <= end1){tmp[k++] = arr[begin1++];}while (begin2 <= end2){tmp[k++] = arr[begin2++];}//每次归并完拷贝会原数组memcpy(arr+i, tmp+i, sizeof(int)*(end2-i+1));}gap *= 2;}free(tmp);
}

总结:

  1. 归并的缺点在于需要O(N)的空间复杂度,归并排序的思考更多的是解决在磁盘中的外排序问题。
  2. 时间复杂度:O(N*logN)
  3. 空间复杂度:O(N)
  4. 稳定性:稳定

计数排序

计数排序是非常奇怪的排序,它不需要比较任何数据,他开辟一个和最大值一样的数组,然后将该数组初始化为0,然后原遍历数组,将原数组的值对应到我们开辟数组的下标,出现一次我们就++该位置,然后统计每个位置出现的次数,然后在依次拷贝回原数组,就可以了。
但是如果数据很大很集中,我们就没必要开那么大,会很浪费,我们需要找到最大值和最小值,然后使用相对位置,就可以了,每个数对应到减去最小值的那个小下标,这样我们数组也不用开的很大。

代码如下:

void CountSort(int* arr, int n)
{int min = arr[0];int max = arr[0];//找最大值和最小值for (int i = 0; i < n; i++){if (max < arr[i]){max = arr[i];}if (min > arr[i]){min = arr[i];}}//计算区间方便开数组int c =max-min+1;int* nums = (int*)malloc(sizeof(int) * c);memset(nums, 0, c * sizeof(int));//统计for (int i = 0; i < n; i++){nums[arr[i]-min]++;}int k = 0;//拷贝回原数组for (int i = 0; i < c; i++){while (nums[i]--){arr[k++] = i+min;}}free(nums);
}

总结:

  1. 计数排序在数据范围集中时,效率很高,但是适用范围及场景有限。
  2. 时间复杂度:O(MAX(N,范围))
  3. 空间复杂度:O(范围)
  4. 稳定性:稳定

各大排序的比较:
在这里插入图片描述
今天的分享就到这里感谢大家的关注和支持!

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

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

相关文章

【Linux】IO多路转接——select接口

目录 I/O多路转接之select select初识 select函数 socket就绪条件 select基本工作流程 select服务器 select的优点 select的缺点 select的适用场景 I/O多路转接之select select初识 select是系统提供的一个多路转接接口。 select系统调用可以让我们的程序同时监视多…

拦截器和过滤器的区别

&#x1f600;前言 本篇博文是关于拦截器VS 过滤器的分享&#xff0c;希望你能够喜欢&#x1f60a; &#x1f3e0;个人主页&#xff1a;晨犀主页 &#x1f9d1;个人简介&#xff1a;大家好&#xff0c;我是晨犀&#xff0c;希望我的文章可以帮助到大家&#xff0c;您的满意是我…

【机器学习4】构建良好的训练数据集——数据预处理(一)处理缺失值及异常值

数据预处理 &#x1f4ab;数据预处理的重要性&#x1f4ab;处理缺失值⭐️识别表格中的数据⭐️计算每列缺失值的数量⭐️删除含有缺失值的样本或特征⭐️填充缺失值 &#x1f4ab;处理异常值⭐️异常值的鉴别⭐️异常值的处理 &#x1f4ab;将数据集划分为训练数据集和测试数据…

Jmeter-压力测试工具

文章目录 Jmeter快速入门1.1.下载1.2.解压1.3.运行 2.快速入门2.1.设置中文语言2.2.基本用法 Jmeter快速入门 1s内发送大量请求&#xff0c;模拟高QPS&#xff0c;用以测试网站能承受的压力有多大 Jmeter依赖于JDK&#xff0c;所以必须确保当前计算机上已经安装了JDK&#xff0…

使用ip2region获取客户端地区

目录 从gitee拉取ip2region.xdb资源文件 写测试类 注意要写对资源路径 本地测试结果 ​编辑 远端测试结果 从gitee拉取ip2region.xdb资源文件 git clone https://gitee.com/lionsoul/ip2region.git 将xdb放入resources资源文件夹 引入依赖 <dependency><groupId&…

Vue响应式数据的原理

在 vue2 的响应式中&#xff0c;存在着添加属性、删除属性、以及通过下标修改数组&#xff0c;但页面不会自动更新的问题。而这些问题在 vue3 中都得以解决。 vue3 采用了 proxy 代理&#xff0c;用于拦截对象中任意属性的变化&#xff0c;包括&#xff1a;属性的读写、属性的…

Pycharm社区版连接WSL2中的Mysql8.*

当前时间2023.08.13&#xff0c;Windows11中默认的WSL版本已经是2了&#xff0c;在WSL2中默认的Ubuntu版本已经是22.04&#xff0c;而Ubuntu22.04中默认的Mysql版本已经是8.*。 Wsl 2 中安装mysql WSL2中安装Mysql的方法参考自微软官方文档【开始使用适用于 Linux 的 Windows …

《论文阅读12》RandLA-Net: Efficient Semantic Segmentation of Large-Scale Point Clouds

一、论文 研究领域&#xff1a;全监督3D语义分割&#xff08;室内&#xff0c;室外RGB&#xff0c;kitti&#xff09;论文&#xff1a;RandLA-Net: Efficient Semantic Segmentation of Large-Scale Point Clouds CVPR 2020 牛津大学、中山大学、国防科技大学 论文链接论文gi…

C++储备

一、类的 三大特性 封装&#xff0c;继承&#xff0c;多态 二、虚函数 为啥要用到虚函数 C虚函数详解_Whitesad_的博客-CSDN博客 三、函数重载 四、封装的保护权限 1.public 成员类内&#xff0c;内外都可以访问 2.protected 成员&#xff0c;类内可以访问&#xff0c…

两只小企鹅(Python实现)

目录 1 和她浪漫的昨天 2 未来的旖旎风景 3 Python完整代码 1 和她浪漫的昨天 是的,春天需要你。经常会有一颗星等着你抬头去看&#xff1b; 和她一起吹晚风吗﹖在春天的柏油路夏日的桥头秋季的公园寒冬的阳台&#xff1b; 这世界不停开花&#xff0c;我想放进你心里一朵&am…

【软件工程】软件测试

软件测试的对象 软件程序文档 测试对象&#xff1a;各个阶段产生的源程序和文档。 软件测试的目的 基于不同的立场&#xff0c;对软件测试的目的存在着两种完全对立的观点。 &#xff08;1&#xff09;一种观点是通过测试暴露出软件中所包含的故障和缺陷(从用户的角度)&#xf…

语聚AI公测发布,大语言模型时代下新的生产力工具

语聚AI 公测发布 距离语聚AI内测上线已经过去近1个月。 这期间&#xff0c;我们共邀请了近百位资深用户与行业专家加入语聚AI产品体验。通过大家的热情参与积极反馈&#xff0c;我们不断优化并完善了语聚AI的功能与使用体验。 经过研发团队不懈的努力&#xff0c;今天语聚AI终…

深度学习实战基础案例——卷积神经网络(CNN)基于SqueezeNet的眼疾识别|第1例

文章目录 前言一、数据准备1.1 数据集介绍1.2 数据集文件结构 二、项目实战2.1 数据标签划分2.2 数据预处理2.3 构建模型2.4 开始训练2.5 结果可视化 三、数据集个体预测 前言 SqueezeNet是一种轻量且高效的CNN模型&#xff0c;它参数比AlexNet少50倍&#xff0c;但模型性能&a…

实战项目:基于主从Reactor模型实现高并发服务器

项目完整代码仿mudou库one thread one loop式并发服务器实现: 仿muduo库One Thread One Loop式主从Reactor模型实现⾼并发服务器&#xff1a;通过模拟实现的⾼并发服务器组件&#xff0c;可以简洁快速的完成⼀个⾼性能的服务器搭建。并且&#xff0c;通过组件内提供的不同应⽤层…

uniapp的UI框架组件库——uView

在写uniapp项目时候&#xff0c;官方所推荐的样式库并不能满足日常的需求&#xff0c;也不可能自己去写相应的样式&#xff0c;费时又费力&#xff0c;所以我们一般会去使用第三方的组件库UI&#xff0c;就像vue里我们所熟悉的elementUI组件库一样的道理&#xff0c;在uniapp中…

UVM学习知识点

UVM构建 include 和 import pkg区别.sv .svhhdl_top.sv和hvl_top.sv回顾uvm_config&#xff0c;以及自定义uvm_configverilog:parameter、defparam与 localparamtest_basebuild_phaseend_of_elaboration_phasefunction void configure_agentset_seqsend_of_elaboration_phaseuv…

Shell编程——弱数据类型的脚本语言快速入门指南

目录 Linux Shell 数据类型 变量类型 运算符 算术运算符 赋值运算符 拼接运算符 比较运算符 关系运算符 控制结构 顺序结构 条件分支结构 if 条件语句 case 分支语句 循环结构 for 循环 while 循环 until 循环 break 语句 continue语句 函数 函数定义 …

【数学建模】清风数模更新5 灰色关联分析

灰色关联分析综述 诸如经济系统、生态系统、社会系统等抽象系统都包含许多因素&#xff0c;系统整体的发展受各个因素共同影响。 为了更好地推动系统发展&#xff0c;我们需要清楚哪些因素是主要的&#xff0c;哪些是次要的&#xff0c;哪些是积极的&#xff0c;哪些是消极的…

@RequestHeader使用

RequestHeader 请求头参数的设置 GetMapping("paramTest/requestHeader")public String requestHeaderTest(RequestHeader("name") String name){return name;} 在Postman的Headers中添加请求头参数&#xff0c;不过貌似不能加中文

DMA技术

先总结: DMA是指外部设备不通过CPU而直接与系统内存交换数据的接口技术 主要工作是由DMA控制器来完成的. 下面开始正文 ---------------------------------------------------------------------------- 1、DMA由来 DMA(Direct Memory Access,直接存储器访问)。在&#xf…