一、关于快速排序的思想
快速排序是一种分治的思想,它通过一趟排序将待排序记录分割成独立的两个部分,其中的一部分关键字均比另一部分的关键字小,再分别对这两部分记录继续进行排序,以便达到整个序列有序的目的。
二、快速排序的代码(来源于大话数据结构)
#include <iostream>
#include <initializer_list>
#define MAXSIZE 10000
#define TRUE 1
#define FALSE 0
#define MAX_LENGTH_INSERT_SORT 7
typedef int Status;
struct SqList
{
int r[MAXSIZE+1]= {0, 50, 10, 90, 30, 70, 40, 80, 60, 20};
int length = 9;
};
void swap(SqList &L, int i, int j)
{
int temp = L.r[i];
L.r[i] = L.r[j];
L.r[j] = temp;
}
//插入排序,用于在数据量小于某个值时的备选排序方法
void InsertSort(SqList &L)
{
int i, j;
for(i=2; i<=L.length; ++i)
{
if(L.r[i]<L.r[i-1])
{
L.r[0] = L.r[i];
L.r[i] = L.r[i-1];
for(j=i-2; L.r[j]>L.r[0]; --j)
L.r[j+1] = L.r[j];
L.r[j+1] = L.r[0];
}
}
}
int Partition(SqList &L, int low, int high);
void QSort(SqList &L, int low, int high);
void QuickSort(SqList &L)
{
QSort(L, 1, L.length);
}
void QSort(SqList &L, int low, int high)
{
int pivot;
if(L.length>MAX_LENGTH_INSERT_SORT)
{
while(low<high)
{
pivot = Partition(SqList &L, int low, int high);
QSort(L, low, pivot-1); //对低子表递归排序
//QSort(L, pivot+1, high);
low = pivot+1; //尾递归,用于缩减递归堆栈深度
}
}
else
{
InsertSort(L);
}
}
int Partition(SqList &L, int low, int high)
{
int pivotkey;
//取左端、中间以及右端三个数中的中间值作为枢轴,避免出现极端分布的情况
int m = (low+high)/2;
if(L.r[low]>L.r[high])
swap(L, low, high);
if(L.r[m]>L.r[high]);
swap(L, m, high);
if(L.r[low]<L.r[m])
swap(L, low, m);
pivotkey = L.r[low];
L.r[0] = pivotkey;
while(low<high)
{
while(low<high && L.r[high]>=pivotkey)
--high;
L.r[low] = L.r[high];
while(low<high && L.r[low]<=pivotkey)
++low;
L.r[high] = L.r[low];
}
L.r[low] = L.r[0];
return low;
}
int main()
{
SqList L;
QuickSort(L);
for(int i=1; i<=L.length; ++i)
std::cout << L.r[i] << " ";
}
三、关于优化后的代码
1、优化了Partition函数中枢轴的选取,采用三数取中的方法,避免因为开始选取的pivotkey值过大或者过小,而导致分割成的两部分元素个数相差过大,最后得到的递归树深度过深,从而使得时间复杂度和空间复杂度不够理想;
2、增加了直接插入排序作为备选,快速排序适合用于数据量较大的情况,而直接插入排序适合用于数据量较小的情况,故增加备选,以保证不同数据量的处理也能得到较好的性能;
3、优化了递归操作,将原本的QSort函数尾部的两次递归操作改变为尾递归优化,采用迭代而非递归,以便缩减堆栈深度,从而提高性能。(此处本人还存在疑惑,需要进一步学习)
四、关于时间复杂度和空间复杂度
在最优的情况下,Partition每次划分得很均匀,假设排序的关键字为n个,则此时的时间复杂度为O(nlogn);在最坏的情况下,待排序的序列为正序或者逆序,每次划分只得到比上一次划分少一个记录的子序列,另一个子序列为空,此时需要执行n-1次递归调用,且第i次划分需要经过n-i次关键字的比较才能找到第i个记录,即枢轴的位置,故总的比较次数为n-1+n-2+…+1=n(n-1)/2,其最终的时间复杂度为O(n^2)。
经过数学归纳法等证明,平均情况下,时间复杂度为O(nlogn)。
关于空间复杂度,主要是由于递归造成的栈空间的使用,最好的情况下,递归树的深度为log2 n,即空间复杂度为O(logn),最坏的情况下,需要进行n-1次递归调用,空间复杂度为O(n),平均情况下,空间复杂度为O(logn)。
五、稳定性
由于关键字的比较和交换为跳跃式的,故快速排序是一种不稳定的排序方法。