1、排序的概念
1.1 排序的概念
排序:所谓排序,就是使一串记录,按照其中的某个或某些关键字的大小,递增或递减的排列起来的操作。
稳定性:假定在待排序的记录序列中,存在多个具有相同的关键字的记录,若经过排序,这些记录的相对次序保持不变,即在原序列中,r[i]=r[j],且r[i]在r[j]之前,而在排序后的序列中,r[i]仍在r[j]之前,则称这种排序算法是稳定的;否则称为不稳定的。
内部排序 :数据元素全部放在内存中的排序。外部排序 :数据元素太多不能同时放在内存中,根据排序过程的要求不能在内外存之间移动数据的排序。
1.2 常见的排序
在编程语言中常见的排序算法有以下几种:
2、常见排序算法的实现
2.1 插入排序
2.1.1 基本思想

2.1.2 直接插入排序
当插入第 i (i >= 1)个元素的时候,i 前面的元素已经排好序,此时用arr[i]与前面的元素进行比较,找到适合的位置插入即可,如果arr[i]的元素小于原来位置上的元素,原来位置上的元素则需要向后移。当arr[i]大于原来元素元素时说明找到了arr[i]该插入的位置,直接将arr[i]插在当前位置的后一个元素即可。
如图,要将当前数组的元素直接插入排序:
我们需要先定义两个下标i = 1, j = i - 1,再定义一个临时变量tmp = arr[i]。
然后再将下标为 j 的元素与tmp进行对比,如果array[j] > tmp,则要把下标为 j 的位置让出来,即 array[j + 1] = array[j],同时 j--,继续对比。
直到 j < 0结束对比,此时 j + 1下标的位置就是当前tmp元素的位置,再将tmp的值赋给array[j + 1]就ok了,即array[j + 1] = tmp。
然后 i 继续向后走,j 继续赋值为 i - 1。
比较array[j] > tmp,则给tmp让出位置(注意,让出位置不能在排序过程中损失任何一个元素),array[j + 1] = array[j] ,j-- 。
继续比较array[j] 仍然大于 tmp 继续让位置 array[j + 1] = array[j],j-- 。
当 j < 0的时候,将tmp赋给索引为 j + 1的位置 。
代码、时间、空间复杂度以及适用场景如下:
//默认从小到大排序
public static void insertSort(int[] arr){
//直接插入排序
//时间复杂度:最好情况:数据完全有序,O(n) 最坏情况:数据完全逆序,O(n^2)
//结论:当给的数据越有序排序越快
//如果有一组基本有序的数据要排序————》这个快
//空间复杂度:O(1)
//稳定性:稳定的排序
//一个本身稳定的排序 是可以实现为不稳定的
//但是相反 一个本身就不稳定的排序 是不可能实现为稳定的
for (int i = 1;i < arr.length;i++){
int tmp = arr[i];
int j = i - 1;
for (; j >= 0 ; j--) {
if(arr[j] > tmp){//说明tmp在当前j的前面
//让j下标的元素往前面盖
arr[j+1] = arr[j];
}else {
break;
}
}
arr[j+1] = tmp;
}
}
2.1.3 希尔排序
希尔排序又称缩小增量法。其基本思想是:先选定一个整数,把待排序数组中所有元素分成多个组(距离为那个整数的元素分在同一组内),并对每一组的元素进行排序,然后,再取一个整数,重复上述分组和排序的工作。当取的值达到1的时候,所有的记录在同一组内排好序。(其实也就是分组进行直接插入排序)。
如下图,有一组数据,其初始状态如下:
取整数gap为5,然后将互相距离为5的元素分为一组,然后进行排序: 再取整数gap为2,然后再将互相距离为2的元素分为一组,即4 2 5 8 5一组,1 3 9 6 7一组,然后组内进行排序:
最后,后再取整数gap为1,此时距离为1,即所有的元素都在一组,然后再进行分组,组内排序:
由上面的内容,我们可以知道希尔排序其实是对直接插入排序的优化,当gap > 1时都是预排序,目的是让数组更接近于有序。当gap == 1时,数组已经接近有序的了,这样就会很快。这样整体而言,可以达到优化的效果。
希尔排序的时间复杂度不好计算,因为gap的取值方法很多,导致很难去计算,因此在好些书中给出的希尔排序的时间复杂度都不固定:
下面是希尔排序的代码、时间复杂度的范围、空间复杂度和稳定性:
//希尔排序
//采用分组:每组进行插入排序 跳跃式的分组可能会将更小的元素 尽可能地往前放
//时间复杂度:???n^1.3~n^1.5
//空间:O(1)
//稳定性:不稳定
public static void shellSort(int[] arr){
int gap = arr.length;
while(gap > 1){
gap /= 2;
shell(arr,gap);
}
}
private static void shell(int[] arr,int gap){
for (int i = gap; i < arr.length; i++) {
//最后都会看成一组进行排序所以++/+gap不影响
int tmp = arr[i];
int j = i-gap;
for (; j >= 0; j-=gap) {
if(arr[j] > tmp){
//如果大于就换位
arr[j+gap] = arr[j];
}else{
//不大于就放回去,并且跳出循环
// arr[j+1] = tmp;
break;
}
}
arr[j+gap] = tmp;
}
2.2 选择排序
2.2.1 基本思想
每一次从待排序的数据元素中选出最小(或最大)的一个元素,存放在序列的起始位置,直到排序的数据元素排完。
2.2.2 直接选择排序
- 初始状态:整个数组分为有序区和无序区,初始时有序区为空,无序区包含数组的所有元素。
- 第 1 轮选择:在无序区中找到最小的元素,将它与无序区的第一个元素交换位置,此时有序区包含 1 个元素,无序区包含 n - 1 个元素。
- 第 2 轮选择:在剩余的无序区中找到最小的元素,将它与无序区的第一个元素交换位置,此时有序区包含 2 个元素,无序区包含 n - 2 个元素。
- 重复上述步骤,直到无序区为空,此时整个数组就有序了。
如下图,如何将其以直接选择排序的方式进行排序。 定义下标i , j , minIndex。
然后将 j 逐步向后加加,比较arr[j] 与 arr[minIndex]的大小,如果arr[j] < arr[minIndex],则交换对应的值一直 j 遍历完整个数组后。
进行交换
i++,继续寻找下一个最小的元素。最终使得数组称为升序。
//直接选择排序
//[j]<[minIndex]——>更新
//时间复杂度:不管最好最坏 都是O(n^2)
//空间复杂度:O(1)
//稳定性:不稳定
public static void selectSort(int[] arr){
for (int i = 0; i < arr.length; i++) {
int minIndex = i;
int j = i+1;
for (; j < arr.length; j++) {
if (arr[j] < arr[minIndex]){
minIndex = j;
}
}
swap(arr,minIndex,i);
}
}
private static void swap(int[] arr,int i,int j){
int tmp = arr[i];
arr[i] = arr[j];
arr[j] = tmp;
}
直接选择排序还有一种优化的方法:定义minindex和maxindex,设置一个i遍历数组,如果arr[i] < arr[minIndex] 则把 i 赋给minIndex,如果arr[i] > arr[maxIndex],则把 i 赋给maxIndex,这样 i 遍历一次之后,就能知道最大和最小的值了,然后然后将对应最大和最小的值分别放在索引为right 和 left 的位置,然后left++,right--。
private static void swap(int[] arr,int i,int j){
int tmp = arr[i];
arr[i] = arr[j];
arr[j] = tmp;
}
public static void selectSort2(int[] arr){
int left = 0;
int right = arr.length-1;
while(left < right){
int minIndex = left;
int maxIndex = left;
for (int i = left+1; i <= right ; i++) {
//更新最大最小值下标
if(arr[i] < arr[minIndex]){
minIndex = i;
}
if(arr[i] > arr[maxIndex]){
maxIndex = i;
}
}
swap(arr,left,minIndex);
//最大值正好在最小值地的位置交换到了minIndex
if(maxIndex == left){
maxIndex = minIndex;
}
swap(arr,right,maxIndex);
left++;
right--;
}
}
2.2.3 堆排序

如果要得到一个从小到大排序的数组,则建立一个大根堆。 将堆顶元素和最后一个元素进行交换。
交换后,usedSize-- 然后将剩余的元素再进行调整,使其为大根堆。
继续进行将第一个元素与堆顶元素进行交换。
然后再继续调整为大根堆。
如此就实现了从小到大的排序,代码如下:
//堆排序
//时间复杂度:O(n*logN)
//空间复杂度:O(1)
//稳定性:不稳定的
//数据量大的时候堆排一定比希尔快
public static void heapSort(int[] arr){
creatBigHeap(arr);
int end = arr.length-1;
while(end > 0) {
swap(arr, 0, end);
shiftDown(arr, 0, end);
end--;
}
}
private static void creatBigHeap(int[] arr){
for (int parent = (arr.length-1-1)/2;parent >= 0 ; parent--) {
shiftDown(arr,parent,arr.length);
}
}
private static void shiftDown(int[]arr,int parent,int end) {
int child = 2*parent+1;
while(child < end){
if(child + 1 < end && arr[child] < arr[child+1]){
child++;
}
if(arr[child] > arr[parent]){
swap(arr,child,parent);
parent = child;
child = 2*parent+1;
}else{
break;
}
}
}
private static void swap(int[] arr,int i,int j){
int tmp = arr[i];
arr[i] = arr[j];
arr[j] = tmp;
}
2.3 交换排序
2.3.1 冒泡排序
由于在之前的文章已经讲解过,这里就不做过多赘述,这是冒泡排序文章的链接:冒泡排序(Bubble Sort)_逆向冒泡排序-优快云博客
代码如下:
//冒泡排序
//时间复杂度:O(n^2) 如果加了优化 最好情况O(n)
//空间复杂度:O(1)
//稳定性:稳定
public static void bubleSort(int[] arr){
for (int i = 0; i < arr.length-1; i++) {//比较的趟数
boolean flg = false;
for (int j = 0; j < arr.length-1-i; j++) {
//每次比较都比上一次少一次
if(arr[j] > arr[j+1]){
swap(arr,j,j+1);
flg = true;
}
}
if(!flg){
return;
}
}
}
private static void swap(int[] arr,int i,int j){
int tmp = arr[i];
arr[i] = arr[j];
arr[j] = tmp;
}
2.3.2 快速排序

2.3.2.1 Hoare法
如下图数组:
定义一个left,一个right
Key对应的元素是6,然后先从后面right开始,找比6小的元素,再从前面left开始,找比6大的元素。 找到后,交换值。
直到left和right相遇之后。 将相遇的这个索引的数值和6进行交换。
换完之后,会发现,6左边的元素都是比6小的值,6右边的元素都是比6大的值
此时称6为privot(基准)。
然后再以上面的方法,分而治之,即再在6的左边,和6的右边进行上述操作
例如:在左边,Key为3。
再先从right开始,找比3小的元素,找到2,再从左边left出发找比3大的元素,发现会相遇。
在拿相遇时候的值和3进行交换。
此时3又是新的基准。
再对3的左边进行操作,有新的left和right。 先从right找比2小的--1,再从left找比2大的,与right相遇。
相遇的值与2进行交换。 此时2有序了,左侧只有一个元素,1也有序了。
再对3的右树进行操作:操作过后,4,5交换位置,这样6的左树就全部有序了。 快排的递归可以类比二叉树的递归过程:
第一次找到privot6:
对6的左树进行递归:
再对3的左树递归:
再逐层向下递归:
然后再递归3的右树,再递归6的右树等等,所以快速排序重点在于:如何确定基准privot的位置。Hoare法只是其中找基准的方法之一。
完整代码:
public static void quickSort(int[] arr){
quick(arr,0,arr.length-1);
}
private static void quick(int[] arr,int start,int end){
if(start >= end) return;//左边是一个节点 或者 一个节点也没有了
//取基准
int priot = parttionHoare(arr,start,end);
//类比遍历二叉树
quick(arr,start,priot-1);
quick(arr,priot+1,end);
}
private static int parttionHoare(int[] arr,int left,int right){
int k = arr[left];
int i = left;
while(left < right){
//如果left先走的话,left和right相遇的地方一定是比k大的
while(left < right && arr[right] >= k){//这里为什么取等
//如果不取可能会死循环
right--;
}//right 下标一定是比k小的数据
while(left < right && arr[left] <= k){//这里为什么取等
left++;
}//left 下标一定是比k大的数据
swap(arr,left,right);
}
//相遇的位置和i交换
swap(arr, left, i);
return left;
}
private static void swap(int[] arr,int i,int j){
int tmp = arr[i];
arr[i] = arr[j];
arr[j] = tmp;
}
2.3.2.2 挖坑法
挖坑法,顾名思义,是会挖出一个一个空的,会首先将key的值单独存起来(相当于挖了一个坑),然后从right开始找比key小的元素,找到后,把该索引的值填入给key的下标索引中
如图:先将6存起来
然后right找到比6小的元素5。 把5覆盖到6的位置,同时之前5的位置相当于又留了一个坑。
left向前走,找到比6大的元素的下标索引。
把7填到刚刚5留下的坑的位置,同时7也留下了新的坑。 right继续向前找到比6小的元素。
把4填到刚刚7留下的坑,同时4也留下了新的坑。 left继续向后找到比6大的元素,9把刚刚4留下的坑填入,留下新的坑。
right继续向前找到比6小的元素,3把刚刚9留下的坑填入,留下新的坑。 left向后走,和right相遇,相遇位置是空的坑,将最开始记录下的6填入。
完成一次排序,返回基准值,然后进行递归,挖坑法的大致流程和Hoare法相同,但在细节和每一次的排序结果中有所不同。
代码:
public static void quickSort(int[] arr){
quick(arr,0,arr.length-1);
}
private static void quick(int[] arr,int start,int end){
if(start >= end) return;//左边是一个节点 或者 一个节点也没有了
//取基准
int priot = parttion(arr,start,end);
//类比遍历二叉树
quick(arr,start,priot-1);
quick(arr,priot+1,end);
}
private static int parttion(int[] arr,int left,int right){
int k = arr[left];
int i = left;
while(left < right){
//如果left先走的话,left和right相遇的地方一定是比k大的
while(left < right && arr[right] >= k){//这里为什么取等
//如果不取可能会死循环
right--;
}//right 下标一定是比k小的数据
arr[left] = arr[right];
while(left < right && arr[left] <= k){//这里为什么取等
left++;
}//left 下标一定是比k大的数据
arr[right] = arr[left];
}
//相遇的位置和i交换
arr[left] = k;
return left;
}
2.3.2.3 前后指针法
起始时,prev指针指向序列的开头,cur指针指向prev指针的后一个位置。
使用代码来理解这种方法:
示例与代码结合,示例满足273行循环,进入循环,274行中array[cur] < array[left],示例中array[cur] 等于1,array[left] 等于6, 满足条件,再看array[++prev] != array[cur] array[++prev]等于1,array[cur] 等于1,两个值相等,不满足if条件,不进入循环,cur++。
cur++后,cur仍小于等于right,继续进入循环,仍不满足if的第二个条件,cur++。
继续进入循环,此时情况不满足if的第一个条件,直接cur++,注意此时因为不满足第一个if条件,就不会进入if的第二个条件,所以prev不会执行前置++。 继续进入循环,此时情况不满足if的第一个条件,直接cur++,prev仍然不会执行前置++。
此时进入循环,if的两个条件都满足,且在第二个条件的时候,prev的位置也进行了变化。 进入if循环,交换cur和prev的值。
然后cur++。 继续向后推进,if的两个条件都满足,在满足第二个条件的时候,prev的位置继续进行变化。
进入if条件,进行swap交换。 cur++。
进入循环,满足if的两个条件,prev先前置++。 再swap交换。
cur++。
再进入循环,不满足if的第一个条件,prev不前置++,不交换,cur++ 此时cur 等于 right 还可以进入循环,不满足if的第一个条件,prev不前置++,不交换。
此时cur > right 无法进入循环,再交换left和prev的值。 prev的位置作为基准进行返回,这就是一次前后指针的过程。
前后指针方法的特点是:使用单指针进行扫描:cur指针从左到右扫描数组,同时利用prev指针标记小于基准元素区域的边界,通过条件判断和交换操作完成分区。
public static void quickSort(int[] arr){
quick(arr,0,arr.length-1);
}
private static void quick(int[] arr,int start,int end){
if(start >= end) return;//左边是一个节点 或者 一个节点也没有了
//取基准
int priot = partition(arr,start,end);
//类比遍历二叉树
quick(arr,start,priot-1);
quick(arr,priot+1,end);
}
//前后指针法
private static int partition(int[] array, int left, int right) {
int prev = left ;
int cur = left+1;
while (cur <= right) {
if(array[cur] < array[left] && array[++prev] != array[cur]) {
swap(array,cur,prev);
}
cur++;
}
swap(array,prev,left);
return prev;
}
2.3.3 快速排序优化
在刚刚提到的快速排序中,无论是Hoare法、挖坑法还是前后指针法,都会出现最坏情况:即单分支树的情况,导致栈溢出异常,如何解决呢?
栈溢出,就是因为树的高度太多了,想办法优化,就是降低需要的空间,就是要降低树的高度,即尽量使得单分支树变为满 / 完全二叉树。
2.3.3.1 三数取中法选key
如下图数据,如果直接进行快速排序,会形成单分支树。
可以对下标索引进行操作,找到位于 left 和 right 的中间位置索引mid。 对比left mid right 三个下标索引对应的元素值 5 7 9, 比较这三个值的大小,判断哪一个的大小位于这三个值的中间位置,即7是5 7 9 三个元素的中间,然后7的位置和left进行交换。
这样7就称为了新的key值,在每次递归前,先进行上面操作,这样进行快速排序的时候,不会出现单分支树的情况了。
下面是实现三数取中法的代码:
private static int midOfTree(int[] arr, int left, int right) {
int mid = (left+right)/2;
if(arr[left] < arr[right]){
if(arr[mid] < arr[left]){
return left;
} else if(arr[mid] > arr[right]){
return right;
} else{
return mid;
}
}else {
if(arr[mid] > arr[left]){
return left;
} else if (arr[mid] < arr[right]) {
return right;
}else {
return mid;
}
}
}
在快排方法中使用:
2.3.3.2 递归到比较小的子区间的时候,可以考虑使用插入排序
对于一棵满 / 完全二叉树来说,最后两层的结点的总数,一般是最多的,占了整棵树的 2 / 3。
在递归创建的时候,最后两层所占用的空间也是最多的。在快速排序中,是利用树的递归思想不断在进行排序,越递归到后面,即递归到比较小的子区间的时候,其实是到最后已经大致有序了,我们知道,直接插入排序方法在对大致有序的数组进行排序时候,其效率较高。
所以,我们可以在进行快速排序的过程中,当递归到比较小的子区间的时候,使用插入排序。
当 end - start + 1 <= 15 代表当前子数组中包含的元素总数,当子数组中包含的元素总数小于15的时候,就可以使用直接插入排序方法,减少递归的次数。
下面是快速排序的完整代码:
//快速排序
//时间复杂度:最好情况:完全二叉树 O(N*logN) 最坏情况:O(N^2)单分支
//空间复杂度:最好情况:完全二叉树 O(logN) 最坏情况:O(N)单分支
//稳定性:不稳定
//优化方法:三数取中法降低树的高度 小的有序的区间用插入排序 减少递归次数
//1、挖坑法 2、hor法 3、前后指针法
public static void quickSort(int[] arr){
quick(arr,0,arr.length-1);
}
private static void quick(int[] arr,int start,int end){
if(start >= end) return;//左边是一个节点 或者 一个节点也没有了
if(end-start+1 <= 15){
//插入排序
insertSortRange(arr,start,end);
return;
}
//三数取中
int index = midOfTree(arr,start,end);
swap(arr,index,start);//此时交换完之后 一定能保证start下标是中间大的数字
//取基准
int priot = parttion(arr,start,end);
//类比遍历二叉树
quick(arr,start,priot-1);
quick(arr,priot+1,end);
}private static int midOfTree(int[] arr, int left, int right) {
int mid = (left+right)/2;
if(arr[left] < arr[right]){
if(arr[mid] < arr[left]){
return left;
} else if(arr[mid] > arr[right]){
return right;
} else{
return mid;
}
}else {
if(arr[mid] > arr[left]){
return left;
} else if (arr[mid] < arr[right]) {
return right;
}else {
return mid;
}
}
}
private static void insertSortRange(int[] arr,int begin,int end){
for (int i = begin+1; i <= end; i++) {
int tmp = arr[i];
int j = i-1;
for (; j >= begin; j--) {
if(arr[j] > tmp){
//如果大于就换位
arr[j+1] = arr[j];
}else{
//不大于就放回去,并且跳出循环
// arr[j+1] = tmp;
break;
}
}
arr[j+1] = tmp;
}
}
private static int parttion(int[] arr,int left,int right){
int k = arr[left];
int i = left;
while(left < right){
//如果left先走的话,left和right相遇的地方一定是比k大的
while(left < right && arr[right] >= k){//这里为什么取等
//如果不取可能会死循环
right--;
}//right 下标一定是比k小的数据
arr[left] = arr[right];
while(left < right && arr[left] <= k){//这里为什么取等
left++;
}//left 下标一定是比k大的数据
arr[right] = arr[left];
}
//相遇的位置和i交换
arr[left] = k;
return left;
}
2.3.4 非递归实现快速排序
在新的基准操作的时候,入栈的时候先入left再入right,操作的时候,仍然先操作右边,再操作左边。
//非递归实现快排
public static void quickSortNor(int[] arr){
Stack<Integer> stack = new Stack<>();
int left = 0;
int right = arr.length-1;
int piovt = partition(arr,left,right);
if(piovt-1 > left){//说明左边一定有两个元素
stack.push(left);
stack.push(piovt-1);
}
if(piovt+1 < right){//说明右边一定有两个元素
stack.push(piovt+1);
stack.push(right);
}
while(!stack.isEmpty()){
right = stack.pop();
left = stack.pop();
piovt = partition(arr,left,right);
if(piovt-1 > left){//说明左边一定有两个元素
stack.push(left);
stack.push(piovt-1);
}
if(piovt+1 < right){//说明右边一定有两个元素
stack.push(piovt+1);
stack.push(right);
}
}
}
2.4 归并排序
2.4.1 基本思想

分步讲解:
对其先进行分解(先分解左边 再 分解右边 最后合并),第一次分解 以中间的m的索引 作为新的数组的r进行分解。
再向下进行分解。
继续分解,直到r == l的时候分解完毕 。 分解完毕后,再进行合并。
分解很简单,就一直向下递归即可。
如何将两个序列段进行合并呢?
以如下图的两个有序段(6,10 和 1, 7)举例,对两个序列段分别定义s 和 e,来表示第一个和第二个序列端的开始和合并。
因为要合并到一个新的数组,所以应该计算两个序列段的元素个数,即 3 - 0 + 1 = 4,申请一个长度为4的数组来存放合并后的序列。然后每次让s1和s2进行比较,谁小,谁放入数组中,对应的s++,e是一个结束的位置,如果有一个数组的s逐渐向后递归,直到其有一个数组没有元素了,则说明另一个数组的值都大于另一个数组的最大的值,直接将剩下的数组全部按顺序放入即可。
2.4.2 递归实现归并排序
//归并排序 分解左边 分解右边 合并
//最常用的外部排序
//空间复杂度:O(n)
//时间复杂度:不管有无序都是 N*logN
//稳定的排序:插入 冒泡 归并
public static void mergeSort(int[] arr){
mergrSortFunc(arr,0,arr.length-1);
}
private static void mergrSortFunc(int[] arr,int left,int right){
if(left >= right) return;;
int mid = (left+right)/2;
//分解左边
mergrSortFunc(arr,left,mid);
//分解右边
mergrSortFunc(arr,mid+1,right);
//合并
merge(arr,left,right,mid);
}
private static void merge(int[] arr, int left, int right, int mid) {
int s1 = left;
int s2 = mid+1;
int[] tmpArr = new int[right-left+1];
int k = 0;
//证明两个区间 都同时有数据的
while(s1 <= mid && s2<= right){
if(arr[s1] <= arr[s2]){
tmpArr[k++] = arr[s1++];
}else{
tmpArr[k++] = arr[s2++];
}
}
//把剩余的元素放入tmpArr中
while(s1 <= mid){
tmpArr[k++] = arr[s1++];
}
while(s2 <= right){
tmpArr[k++] = arr[s2++];
}
//tmpArr 里面一定是有序的
for (int i = 0; i < tmpArr.length; i++) {
arr[i+left] = tmpArr[i];
}
}
2.4.3 非递归实现归并排序
//非递归实现归并
public static void mergeSort2(int[] arr){
int gap = 1;
while(gap < arr.length){
for (int i = 0; i < arr.length; i += 2*gap) {
int left = i;
int mid = left+gap-1;
int right = mid+gap;
if(mid >= arr.length){
mid = arr.length-1;
} if(right>= arr.length){
right = arr.length-1;
}
merge(arr,left,right,mid);
}
gap *= 2;
}
}
2.4.4 海量数据排序问题
外部排序:排序过程需要在磁盘等等外部存储的排序。
前提:内存只有 1G,需要排序的数据有 100G。

3、排序算法复杂度及稳定性分析总结