C语言快速排序算法及三种优化方式
1. 原理
快速排序的基本思想就是从一个数组中任意挑选一个元素(通常来说会选择最左边的元素)作为中轴元素,将剩下的元素以中轴元素作为比较的标准,将小于等于中轴元素的放到中轴元素的左边,将大于中轴元素的放到中轴元素的右边,然后以当前中轴元素的位置为界,将左半部分子数组和右半部分子数组看成两个新的数组,重复上述操作,直到子数组的元素个数小于等于1。
2. 快速排序复杂度分析
2.1 时间复杂度
- 在最好情况下,快速排序算法的时间复杂度为O(nlogn)
- 在最坏情况下,待排序的序列为正序或者逆序,每次划分只得到一个臂上次划分少一个记录的子序列,而且另一个序列还为空。如果递归树画出来,它就是一棵斜树。此时需要执行(n-1)次递归调用,且第i次划分需要经过i-1次关键字的比较才能找到第i个记录,即中轴元素的位置,因此比较次数为:n-1+n-2+n-3+…+1 = n*(n-1)/2 时间复杂度为O(n^2)
2.2 空间复杂度
- 在最好情况下,递归树的深度为logn,其时间复杂度为O(logn)
- 在最坏情况下,需要进行n-1次递归调用,其空间复杂度为O(n)
3. 快速排序代码实现
3.1 普通快速排序
普通快速排序代码如下
/***************************
Author:tmw
date:2017-11-5
*************************/
#include <stdio.h>
#include <stdlib.h>
/**普通快速排序**/
//选取序列中的第一个元素作为中轴元素
int first_ele_fast_sort( int array[] , int low , int high )
{
int target = array[low];//选取最左边元素为中轴
while( low < high )
{
while( low < high && array[high] >= target )
high--;//high为数组最右边下标
//交换
int temp;
temp = array[high];
array[high] = array[low];//当前low的位置存的是target值
array[low] = temp;
while( low < high && array[low] <= target )
low++;
//交换
temp = array[low];
array[low] = array[high];//经上一轮交换,当前high位置存的是target值
array[high] = temp;
}
return low;//返回当前中轴元素下标值
}
int* first_ele_fast_sort_all( int array[] , int low , int high )//这里的high为array_len-1
{
if( low < high )
{
int target_index;
target_index = first_ele_fast_sort(array,0,high);//数组元素从0下标开始存
first_ele_fast_sort(array,0,target_index-1);//对中轴元素左边快排
first_ele_fast_sort(array,target_index+1,high);//对中轴元素的右边快排
}
return array;
}
3.2 快速排序优化1—-三数取中&&优化不必要的交换
优化点:
优化选取中轴元素
以上代码target 选取的位置是认定了数组元素的首位,但是若这个数值的大小不在整个数组的中间位置,会大大降低快排的性能。target = array[low] 这句就成了一个潜在的性能瓶颈。因此快速排序的速度还取决于这个target关键元素在数组中的位置。
【改进方法】
三数取中法:去三个元素先进行排序,将中间数作为中轴元素,下面的代码选取数组的左、中、右三个数;
优化交换
将上一个代码中的交换改成直接赋值,减少了多次交换数据的操作,在性能上又得到了部分提高
优化1的快速排序代码如下:
/*************************
Author:tmw
date:2017-11-5
**************************/
#include <stdio.h>
#include <stdlib.h>
/**快速排序优化----三数取中&&优化不必要的交换**/
//取三个元素先进行排序,将中间值作为中轴元素,选取左端、右端、中间三个数,也可以随机选取
int Three_choose_one_optimize_fastSort( int array[] , int low , int high )
{
int mid = ( low + high ) / 2;
if( array[high] < array[low] )
{
int temp1;
temp1 = array[high];
array[high] = array[low];
array[low] = temp1;
}
if( array[high] < array[mid] )
{
int temp2;
temp2 = array[high];
array[high] = array[mid];
array[mid] = temp2;
}//以上,保证high为三个值当中最大值的下标
if( array[mid] > array[low] )//将low作为中间值的下标
{
int temp3;
temp3 = array[low];
array[low] = array[mid];
array[mid] = temp3;
}//以上,完成了三数取中,此时array[low]即可作为快排优化后的中轴元素
int target = 0;
target = array[low];
while( low < high )
{
while( low < high && array[high] >= target )
high--;
array[low] = array[high];//此时high位置上的元素为待处理元素
while( low < high && array[low] <= target )
low++;
array[high] = array[low];//将大于target值的元素放到待处理的high位置上,那么此时,low位置变为待处理
}
array[low] = target; //以上,省去交换,变为“挖坑赋值”,降低时间复杂度
return low;
}
void fast_sort( int array[] , int low , int high )
{
if( low < high )
{
int index;
index = Three_choose_one_optimize_fastSort(array,low,high);
fast_sort(array,low,index-1);
fast_sort(array,index+1,high);
}
}
3.3 快速排序优化2—-优化递归操作:
递归对性能是有一定影响的,快排在其尾部有两次递归操作,如果待排序的序列划分极端不平衡,递归深度将趋近于n,而不是平衡时的logn。这就不仅仅是速度快慢的问题了,栈的大小是有限的,每次递归调用都会耗费一定的栈空间,函数的参数越多,每次递归耗费的空间也越多,因此,减少递归会大大提高性能
- 下面是用一次迭代来换取一次递归的代码,此代码主要对快排的递归部分修改,中轴元素的选取和排序部分代码沿用优化1方案的。
/*************************
Author:tmw
date:2017-11-5
**************************/
#include <stdio.h>
#include <stdlib.h>
int Three_choose_one_optimize_fastSort( int array[] , int low , int high )
{
int mid = ( low + high ) / 2;
int temp;
if( array[low] > array[high] )
{
temp = array[low];
array[low] = array[high];
array[high] = temp;
}
if( array[mid] > array[high] )
{
temp = array[high];
array[high] = array[mid];
array[mid] = temp;
}//以上,high位置的元素为三者中的最大
if( array[low] < array[mid] )
{
temp = array[low];
array[low] = array[mid];
array[mid] = temp;
}//以上,low位置的元素为三者中第二大元素
int target = array[low];
while( low < high )
{
while( low < high && array[high] > target )
high--;
array[low] = array[high];
while( low < high && array[low] < target )
low++;
array[high] = array[low];
}
array[low] = target;
return low;
}
void interate_replace_one_recursion_fastSort( int array[] , int low , int high )
{
int index;
while( low < high )//迭代换递归,这里从if换成while
{
index = Three_choose_one_optimize_fastSort(array,low,high);
interate_replace_one_recursion_fastSort(array,low,index-1);
low = index+1;
}
}
3.4 快速排序优化3—-优化小数组时的排序方案
以上两种优化方法是充分利用了快速排序解决大数组排序强大的功能,那么相反的情况,当数组非常小,其实快速排序不如直接插入排序来的好。
本质原因还是因为快速排序用到了递归,在大量数据排序时,这点性能影响可以通过上面的优化方法忽略掉,但是对于一个只有几个元素的数组需要排序时,用快排就像“大炮打蚊子”。
解决方案就是设定一个判断分支:
当数组长度大于7,则使用优化2的快排
当数组长度小于7,则使用直接插入排序
(有资料认为7这个设定比较合理,也有人认为50合理,不论多少,有个这个思想就行啦~~~)