算法思想
取待排序元素的区间端点作为作为界值,然后通过交换使得待排序元素中所有大于界值的元素位于界值的一侧,
所有小于界值的元素位于界值的另一侧,然后在对界值左右两侧的待排序元素使用同样的方法进行排序,直到下一
次待排序区间只有一个元素时,则表示所有的元素排序完成,可以看出这种排序方式使用了分而治之的思想,个人
感觉快速排序是一种"折半排序";
下面以升序排序{4,5,8,2,3,9,7,1}这个待排序序列,图示如何根据界值进行划分:
开始状态如下:
划分过程如下:
元素5比元素4大所以不需要交换,j指向下一个元素,此时状态如下:
元素8比元素4大所有不需要交换,j指向下一个元素,此时状态如下:
2比4小,所以需要和下标为i+1的元素进行交换,也就是和下标为1的元素交换,然后j指向下一个元素,
此时状态如下:
3比4小,所以需要和下标为i+1的元素进行交换,也就是和下标为2的元素交换,然后j指向下一个元素,
接着是9比4大,无需交换,然后j指向下一个元素,此时状态如下:
接着是7比4大,无需交换,然后j指向下一个元素,此时状态如下:
1比4小,所以需要和下标为i+1的元素进行交换,也就是和下标为3的元素交换,然后j指向下一个元素,
此时j指向数组尾部,数组的元素分为两部分,一部分是比4大的,一部分是比4小的,将界值4和当前i所指元素进行
交换即可使,界值左侧的元素都小于4,,右侧元素都大于4:
对界值4左右两侧的元素分别进行上述操作,知道最后一次划分时,待排序区间只有一个元素时,此时整体已经是有
序的了
代码实现
int Partition(int * pPartOfUnSort, const int nStart, const int nEnd)
{
int iIndex = nStart;
int jIndex = nStart + 1;
int nTemp = 0;
for (; jIndex <= nEnd; jIndex++)
{
if (pPartOfUnSort[jIndex] < pPartOfUnSort[nStart])
{
iIndex++;
nTemp = pPartOfUnSort[iIndex];
pPartOfUnSort[iIndex] = pPartOfUnSort[jIndex];
pPartOfUnSort[jIndex] = nTemp;
}
}
nTemp = pPartOfUnSort[nStart];
pPartOfUnSort[nStart] = pPartOfUnSort[iIndex];
pPartOfUnSort[iIndex] = nTemp;
return iIndex;
}
void QuickSort(int * pUnSortAry, const int nStart, const int nEnd)
{
if (nStart > nEnd)
{
return;
}
int nIndexOfPivot = 0;
nIndexOfPivot = Partition(pUnSortAry, nStart, nEnd);
QuickSort(pUnSortAry, nStart, nIndexOfPivot - 1);
QuickSort(pUnSortAry, nIndexOfPivot + 1, nEnd);
}
时空复杂度分析
-
最差情况下的时间复杂度
当选取的界值是待排序中最大或者最小的数值时,那么这次的划分将会比较低效,因为所有元素都排在了界值的一
侧,如果待排序元素已经是有序的,或者是逆序的,或是差不多有序的,或者是差不多逆序的时,每次进行划分时,界
值只有一侧有数据,这种情况下快速排序效率较低,Partition函数的整体时间复杂度为Θ(n),QuickSort是一个递
归函数,在这种最差情况下,所有游元素都排在了界值的一侧,所以QuickSort内部的两个递归函数只有一个进行了
Partition函数中的for循环,另一个递归调用直接就返回了,所以其时间复杂度为T(0),所以在这种糟糕情况下,
QuickSort整体时间复杂度为:
T(n)=T(0)+T(n-1)+Θ(n)=T(n-1)+O(n)=O(n^2)
-
最好情况下的时间复杂度
当选取的界值划分后,界值左右两侧的元素个数差不多时,快速排序效率较高,此时QuickSort内部的两个递归函数
调用的时间复杂度均为T(n/2),此时快速排序的时间复杂度为:
T(n)=2T(n/2)+Θ(n)=nlgn
稳定性
因为快速排序的一次划分的过程中,最后一次是将界值和界值所在区间的另一端的元素交换,可能会造成相同元素
的相对位置发生改变所以,快速排序是不稳定的