快速排序是冒泡排序的升级版,都是通过不断交换数据来进行排序。快速排序的基本思想是:选出一个关键字,通过一次遍历,把比关键字小的数据都放到关键字左边,把比关键字大的数据都放到关键字的右边。然后在对左右两边依次进行快速排序。
由此可以看出,第一步关键字的选择很大程度上决定了快速排序的性能,如果关键字刚好选到了在中间左右的数据,那么经过一次排序后会把整段数据分成两段基本相等的数据,在进行下去效率就比较高;若果一不小心选择了最大或者最小的数据,那么一次排序下来相当于只排好了一个数据,相当不划算。为了提高性能,通常使用三数取中的方法来选取关键字,由于数据是随机排列的,可以分别在数组的左端、右端、和中间各取一个数,然后把最大的和最小的去掉,留下中间的值作为关键字,三个数同时都取得较大或者较小的概率比较小,因此可以接受。在部分情况下,还会用到九数取中的方法,就是分别取三组三个数,把三组的中数再作比较留下中间的值。
快速排序是一种不稳定的排序,其时间复杂度为O(nlogn),空间复杂度为O(logn)~O(n)。
//快速排序函数
//L[]为待排序序列,low为最低位的下标,high为最高位的下标
void QuickSort(int L[], int low, int high)
{
int middle = 0; //middle为关键字返回的下标
while (low < high) {
middle = Partition(L,low,high); //把关键字排好位置返回其下标
QuickSort(L, low, middle - 1); //对关键字左边的部分进行递归快速排序
low = middle + 1; //对关键字右边的部分进行递归快速排序
}
}
//将关键字排好顺序,这是快速排序中最重要的步骤
//L[]为待排序序列,low为最低位的下标,high为最高位的下标
int Partition(int L[],int low, int high)
{
int middlekey = 0;
//首先通过三数取中算法得到比较中值得关键字
int m = low + (high - low) / 2; //计算数组中间元素下标
if(L[low] > L[high])
{
swap(L, low, high); //保证高位得数比低位的大
}
if(L[m] > L[high])
{
swap(L, m, high); //保证高位得数比中间的的大
}
if (L[low] < L[m])
{
swap(L, low, m); //把三个数的中间值放在L[low]的位置上
}
middlekey = L[low];
L[0] = middlekey; //把选出来的中值备份到L[0]的位置上
while (low < high && L[high] >= middlekey) //当后面的值一直比中值打,则不用交换,high向前移动
{
high--;
}
//如果跳出while循环则代表高位的值比中值小,把高位的值覆盖到低位
L[low] = L[high]; //此函数返回的是中值的下标,因此数据替换掉没有关系
//对低位的处理和高位一样
while (low < high && L[low] <= middlekey)
{
low++;
}
L[high] = L[low];
L[low] = L[0];
return low;
}
快速排序的时间复杂度:
最坏的情况:所选的关键字每次都是最大值或者最小值,这样的话快速排序就成了冒泡排序,时间复杂度为O(n^2)
最优/平均情况:第一次调用partition将整个数组扫描一遍,做n次比较。递归logn次,所以时间复杂度为O(nlogn)
空间复杂度:
最坏情况:进行n - 1次递归调用,其空间复杂度为O(n)
平均情况:O(logn)