快速排序和冒泡排序都属于交换类排序。即它也是通过不断比较和移动交换来实现排序的,只不过它的实现,增大了记录的比较和移动的距离,将关键字较大的记录从前面直接移动到后面,关键字较小的记录从后面直接移动到前面,从而减少了总的比较次数和移动交换次数。
快速排序(Quick Sort)的基本思想是:通过一趟排序将待排记录分割成独立的两部分,其中一部分记录的关键字均比另一部分记录的关键字小,则可分别对这两部分记录继续进行排序,以达到整个序列有序的目的。
/**
* 交换数组中的两个元素
*
* @param a 数组
* @param from 元素一的下标
* @param to 元素二的下标
*/
void swap(int[] a, int from, int to) {
int temp = a[from];
a[from] = a[to];
a[to] = temp;
}
/**
* 将待排序列分成以某枢轴分割的两个有序子序列
* 第一个子序列所有关键字均小于枢轴,第二个子序列所有关键字均大于枢轴
* {5,1,9,3,7,4,8,6,2} 取5为枢轴=> {2,1,4,3,5,7,8,6,9}
*
* @param a 待排序列
* @param low 序列起始地址
* @param high 序列结束地址
* @return
*/
int partition(int[] a, int low, int high) {
int pivotkey;
pivotkey = a[low]; /* 用子表的第一个记录作枢轴记录 */
while (low < high) { /* 从表的两端交替向中间扫描 */
while (low < high && a[high] >= pivotkey) {
high--;
}
swap(a, low, high); /* 将比枢轴记录小的记录交换到低端 */
while (low < high && a[low] <= pivotkey) {
low++;
}
swap(a, low, high); /* 将比枢轴记录大的记录交换到高端 */
}
return low; /* 返回枢轴所在位置 */
}
/**
* 快速排序(Quick Sort)
*
* @param a 待排序列
* @param low 序列起始地址
* @param high 序列结束地址
*/
void quickSort(int[] a, int low, int high) {
int pivot;
if (low < high) {
pivot = partition(a, low, high); /* 将待排序列一分为二,并算出枢轴值pivot */
quickSort(a, low, pivot); /* 对低子表递归排序 */
quickSort(a, pivot+1, high); /* 对高子表递归排序 */
}
}
//声明待排序列
int[] a = {5,1,9,3,7,4,8,6,2};
//测试调用
quickSort(a, 0, 8); //输出1,2,3,4,5,6,7,8,9
分析代码pivotkey = a[low],固定选取第一个关键字作为首个枢轴是一个潜在的性能瓶颈。排序速度的快慢取决于枢轴处在整个序列中的位置,枢轴太小或太大,都会影响性能。于是就有了**三数取中(median-of-three)法。即取三个关键字先进行排序,将中间数作为枢轴,一般是取左端、右端和中间三个数,**也可以随机选取。这样至少这个中间数一定不会是最小或者最大的数,从概率来说,取三个数均为最小或最大的可能性是微乎其微的,因此中间数位于较为中间的值的可能性就大大提高了。由于整个序列是无序状态,随机选取三个数和从左中右端取三个数其实是一回事,而且随机数生成器本身还会带来时间上的开销,因此随机生成不予考虑。
/**
* 将待排序列分成以某枢轴分割的两个有序子序列
* 第一个子序列所有关键字均小于枢轴,第二个子序列所有关键字均大于枢轴
* {5,1,9,3,7,4,8,6,2} 取5为枢轴=> {2,1,4,3,5,7,8,6,9}
*
* @param a 待排序列
* @param low 序列起始地址
* @param high 序列结束地址
* @return
*/
int partition(int[] a, int low, int high) {
int pivotkey;
/* 三数取中法选枢轴 */
int mid = low+(high-low)/2; /* 计算数组中间的元素下标 */
if (a[low] > a[high]) {
swap(a, low, high); /* 交换左端和右端数据,保证左端较小 */
}
if (a[mid] > a[high]) {
swap(a, mid, high); /* 交换中间和右端数据,保证中间较小 */
}
if (a[low] < a[mid]) {
swap(a, low, mid); /* 交换左端和中间的数据,保证左端较小 */
}
/* 此时,a[low]已经成为整个序列左中右三个关键字的中间值 */
pivotkey = a[low]; /* 用子表的第一个记录作枢轴记录 */
while (low < high) { /* 从表的两端交替向中间扫描 */
while (low < high && a[high] >= pivotkey) {
high--;
}
swap(a, low, high); /* 将比枢轴记录小的记录交换到低端 */
while (low < high && a[low] <= pivotkey) {
low++;
}
swap(a, low, high); /* 将比枢轴记录大的记录交换到高端 */
}
return low; /* 返回枢轴所在位置 */
}
/**
* 将待排序列分成以某枢轴分割的两个有序子序列
* 第一个子序列所有关键字均小于枢轴,第二个子序列所有关键字均大于枢轴
* {5,1,9,3,7,4,8,6,2} 取5为枢轴=> {2,1,4,3,5,7,8,6,9}
*
* @param a 待排序列
* @param low 序列起始地址
* @param high 序列结束地址
* @return
*/
int partition(int[] a, int low, int high) {
int pivotkey;
int temp; /* 申明一个临时存储空间 */
/* 三数取中法选枢轴 */
pivotkey = a[low]; /* 用子表的第一个记录作枢轴记录 */
temp = pivotkey; /* 将枢轴关键字备份到temp中 */
while (low < high) { /* 从表的两端交替向中间扫描 */
while (low < high && a[high] >= pivotkey) {
high--;
}
a[low] = a[high]; /* 采用替换而不是交换的方式进行操作 */
while (low < high && a[low] <= pivotkey) {
low++;
}
a[high] = a[low]; /* 采用替换而不是交换的方式进行操作 */
}
a[low] = temp; /* 将枢轴数值替换回a[low] */
return low; /* 返回枢轴所在位置 */
}
我们这边申明一个临时存储空间temp,然后在之前是swap时,只作替换的工作,最终当low与high会和,即找到枢轴的位置时,再将temp的数值赋值回a[low]。因为这当中少了多次交换数据的操作,在性能上又得到了部分的提高。
如果待排序列非常小,则快速排序反而不如直接插入排序来得更好(直接插入是简单排序中性能最好的)。其原因在于快速排序用到了递归操作,在大量数据排序时,这点性能影响相对于它的整体算法优势而言是可以忽略的,但如果数组只有几个记录需要排序时。这就成了一个大炮打蚊子的大问题。
/**
* 快速排序(Quick Sort)
*
* @param a 待排序列
* @param low 序列起始地址
* @param high 序列结束地址
*/
void quickSort(int[] a, int low, int high) {
int MAX_LENGTH_SORT = 7; /* 数组长度阀值 */
int pivot;
if ((high-low) > MAX_LENGTH_SORT) { /* 当high-low大于阀值时用快速排序 */
pivot = partition(a, low, high); /* 将待排序列一分为二,并算出枢轴值pivot */
quickSort(a, low, pivot); /* 对低子表递归排序 */
quickSort(a, pivot+1, high); /* 对高子表递归排序 */
} else { /* 当high-low小于等于阀值时用直接插入排序 */
insertSort(a);
}
}
大家知道,递归对性能是有一定影响的,quickSort函数在其尾部有两次递归操作。如果待排序的序列划分极端不平衡,递归深度将趋近于n,而不是平衡的log2n,这样就不仅仅是速度快慢的问题了。栈的大小是很有限的,每次递归调用都会耗费一定的栈空间,函数的参数越多,每次递归耗费的空间也越多。因此如果能减少递归,将会大大提高性能。
/**
* 快速排序(Quick Sort)
*
* @param a 待排序列
* @param low 序列起始地址
* @param high 序列结束地址
*/
void quickSort(int[] a, int low, int high) {
int MAX_LENGTH_SORT = 7; /* 数组长度阀值 */
int pivot;
if ((high-low) > MAX_LENGTH_SORT) { /* 采用快速排序 */
pivot = partition(a, low, high); /* 将待排序列一分为二,并算出枢轴值pivot */
quickSort(a, low, pivot); /* 对低子表递归排序 */
/* 采用迭代而不是递归,缩减堆栈深度,提高整体性能 */
low = pivot+1;
} else { /* 采用直接插入排序 */
insertSort(a);
}
}