最近重新写了一下快速排序的算法和归并排序的算法,不得不说,坑真多......
1、快速排序
快速排序的思想是现在数组中选择一个核心元pivot(一般是选择数组开头的元素),然后对数组剩下的部分进行排序,使得小于等于pivot的元素位于前面一部分,大于pivot的位于后面一部分,假定下标p以前的包括p对应的元素小于等于pivot,p之后的不包括p的元素大于pivot,确定p的位置之后,交换核心元与位于p位置的元素的位置,这样可以使得位置p的元素刚好是pivot,p之前的元素都比pivot小或者相等,p之后的元素都比pivot大。接下来,只需要再对p之前的元素和p之后的元素进行排序即可。
快排的难点在于找到p的位置。可以这么思考,使用两个下标i和j,分别从数组的起始位置和结束位置开始扫描,j先开始,从结束位置往前扫描,直到找到第一个比pivot(默认为起始位置的元素)小或者相等的元素,i再开始,从起始位置往后扫描,直到找到第一个比pivot大的元素,交换i和j位置的元素,重复这个过程直到i和j相遇(这句话的意思是如果j从后往前扫描,在于i相遇前,j扫描过的所有的元素都比pivot相等或者大于,就并不需要交换i和j位置的元素)。若i和j相遇了,那么j位置就是我们要找的p位置。
需要注意的是,一定要先从数组尾部开始扫描。理由如下,假如输入数组是【2、1、3】,初始状态下pivot = 2, i = 0, j = 2. 如果先从i开始扫描,那么i会一直往后扫描,直到i与j相遇,都指向元素3,i = j = 2.此时j并不是我们想要的p位置,位置1才是我们想要的p位置。反之,先从j开始扫描,那么j会扫描到元素1停下,i会扫描到1也停下(i和j相遇了),此时位置j使我们想要的位置p。
代码如下:
void quickSort(int* n, int low, int high) {
if(low < high) {
int pivot = n[low];
int i,j;
i = low;
j = high;
while(i < j) {
while(i < j && n[j] > pivot) {
j--;
}
while(i < j && n[i] <= pivot) {
i++;
}
if(i < j){
swap(n+i, n+j);
}
else {
break;
}
}
swap(n+j, n+low);
quickSort(n, low, j-1);
quickSort(n, j+1, high);
}
}
void swap(int* a, int* b) {
int tem;
tem = *a;
*a = *b;
*b = tem;
}
快速排序算法的时间复杂度取决于每次选择的pivot值。在最坏情况下,每次选择的pivot把数组切成两半,如果一半是空,另一半是剩下的n-1个元素,则时间复杂度为O(n^2)。最好情况下,pivot把数组均分成两半,时间复杂度为O(nlogn)。下面这篇文章说的很清晰了:
如何理解快速排序的时间复杂度是O(nlogn)-CSDN博客
2、归并排序
归并排序的思想就是将输入的数组尽可能均等的(存在数组长度为奇数的情况)分成两部分,分别再对这两部分进行归并排序,使得在这两部分内部是有序数组,最后合并这两个较小的有序数组,使得输入的较大的数组也有序。
归并排序的重点在于怎么把两部分内部有序的数组合并成为较大的有序数组。可以用两个下标i和j分别指向这两个数组(假设是b和c)的起始位置,用下标k指向由b和c合并之后的较大的数组n的起始位置。逐次比较b[i]和c[j]的大小,若b[i]比较小,则令n[k] = b[i],并且i往后挪;若c[j]比较小,则令n[k] = c[j],并且j往后挪,完成一次n[k]的赋值之后,k都要往后挪。这样的过程一直到数组b遍历玩完或者数组c遍历完结束。如果b先遍历完,说明数组n中剩下的不确定的元素直接copy下来数组c的元素即可;反之,若c先遍历完,说明数组n中剩下的不确定的元素直接copy下来数组b的元素即可。
除去上面的难点,实现过程中遇到的另一个坑点是数组长度的求法....主要是floor()函数和ceil()函数输入是double类型的数,不是int型噢,所以使用这两个函数的时候需要先进行强制类型转换,才能得到正确的数组长度.....
代码如下:
void mergeSort(int* A, int size, int low, int high) {
if(low < high) {
int med = floor((double)(low+high)/2);
mergeSort(A, ceil((double)size/2), low, med);
mergeSort(A, floor((double)size/2), med+1, high);
int sizeB = ceil((double)size/2);
int sizeC = floor((double)size/2);
int* B = (int*)malloc(sizeof(int)*sizeB);
int* C = (int*)malloc(sizeof(int)*sizeC);
int i, j;
for(i = 0; i < sizeB; i++) {
B[i] = A[low+i];
}
for(i = 0; i < sizeC; i++) {
C[i] = A[med+1+i];
}
i = 0;
j = 0;
int k = low;
while(i < sizeB && j < sizeC) {
if(B[i] <= C[j]) {
A[k] = B[i];
i++;
}
else {
A[k] = C[j];
j++;
}
k++;
}
//B has traversed firstly, there remains part of C
if(i >= sizeB) {
while(j < sizeC) {
A[k] = C[j];
k++;
j++;
}
}
//C has traversed firstly, there remains part of B
else {
while(i < sizeB) {
A[k] = B[i];
k++;
i++;
}
}
free(B);
free(C);
}
}
归并排序的时间复杂度来自两方面:划分数组和合并有序数组。长度为n的数组最终划分成长度为1的子数组,时间为O(logn),而每次合并两个有序数组时间为O(n),所以归并排序时间复杂度为O(nlogn)
得空再补上其他排序算法 选择、冒泡、插入、堆排序