正如名字一样,快速排序是实践中最快的排序算法,平均运行时间为O(N log N)。最坏情形为O(N^2),但最坏情况很容易规避。
排序步骤如下:
1. 如果集合S中只有0个或1个元素,则返回。
2. 取S中任一元素p,称为轴(pivot)。
3. 将S - {p} 这个集合分成两个子集合,集合S1中的元素小于v,集合S2中的元素大于v。
4. 继续对子集合S1和S2进行如上3个步骤。
由以上步骤可知,快速排序的效率取决于轴元素的选取,如果每次都选到最大或者最小的元素作为轴,则效率低下。最理想的情况是每次都选到中位数作为轴,使得每次的子集合S1和S2中的元素数目都相等。
但是取到一组元素的中值会耗费很多时间,因此折中后最常使用的方法是三数中值分割法(Median-of-Three Partitioning)。即分别取第一个元素,最后一个元素,以及第N/2个元素,然后取这三个数的中间值作为轴。
排序的第一步是交换轴元素与最后一个元素的位置
输入数据:
8 1 4 9 6 3 5 2 7 0 S[i]=8,S[p]=6,S[j]=0
8 1 4 9 0 3 5 2 7 6 交换S[p]和S[j],然后令j指向S[p-1],因此S[i]=8,S[j]=7,此时S[p]为最后一个元素
然后将i右移,直到遇到不小于S[p]=6的元素
将j左移,直到遇到不大于S[p]的元素
此时如果i在j的左边,则交换i和j指向的元素值,然后继续上面移动
如果i在j的右边,则不再交换了,此时交换i和p指向的元素值,然后对i左边以及i右边的序列再分别做快速排序。
代码实现如下(以下实现并未使用median-of-three):
#include<iostream>
using namespace std;
void QuickSort(int input[], int begin, int end);
int main()
{
int list[] = {8, 7, 6, 5, 0, 3, 2, 1, 1};
QuickSort(list, 0, 8);
return 0;
}
void QuickSort(int list[], int begin, int end)
{
if (begin >= end)//由于未使用三数中值法,因此会存在begin<end的异常情况
;
else if (end - begin == 1) //如果只有2个元素,则直接比较,不做快排
{
if (list[begin] > list[end])
{
int tmp = list[end];
list[end] = list[begin];
list[begin] = tmp;
}
}
else
{
int pivot = (begin + end) / 2;//简单起见这里以序列中间的值代替三数中值法
int i, j;
i = list[pivot];//交换pivot和最后一个元素,三数中值此处应是和倒数第二个元素交换,因为最后一个元素肯定比pivot大
list[pivot] = list[end];
list[end] = i;
i = begin - 1;
j = end;//由于移位时用的是++i,因此这里i做了-1,j做了+1,不然会漏掉第一个和倒数第二个元素,用三数中值时则不存在这个问题,其i = begin,j = end - 1
while (true)
{
while (list[++i] < list[end]){}
while (list[--j] > list[end] && j >= begin){}
//这里j >= begin是考虑pivot取到最小值时的情况,三数中值法不存在这个问题
if (i < j) //如果i在j左边,则做交换
{
int tmp = list[i];
list[i] = list[j];
list[j] = tmp;
}
else //i在j右边,则排序结束
break;
}
int tmp = list[i];
list[i] = list[end];
list[end] = tmp;
//排序结束后需交换i和pivot的元素值
QuickSort(list, begin, i - 1);
QuickSort(list, i + 1, end);
}
}