常见的基于比较的排序算法是,常见的基于比较的排序算法有哪些
博客明星评选
在写作之前,我们离不开排序。比如一次考试后,我们需要根据成绩进行排名。比如我们去银行取钱,需要取号,等待。以上是排序在我们生活中的应用。数据结构的初始部分几乎完成了共享。今天分享完排序,后面有哈希表。希望大家坚持下去。今天我们讲的是基于比较的常用排序算法。在开始正式内容之前,我想说,排序最重要的是思想,而不是代码。我们可以把代码背下来,但是不懂原理还是不懂。下次遇到排序,你的大脑可能会骗你。
关于排序我们就不用说什么了。简单的理解就是把数字按升序或降序排序。关于排序在我们现实生活中的实际应用,有很多例子。比如我们用手机购物,可能会选择销量优先或者价格优先。这些是排序的实际应用。排序算法有很多种,这里我就说几种常用的。
排名主要从三个方面分析。
时间复杂度、空间复杂度、稳定性、稳定性,稳定性也是衡量排序算法的一个非常重要的指标。如果排序算法能够保证两个相等数据的相对位置在排序后不发生变化,我们称之为稳定排序算法。有些人可能会感到困惑。现在数值都一样了,如果改变相对位置会怎么样?在大多数情况下,对于具有相同值的元素,谁先出现并不重要。但是,在某些情况下,具有相同值的元素必须保持原始顺序。稍后,我们会发现,我们不仅对基本类型进行排序,还对自定义类型进行排序。
还有一个简单的方法来判断排序是否稳定。如果我们跳过号码交换,它一定是不稳定的。
排序算法有很多种。我们只需要看看最常见的排序算法。我个人认为排序可以分为比较排序和非比较排序。有些人可能会想,还有什么东西是不需要比较就可以排序的?你在开玩笑吗?其实是存在的,下面这个基数顺序就是其中之一。至于我们今天说的,都是基于比较的排序算法。
基于比较的排序不是基于比较。
常见的基于比较的排序算法有七种。下面,我先给出名称和类型,然后用代码逐一实现。我会给每一张附上动画,这样就不用担心模糊了。
前面的直接插入排序非常简单。所谓插入排序,就是我们选择一个数,判断这个数前面的数是否大于这个数。如果是,则大数字向后移动一个单位。把要排序的记录按照它们的键值一条一条的插入到一个有序序列中,当我们插入完所有的记录后,就会得到一个新的有序序列。这是插入排序。
我们要做的就是从有序区间中找出一个比要排序的数大的数(没有更好的了),然后依次排序,直到无序区间没有了。
代码分为单遍排序和完整的代码编写,直到我们能写出正确的代码。
单程分类
int j=I-1;int ret=arr[I];while(j=0){ if(arr[j]ret){ arr[j 1]=arr[j];} else { break} j-;}//跳出的两种情况都适合arr[j 1]=ret;完全码
//code one void interrupt (int * arr,int len){ assert(arr);for(int I=1;我lenI){ int j=I-1;int ret=arr[I];while(j=0){ if(arr[j]ret){ arr[j 1]=arr[j];} else { break} j-;} arr[j 1]=ret;} }//代码二void中断(int * arr,int len){ assert(arr);for(int I=0;I len-1;I){ int end=I;int ret=arr[end 1];while(end=0){ if(arr[end]ret){ arr[end 1]=arr[end];} else { break} end-;} arr[end 1]=ret;}}性能分析我们来分析一下直接插入排序的性能。
时间复杂度
最好的情况:数组从头开始排序(就像我们希望的那样),我们只是遍历数组的时间复杂度O(N)。最坏的情况:数组是逆序的,每次到达ret时,我们都必须遍历有序区间O=0 1 2.len-1;时间复杂度o (n==2==)空间复杂度
这不需要分析,O(1)
稳定性
如果我们判断arr[end] ret加等号时不稳定,不加等号时稳定,那么直接插入排序就是稳定排序(稳定排序可以不稳定,反之亦然)。
从总结中我们可以知道,在数组有序或者接近有序的情况下,插入排序是非常快的,这对我们来说意义重大。
一般知道代码优化的基础就够了,但是有时候我们又怕面试官当场让你优化这个算法。我在这里说一下。这里给你一个半插入优化方案。
半插入半插入是在一个有序的区间内搜索大于ret的数,类似于半插入。只要找到大于ret的有序区间,就可以插入排序。
in half insert(int * arr,int keyi){//返回最后一个不大于ret的下标assert(arr);int left=0;int right=易科-1;int ret=arr[易科];while(left=right){ int mid=(left right)1;if(arr[mid]=ret){ left=mid 1;if(arr[left]ret){ return mid;} } else { right=mid-1;} }返回右;}void halfInsertSort(int* arr,int len){ assert(arr);for(int I=1;我lenI){ int ret=arr[I];int left=halfInsert(arr,I)1;int right=I-1;while(left=right){ arr[right 1]=arr[right];右-;} arr[left]=ret;}}我们来测试一下优化后的性能。
# define CAP 100000 int main(){ srand((无符号)time(NULL));int arr 1[CAP]={ 0 };int arr 2[CAP]={ 0 };for(int I=0;我帽;I){ int n=rand()% CAP 1;arr 1[I]=n;arr 2[I]=n;} int begin 1=clock();interSort(arr1,CAP);int end 1=clock();Printf(直接插入排序:%d \n ,end 1-begin 1);int begin 2=clock();halfInsertSort(arr2,CAP);int end 2=clock();Printf(优化:%d \n ,end 2-begin 2);系统(“暂停”);返回0;}
希尔排序希尔排序法又称减量增量法。前面说过,直接插入排序在好的时候可以做到O(N),不好的时候可以做到O (n==2==),太不稳定了。即使以上半插,也是治标不治本。我们希望有更好的排序算法。一个叫希尔的大个子提出了一个解决方案,可以进一步优化。
Hill排序:既然插入排序在接近有序的复杂时间里接近O(N),那我们能不能把这个数组分组,先对每个组进行插入排序,最后再进行整个插入排序?这不是个好方法吗?是的,我们的想法和希尔大哥是一样的。
怎么分组?我知道,我知道,别阻止我。让我来说。不就是分组吗?很简单。给我一串数字。去吧,把他们分成几组,我会给你所有的分数。
但是,希尔大哥的思想不是我们普通人能想到的。这才是真正的力量。通过这种划分,大数可以尽快跑到后面,小数可以跑到前面。
代码既然我们知道了如何分组,我们就可以编写单遍排序的代码了。当gap等于1时,它与我们的插入排序相同。
for(int I=gap;我lenI=gap){ int ret=arr[I];int j=I-gap;while(j=0){ if(arr[j]ret){ arr[j gap]=arr[j];} else { break} j-=gap;} arr[j gap]=ret;}完成代码
//code one void shell (int * arr,int len,int gap){ assert(arr);for(int k=0;k缺口;k){ for(int I=gap k;我lenI=gap){ int ret=arr[I];int j=I-gap;while(j=0){ if(arr[j]ret){ arr[j gap]=arr[j];} else { break} j-=gap;} arr[j gap]=ret;} }}void shellSort(int* arr,int len){ assert(arr);int gap=lenwhile(gap 1){ gap=gap/3 1;外壳(arr,len,gap);} }//Code 2//这也是一个优化句柄,只是形式变了,性能没有提升。Voidshell (int * arr,int len,int gap){ assert(arr);for(int I=gap;我lenI){ int ret=arr[I];int j=I-gap;while(j=0){ if(arr[j]ret){ arr[j gap]=arr[j];} else { break} j-=gap;} arr[j gap]=ret;}}void shellSort(int* arr,int len){ assert(arr);int gap=lenwhile(gap 1){ gap=gap/3 1;外壳(arr,len,gap);}}性能分析空间复杂度
空间复杂度为O(1)
时间复杂度
Hill排序的时间复杂度很难计算,我们也不知道什么gap等于代码的最高性能。什么差距,也是数学上没有解决的问题。当差距大时,值较大的数据后移快,而当差距小时,数据后移多一点,这是矛盾的,所以我们可以粗略计算出,当差距=len/3时,速度可能更快,Hill排序的时间复杂度为O(
稳定性
希尔排序是一种不稳定的排序,数据在网格间移动。
选择排序是我们所有人当中最差的排序,它的想法很简单。从数组中取出一个数,并与它后面的数据进行比较。如果比它小,它将被交换,直到在比较期间遍历整个数组。
每次从要排序的数据元素中选择最小(或最大)的元素,并将其存储在序列的开头,直到所有要排序的数据元素
数据完了。
在元素集array [i]-array [n-1]中选择具有最大(最小)键的数据元素。如果它不是这个组中的最后(第一个)元素,则在剩余的array [i]-array [n-2] (array [i 1])中与这个组中的最后(第一个)元素交换
for(int j=I 1;j lenj){ if(arr[j]arr[I]){ int ret=arr[j];arr[j]=arr[I];arr[I]=ret;}}所有代码
void selectSort(int* arr,int len){ for(int I=0;I len-1;I){ for(int j=I 1;j lenj){ if(arr[j]arr[I]){ int ret=arr[j];arr[j]=arr[I];arr[I]=ret;}}}}性能分析:最佳时间复杂度和最差空间复杂度为O (n 2) O(1)不稳定双向选择排序。我们对代码进行了优化,使用双向选择的方法有助于提高性能。可以两边选吗,最大的在右边,最小的在左边?细节我就不说了。看看代码就知道了。
void selectSort(int* arr,int len){ int left=0;int right=len-1;while(left right){ int mini=left;int maxi=leftfor(int I=left;i=对;I){ if(arr[I]arr[mini]){ mini=I;} if(arr[I]arr[maxi]){ maxi=I;} } swap( arr[left],arr[mini]);//防止被掉线if(left==maxi){ maxi=mini;} swap( arr[right],arr[maxi]);左;右-;}}比较优化
#define CAP 10000int main(){ srand((无符号)time(NULL));int * arr 1=(int *)malloc(sizeof(4)* CAP);int * arr 2=(int *)malloc(sizeof(4)* CAP);断言(arr 1 arr 2);for(int I=0;我帽;I){ int n=rand()% CAP 1;arr 1[I]=n;arr 2[I]=n;} int begin 1=clock();selectSort1(arr1,CAP);int end 1=clock();int begin 2=clock();selectSort(arr2,CAP);int end 2=clock();Printf (%d \ n 未优化,end 1-begin 1);Printf(优化%d\n ,end 2-begin 2);免费(arr 1);免费(arr 2);系统(“暂停”);返回0;}
堆排序我们来说说堆排序。给定一个数组,我们如何按升序排列它?之前,我们可以通过冒泡来排序。今天我们来讲一个新的排序方法,堆排序。在讨论这个之前,我们先来看看堆的一些属性。
小堆是所有元素中最小的,大堆是所有元素中最大的。如果按照升序排列,我们需要建立一个大堆。前面我们讲过一个一个的堆。现在来说说如何用数字来堆。我们用两种方法建立一个堆。
使用向上调整来堆积数组。
我来解释一下下面的代码。
为什么我从1开始?
我可以从0开始,但是对于一个元素来说,要构建一个堆,它已经可以是一个堆了,所以没有必要构建。
adjustUp(arr,i1);
为什么我1?这个就看我个人对上调函数参数的定义了。Mine是传入元素的数量,所以需要i 1。比如,当i=1时,前两个元素叠加。
void HeapSort(int* arr,int len){ assert(arr);for(int I=1;我leni ) { adjustUp(arr,I 1);}}完整代码
void HeapSort(int* arr,int len){ assert(arr);//为(int i=1)构建一个堆;我leni ) { adjustUp(arr,I 1);}//堆排序for(int I=len;I){//exchange int ret=arr[0];arr[0]=arr[I-1];arr[I-1]=ret;I-;adjustDown(arr,I,0);}}使用向下调整构建一堆数组。
前面的上调是用来建桩的,这里用的是下调。
void HeapSort(int* arr,int len){ assert(arr);for(int parent=(len-1-1)/2;parent parent-){//叶子不需要adjustDown(arr,len,parent);}}完整代码
void HeapSort(int* arr,int len){ assert(arr);for(int parent=(len-1-1)/2;parent parent-){//叶子不需要adjustDown(arr,len,parent);}//堆排序for(int I=len;I){//exchange int ret=arr[0];arr[0]=arr[I-1];arr[I-1]=ret;I-;adjustDown(arr,I,0);}}性能分析空间复杂度O(1)时间复杂度O(NlogN)稳定性不稳定冒泡排序。冒泡排序是我们最早的接触排序算法,也是非常简单的排序算法。这种场面我们应该都见过。如果水底有气泡,当气泡上升时,气泡会逐渐变大。对于很多泡沫来说,整个过程似乎就是一个从小泡沫到大泡沫的场景。冒泡排序也是如此。我们通过每次比较使最大值运行到数组的末尾。冒泡排序很简单,这里就不细说了。我们唯一应该注意的是边界问题。下面的代码很简单。
void bubbleSort(int* arr,int len){ assert(arr);for(int I=0;I len-1;I){ int flag=0;for(int j=0;j len-1-I;j){ if(arr[j]arr[j 1]){ int ret=arr[j];arr[j]=arr[j 1];arr[j 1]=ret;flag=1;} } if(0==flag){ break;}}}性能分析空间复杂度O(1)时间复杂度o (n==2==)是稳定的,这点要注意。优化还在《漫画算法:小灰的算法之旅》给出了另一个优化。让我们尝一尝。在下面这组特殊的数字中,我们会发现一个特征3,4,1,2,7,8,9;其中,3,4,1,2是无序的一部分,7,8,9是有序的一部分,有序的最大值小于有序的最小值,这意味着当我们无序排序时,数据是不可能与有序数组进行交换的。在我们上面,也就是说我们第一次可以把要排序的数组做成3,4,1,2,可以理解为有效长度直接减少了很多。这是可行的。
不过这种优化效果不是很好,但也算是一种优化。书中有更好的优化,鸡尾酒点餐,是基于冒泡的升级版。这里篇幅有限,不赘述。
void bubbleSortIndex(int* arr,int len){ assert(arr);int sort boundary=len-1;for(int I=0;I len-1;I){ int flag=0;int lastExchangeIndex=0;for(int j=0;j sortBoundaryj){ if(arr[j]arr[j 1]){ int ret=arr[j];arr[j]=arr[j 1];arr[j 1]=ret;flag=1;lastExchangeIndex=j;} } sort boundary=lastExchangeIndex;if(0==flag){ break;} }}
快速排序最后说说快速排序。我们只是用了C语言中的qsort,快速排序。既然我们敢称之为快速排序,那它一定有自己的优势。
快速排序是Hoare在1962年提出的一种二叉树结构的交换排序方法。它的基本思想是:取序列中的任意元素进行排序。
作为参考值,要排序的集合根据排序代码分为两个子序列,左子序列中的所有元素都小于参考值,对吗
子序列中的所有元素都大于参考值,然后最左边的子序列重复这个过程,直到所有元素都排列在相应的位置。
单行程排序在我们讨论快速调度之前,很重要的一点是,我们应该了解如何进行单行程排序。
Hoare版本快速排名的提出者hoare给出了这样一个单程排名思路。
让我们在数组的左侧或右侧找到一个键值,然后遍历整个数组。是的,钥匙的左边不一定要和钥匙一样大,右边也不一定要和钥匙一样小。这里有一个问题需要解决。
为什么左右相交的地方比key小?
它们相互接触时有两种情况。
当你打左击右时,你会停在一个比key小的地方。虽然右右左,停在了比bikey大的地方,但是上一轮已经换了,所以还是比key小。
//选择左边为易科int partsort1 (int * arr,int left,int right){ assert(arr);int易科=左;while(左右){ while(arr[右]arr[易科]){ right-;} while(left right arr[left]=arr[易科]){ left;} swap( arr[left],arr[right]);} swap(arr[易科],arr[左]);向左返回;}//选择右为易科int partsort1 (int * arr,int left,int right){ assert(arr);int易科=右;while(左右){ while(左右arr[左]=arr[易科]){ left;} while(左右arr[右]=arr[易科]){ right-;} swap( arr[left],arr[right]);} swap( arr[left],arr[易科]);向左返回;}挖坑法前面的方法有点难以理解。这里有人优化了一下,虽然没有提高效率。但是我们更容易理解。
//Select left使pit int partsort2 (int * arr,int left,int right){ assert(arr);int ret=arr[left];int piti=leftwhile(left right){ while(arr[right]ret){ right-;} arr[piti]=arr[right];piti=右;while(左右arr[left]=ret){ left;} arr[piti]=arr[left];piti=左;} arr[left]=ret;向左返回;}//Select right使pit int partsort2 (int * arr,int left,int right){ assert(arr);int piti=rightint ret=arr[right];while(左右){ while(左右arr[左]=ret){ left;} arr[piti]=arr[left];piti=左;while(left right arr[right]=ret){ right-;} arr[piti]=arr[right];piti=右;} arr[piti]=ret;返回piti}前后指针法最近又多了一个方法。让我们看一看。前后指针的使用方法可以更简洁。
//选择左边为易科int partsort3 (int * arr,int left,int right){ assert(arr);int prev=leftint易科=左;for(int cur=left 1;cur=右;cur){ if(arr[cur]arr[易科] arr[cur]!=arr[ prev]) { swap( arr[prev],arr[cur]);} } swap(arr[易科],arr[prev]);返回prev}////选择右为易科int partsort3 (int * arr,int left,int right){ assert(arr);int易科=右;int prev=left-1;for(int cur=left;向右弯曲;cur){ if(arr[cur]arr[易科] arr[ prev]!=arr[cur]) { swap( arr[prev],arr[cur]);} } prevswap(arr[易科],arr[上一个]);返回prev}快速排序以上三种方法都是单通排序,得到OK键左右两边很特别。然后,我们通过递归一步一步地把它们按顺序排列。这里我想提一下,使用这种非优化的快速排序是垃圾,1ow的数量会导致堆栈溢出。
void QuickSort(int* arr,int len){ assert(arr);Quick(arr,0,len-1);}void Quick(int* arr,int left,int right){ assert(arr);//条件if(left=right){ return;} int pos=PartSort3(arr,left,right);Quick(arr,left,pos-1);快速(数组,位置1,右侧);}性能分析空间复杂度O(logN)
时间复杂度
最佳日志
最差n==2==
稳定的
易变的
我们会对优化快速排序感到困惑。上面时间复杂度最好的是NlogN,好意思叫快速排序。它害羞吗?但是为什么我们的数据库给出快速排序而不是希尔排序呢?事实上,我们可以优化快速排序,这大大提高了它的性能。在标准数据库中炫耀也进行了优化。
选择优化的关键值。我们之前选择的所有键都在左边。这使得很难快速排列有序数据。我们主要选择L进行优化。
选择随机钥匙适合幸运的人。为了避免对排序后的数据进行排序,也就是最坏的情况是n==2==,我们选择最左边和最右边的数据以及中间的数据作为中间值,将中间值与最左边的数据交换然后递归。
int getMid(int* arr,int left,int right){ assert(arr);int mid=(左右)1;int i=leftif(arr[left]arr[mid]){ if(arr[mid]arr[right]){ return mid;} else { if(arr[left]arr[right]){ return left;} else {返回右;} } }//3 1 2 else { if(arr[mid]arr[right]){ return mid;} else { if(arr[left]arr[right]){ return left;} else {返回右;}}}}比较优化
单元间优化在一个小区间内,可以直接选择插入排序,避免再次递归。
if(right-left 1 13){ InterSort(arr left,right-left 1);返回;}快速排序的非递归实现。我们前面用递归的方法实现了快速排序,这里用非递归实现。这个面试你可以随便用一个,这里就不写关于堆的源码了。我以前和你分享过。
我们需要使用堆栈。每次进入堆栈,都是数组的下标。注意,怎么进怎么出的逻辑一定要统一。
void QuickSort(int* arr,int len){ QuickSortNonR(arr,0,len-1);} void quicksortnor(int * arr,int left,int right){ assert(arr);栈栈;InitStack(堆栈);int pivot=PartSort2(arr,left,right);if(向左旋转1) { push( stack,left);push( stack,pivot-1);} if(pivot right-1) { push( stack,pivot 1);推(栈,右);} while(!IsEmpty(stack)){ right=pop(stack);left=pop(栈);pivot=PartSort2(arr,left,right);if(向左旋转1) { push( stack,left);push( stack,pivot-1);} if (pivot right - 1) { push( stack,pivot 1);推(栈,右);}}}归并排序归并排序也是一个不错的排序算法。归并排序是一种基于归并运算的有效排序算法,是分治法的典型应用。组合有序子序列以获得完全有序的序列;也就是说,首先对每个子序列进行排序,然后对子序列段进行排序。如果两个有序表合并成一个有序表,称为双向合并。
对两个有序数组进行排序在讨论这个之前,我们需要对两个有序数组进行排序。这一点奠定了基础。
int * sorttowarray(int * arr 1,int len1,int* arr2,int len2,int * returnSize){ assert(arr 1 arr 2);* returnSize=len1 len2int * array=(int *)malloc(sizeof(int)*(len 1 len 2));assert(数组);int S1=0;int E1=len 1-1;int S2=0;int E2=len 2-1;int I=0;while(S1=E1 S2=E2){ if(arr 1[S1]=arr 2[S2]){ array[I]=arr 1[S1];} else { array[I]=arr 2[S2];} } while(S1=E1){ array[I]=arr 1[S1];} while(S2=E2){ array[I]=arr 2[S2];}返回数组;}int main(){ int arr1[]={ 1,3 };int sz1=sizeof(arr 1)/sizeof(arr 1[0]);int arr2[]={ 2,4,6,8,10 };int sz2=sizeof(arr 2)/sizeof(arr 2[0]);int count=0;int * array=sorttowarray(arr 1,sz1,arr2,sz2,count);for(int I=0;我数;i ) { printf(%d ,array[I]);}返回0;}
要实现递归归并排序,我们先用递归的方法。递归的思想是分而治之,其量类似于我们二叉树的后续遍历。我们在递归结束时对两个有序数组进行排序,并用一个附加数组记录排序结果。最后,我们每次都把排序后的结果再次给原始数组。
void merge(int* arr,int left,int right,int * ret){ if(left=right){ return;} int mid=(左右)1;merge(arr,left,mid,ret);merge(arr,mid 1,right,ret);int i=leftint s1=leftint e1=midint S2=mid 1;int e2=rightwhile(S1=E1 S2=E2){ if(arr[S1]=arr[S2]){ ret[I]=arr[S1];} else { ret[I]=arr[S2];} } while(S1=E1){ ret[I]=arr[S1];} while(S2=E2){ ret[I]=arr[S2];} memcpy(arr left,ret left,sizeof(int)*(right-left 1));}void mergeSort(int* arr,int len){ assert(arr);int * ret=(int *)malloc(sizeof(int)* len);断言(ret);merge(arr,0,len - 1,ret);免费(ret);}性能分析时间复杂度NlogN空间复杂度O(N)稳定性稳定性合并排序的非递归实现。现在,我们将使用合并排序的非递归实现。一般我们都是用栈来模拟递归,但是用栈来模拟归并排序比较困难,但是可以用循环来模拟。困难在于我们在控制边界方面的弱点。
void mergeSortNoR(int* arr,int len){ assert(arr);int * ret=(int *)malloc(sizeof(int)* len);断言(ret);int gap=1;while(gap len){ for(int I=0;我lenI=2 * gap){ int left=I;int mid=左缺口-1;//防止数组越界if(mid=len){ mid=len-1;} int right=中间缺口;//防止数组越界if(right=len){ right=len-1;} int j=左;int s1=leftint e1=midint S2=mid 1;int e2=rightwhile(S1=E1 S2=E2){ if(arr[S1]=arr[S2]){ ret[j]=arr[S1];} else { ret[j]=arr[S2];} } while(S1=E1){ ret[j]=arr[S1];} while(S2=E2){ ret[j]=arr[S2];} }//最后将元素复制到原数组memcpy(arr,ret,sizeof(int)* len);gap *=2;}免费(ret);}int main(){ int arr[]={ 3,3,4,5,6,7,3,3,3 };int SZ=sizeof(arr)/sizeof(arr[0]);mergeSortNoR(arr,SZ);for(int I=0;i i ) { printf(%d ,arr[I]);}返回0;}
我们在工作中经常会遇到这种问题。我们要对100G的数据进行排序,但是内存只有1 g,怎么办?因为我们不能把所有的数据都放在内存里,所以需要外部排序,归并排序是最常用的外部排序。
先把文件切成200份,每份512 M排序。因为内存已经可以存储了,所以任何排序方法都可以用来合并200路。同时可以合并200个有序文件,最终结果会按顺序排序。总结我们看了这么多排序,现在想总结一下。
时间复杂度空间复杂度稳定性插入排序最佳O(N)最差O(N 2) o (1)稳定希尔排序o (n 1.3 ~ o (n 1.5)) o (1)稳定无选择排序o (n 2) o (1)不稳定堆排序O(NlogN) o
郑重声明:本文由网友发布,不代表盛行IT的观点,版权归原作者所有,仅为传播更多信息之目的,如有侵权请联系,我们将第一时间修改或删除,多谢。