并不是很懂wikipedia上面说快排的空间复杂度最坏情况是O(NlogN)啊,难道不是空间复杂度平均O(logN),最坏O(N)么……原地快排难道不是只要算递归栈深度就好了么……有谁给我解释一下啊(评论区orz)。
快排的核心思想我感觉就是选一个pivot,然后将待排序数列通过与pivot比较分为两个部分A和B(A部分的数均小于或等于pivot,B部分均大于或等于pivot),这样划分好的数列中的数就处于A <= pivot <= B的状态,接着对划分好的两个部分A和B分别递归调用快排函数,最后一定能出现划分好的部分中只剩下一个元素或没有元素的情况,而对于只有一个元素的数列或空列,我们认为它已经是有序的,这样就把数列排好了。
所以在快排中怎样对数组进行划分是关键,不同版本的快速排序大体结构都差不多,不同之处就在于划分函数的不同,这里就暂时称它为Partition函数吧,下面介绍四种不同的实现。
第一个版本
其实快排一开始不是我们现在看到的这样的,一开始Hoare发明的版本在《算法导论》这本书的思考题7-1上有提及。这个版本相当于是以开头元素为pivot,然后两个index分别从头和尾开始遍历,暂时把这两个index变量称为i和j吧,i从头开始,j从尾开始,其过程如下:
第一步——j往前遍历,遇到小于或等于pivot的数就停止
第二步——i往后遍历,遇到大于或等于pivot的数就停止
第三步——比较i和j大小,j > i则交换i和j位置上的数并在此基础上继续第一步,否则函数返回j
函数返回后,经过Partition函数的调用,数组中下标为j之前(包括j)的元素都小于或等于pivot,j之后的元素都大于或等于pivot,然后对这两部分进行快速排序。
例子:
代码如下:
int Hoare_Partition(int A[ ], int begin, int end){ int pivot = A[begin]; int i = begin - 1;; int j = end + 1; int tmp; while(1){ do{ j--; }while(A[j] > pivot); do{ i++; }while(A[i] < pivot); if(j > i){ tmp = A[i]; A[i] = A[j]; A[j] = tmp; } else{ return j; } } } void Quick_Sort_1(int A[ ], int begin, int end){ int pivot_position; if(begin >= end) return; pivot_position = Hoare_Partition(A, begin, end); Quick_Sort_1(A, begin, pivot_position); Quick_Sort_1(A, pivot_position + 1, end); }
第二个版本
这个版本是对Hoare原版本的一个改进,Hoare原版本中Partition函数调用结束后pivot会存在于划分好的两个部分中的某一个中,因此第二个版本对此进行了改进,同样选取第一个元素作为pivot,如果数列的下标从begin开始,end结束的话,那么用Hoare的处理思想去处理从begin + 1到end部分的数列(Hoare的版本是处理从begin到end部分的数列的),这样处理完成后下标为begin + 1到j的数都是小于或等于pivot的,而下标为j + 1到end的数都是大于或等于pivot的,最后再将pivot和下标为j的数进行交换并返回j,这样一来下标为j之前(不包括j)的数均小于或等于pivot,下标为j之后的数均大于或等于pivot,pivot也就不在两个划分好的部分中,而独立存在了,因此只要对两个部分继续快排就好了,而不需要再将pivot包括进去。
例子:
代码如下:
int New_Partition_1(int A[ ], int begin, int end){ int pivot = A[begin]; int i = begin; int j = end + 1; int tmp; while(1){ do{ j--; }while(A[j] > pivot); do{ i++; }while(A[i] < pivot); if(j > i){ tmp = A[i]; A[i] = A[j]; A[j] = tmp; } else{ A[begin] = A[j]; A[j] = pivot; return j; } } } void Quick_Sort_2(int A[ ], int begin, int end){ int pivot_position; if(begin >= end) return; pivot_position = New_Partition_1(A, begin, end); Quick_Sort_2(A, begin, pivot_position - 1); Quick_Sort_2(A, pivot_position + 1, end); }
第三个版本
这个版本是现在比较常见的版本之一,将pivot所在位置挖成一个坑,i从begin + 1开始,j从end开始,然后:
第一步——j往前遍历,找到小于或等于pivot的数
如果此时j > i,将下标为j的数填入现在的坑中,并在下标为j的位置产生一个新坑,进行第二步
否则就将pivot填入现在的坑中,并返回j
第二步——i往后遍历,找到大于或等于pivot的数
如果此时j > i,将下标为i的数填入现在的坑中,并在下标为i的位置产生一个新坑,进行第一步
否则就将pivot填入现在的坑中,并返回i
例子:
代码如下:
int New_Partition_3(int A[ ], int begin, int end){ int pivot = A[end]; int i = begin, j = begin; int tmp; for(i = begin; i < end; i++){ if(A[i] < pivot){ tmp = A[j]; A[j] = A[i]; A[i] = tmp; j++; } } A[end] = A[j]; A[j] = pivot; return j; } void Quick_Sort_3(int A[ ], int begin, int end){ int pivot_position; if(begin >= end) return; pivot_position = New_Partition_2(A, begin, end); Quick_Sort_3(A, begin, pivot_position - 1); Quick_Sort_3(A, pivot_position + 1, end); }
第四个版本
这个版本也很常见,就是把最后一个数作为pivot,i一开始等于begin,用j从begin开始往end - 1遍历,找到小于或等于pivot的数,与i所在位置的数交换,并将i++,最后将pivot和下标为i的数交换,返回i。
例子:
代码如下:
int New_Partition_3(int A[ ], int begin, int end){ int pivot = A[end]; int i = begin, j = begin; int tmp; for(i = begin; i < end; i++){ if(A[i] < pivot){ tmp = A[j]; A[j] = A[i]; A[i] = tmp; j++; } } A[end] = A[j]; A[j] = pivot; return j; } void Quick_Sort_4(int A[ ], int begin, int end){ int pivot_position; if(begin >= end) return; pivot_position = New_Partition_3(A, begin, end); Quick_Sort_4(A, begin, pivot_position - 1); Quick_Sort_4(A, pivot_position + 1, end); }
完整代码:
// // main.c // Quick Sort // // Created by 余南龙 on 2016/11/29. // Copyright ? 2016年 余南龙. All rights reserved. // #include <stdio.h> int Hoare_Partition(int A[ ], int begin, int end){ int pivot = A[begin]; int i = begin - 1;; int j = end + 1; int tmp; while(1){ do{ j--; }while(A[j] > pivot); do{ i++; }while(A[i] < pivot); if(j > i){ tmp = A[i]; A[i] = A[j]; A[j] = tmp; } else{ return j; } } } int New_Partition_1(int A[ ], int begin, int end){ int pivot = A[begin]; int i = begin; int j = end + 1; int tmp; while(1){ do{ j--; }while(A[j] > pivot); do{ i++; }while(A[i] < pivot); if(j > i){ tmp = A[i]; A[i] = A[j]; A[j] = tmp; } else{ A[begin] = A[j]; A[j] = pivot; return j; } } } int New_Partition_2(int A[ ], int begin, int end){ int pivot = A[begin]; int i = begin; int j = end + 1; while(1){ do{ j--; }while(A[j] > pivot); if(j > i){ A[i] = A[j]; } else{ A[i] = pivot; return i; } do{ i++; }while(A[i] < pivot&&i < j); if(j > i){ A[j] = A[i]; } else{ A[j] = pivot; return j; } } } int New_Partition_3(int A[ ], int begin, int end){ int pivot = A[end]; int i = begin, j = begin; int tmp; for(i = begin; i < end; i++){ if(A[i] < pivot){ tmp = A[j]; A[j] = A[i]; A[i] = tmp; j++; } } A[end] = A[j]; A[j] = pivot; return j; } void Quick_Sort_1(int A[ ], int begin, int end){ int pivot_position; if(begin >= end) return; pivot_position = Hoare_Partition(A, begin, end); Quick_Sort_1(A, begin, pivot_position); Quick_Sort_1(A, pivot_position + 1, end); } void Quick_Sort_2(int A[ ], int begin, int end){ int pivot_position; if(begin >= end) return; pivot_position = New_Partition_1(A, begin, end); Quick_Sort_2(A, begin, pivot_position - 1); Quick_Sort_2(A, pivot_position + 1, end); } void Quick_Sort_3(int A[ ], int begin, int end){ int pivot_position; if(begin >= end) return; pivot_position = New_Partition_2(A, begin, end); Quick_Sort_3(A, begin, pivot_position - 1); Quick_Sort_3(A, pivot_position + 1, end); } void Quick_Sort_4(int A[ ], int begin, int end){ int pivot_position; if(begin >= end) return; pivot_position = New_Partition_3(A, begin, end); Quick_Sort_4(A, begin, pivot_position - 1); Quick_Sort_4(A, pivot_position + 1, end); } int main(int argc, const char * argv[]) { int A_1[10] = {2, 4, 6, 1, 5, 9, 0, 8, 7, 3}; int A_2[10] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9}; int A_3[10] = {9, 8, 7, 6, 5, 4, 3, 2, 1, 0}; int A_4[10] = {3, 9, 1, 7, 0, 2, 8, 5, 4, 6}; int i; Quick_Sort_1(A_1, 0, 9); for(i = 0; i < 10; i++){ printf("%d ", A_1[i]); } putchar(‘\n‘); Quick_Sort_2(A_2, 0, 9); for(i = 0; i < 10; i++){ printf("%d ", A_2[i]); } putchar(‘\n‘); Quick_Sort_3(A_3, 0, 9); for(i = 0; i < 10; i++){ printf("%d ", A_3[i]); } putchar(‘\n‘); Quick_Sort_4(A_4, 0, 9); for(i = 0; i < 10; i++){ printf("%d ", A_4[i]); } putchar(‘\n‘); }