快排算法的一点思考
基础的快排算法是很常用且效率高的排序算法,而且在一些面试和考试中也用得到,故在尝试实现基础的快排算法后写下此文。
1、整体代码(主要根据算法导论的快排章节的思想):
#include <stdio.h>
#include <stdlib.h>
#define M 15
void QuickSort(int A[],int p,int r);//递归实现的主程序
int PARTITION(int A[],int p,int r);//局部排序的函数
int main(){
int i = 0;
int a[M] = {3,9,1,6,5,98,15,47,11,24,35,12,4,78,36};
printf("start: ");
for(i = 0;i<M;i++){
printf("%3d",a[i]);
}
printf("\n");
QuickSort(a,0,M-1);
printf("end : ");
for(i = 0;i<M;i++){
printf("%3d",a[i]);
}
return 0;
}
void QuickSort(int A[],int p,int r){
int q = 0;
if(p < r){
q = PARTITION(A,p,r);
//递归实现子串排序
QuickSort(A,p,q-1);
QuickSort(A,q+1,r);
}
}
int PARTITION(int A[],int p,int r){
int x = A[r];//选定标记位(这边是选定了需要排序的最后一位)
int i = p-1,j=p,temp;
for(j = p;j < r;j++){
//将小于标记位的数字与前面的数字进行交换,这样使得数组前面变成小于标记位的数字
if(A[j] <= x){
i++;
temp = A[j];
A[j] = A[i];
A[i] = temp;
}
}
//最后把标记位数字(这里是选定了最后一位)与前面大于标记位的数字进行交换(不存在大于的话就是和自己交换)
temp = A[r];
A[r] = A[i+1];
A[i+1] = temp;
return i+1;//返回当前标记位数字的位置
}
执行结果:
start: 3 9 1 6 5 98 15 47 11 24 35 12 4 78 36
end : 1 3 4 5 6 9 11 12 15 24 35 36 47 78 98
2、主要思路
快速排序(Quicksort)是对冒泡排序的一种改进,大多通过递归实现,是一种不稳定的排序算法。
主要思路是根据分治法的原则,把一整串数据拆分多次,实现大事化小从而进行排序。
具体的思想为,从一串数据中选定一个标志数据,在一次排序之中,使其前面的数字都比起小,后面的数字都比其大,并对前面的数字串和后面的数字串执行程序,反复直至确定所有元素的位置。
3、不稳定性的理解
从思想中可以看出,每调用一次局部排序的函数,就有一个元素确定位置,因为递归的缘故,所以平均时间复杂度O(nlogn),且是一种不稳定的算法
因为对于相同的数字,如果把后面的数字作为标记,前面的数字判定为<=,后面的数字就应该与前面的数字交换位置,导致数字顺序颠倒成为不稳定的排序。(这里可能有人会想,如果相等不交换不就是变成稳定的了? 很遗憾,并不可以,如果相等的值不触发交换,那么就可能出现标记数前后都有与标记数等值的数字,那么排序就失败了)
一个例子(标红的6本来在后面)
3 9 1 6 6
经过排序后
3 1 6 9 6
4、对递归的理解
我给局部函数加了一个输出,就是每次调用函数时输出排序的数字在数组的位置,这样便于理解递归的调用。
void QuickSort(int A[],int p,int r){
int q = 0;
if(p < r){
q = PARTITION(A,p,r);
QuickSort(A,p,q-1);
QuickSort(A,q+1,r);
}
}
int PARTITION(int A[],int p,int r){
int x = A[r];
int i = p-1,j=p,temp;
for(j = p;j < r;j++){
if(A[j] <= x){
i++;
temp = A[j];
A[j] = A[i];
A[i] = temp;
}
}
temp = A[r];
A[r] = A[i+1];
A[i+1] = temp;
//添加的简单输出
printf("(%2d-%2d)",p,r);
for(j = p;j<=r;j++){
printf("%3d",A[j]);
}
printf("\n");
return i+1;
}
输出:
start: 3 9 1 6 5 98 15 47 11 24 35 12 4 78 36
( 0-14) 3 9 1 6 5 15 11 24 35 12 4 36 98 78 47
( 0-10) 3 1 4 6 5 15 11 24 35 12 9
( 0- 1) 1 3
( 3-10) 6 5 9 11 24 35 12 15
( 3- 4) 5 6
( 6-10) 11 12 15 24 35
( 6- 7) 11 12
( 9-10) 24 35
(12-14) 47 78 98
(13-14) 78 98
end : 1 3 4 5 6 9 11 12 15 24 35 36 47 78 98
递归的调用就是调用一个函数的时候,又再次调用自己,函数并未结束,再次调用的函数又会调用自己,直到达到结束标记。
以第一次排序为例子,先选定36位标记数,排序后再对左边0-10进行排序
选定4为标记数,排序再对左边0-1进行排序,此时左边结束,进行右边3-10排序,以此类推。