看到一篇讲快排比较容易理解的文章,实在佩服!
快速排序是另一种排序算法,不像希尔排序,堆排序那样,要么以发明的人的名字命名,要么以使用的数据结构或特点命名,而快速排序直接嚣张的命名为快速排序!而实际上,快速排序确实是几个复杂度为O(nlogn)的排序算法中效率最高的一种,自然也就敢如此霸气的命名了。
快速排序的思想是:
1.先从数组中取出一个数作为基准数。
2.分区:将比这个数大的数全部放到它的右边,比他小的数全部放到它的左边。
3.对这个数的左右区间分别重复上述步骤,直到各个区间只有一个数。
快速排序使用的思想同归并排序一样,也是分治法,但是分治法说的显然有些模糊,大佬们总结出来的一个理解就是:挖坑填数+分治法,下面具体说明
假设有下面这样一个数组,取其第一个数a[0] = 72作为基准数
0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 |
72 | 6 | 57 | 88 | 60 | 42 | 83 | 73 | 48 | 85 |
初始时,设定两个索引i = 0; j = 9; x = a[0] = 72;
首先将第一个数72保存到了x中,可以认为是在数组a[0]的位置挖了一个坑,接下来可以将其他数填到这个坑里,从j开始向前找一个比x小的数,当j = 8时满足条件,将a[8]挖出填到a[0]上的坑,即a[0] = a[8],i++,这样a[0]这个坑就被搞定了,但是a[8]的地方形成了一个新的坑,很简单,从i的索引开始向后找一个大于x的数填过去就好,当i = 3时满足条件,将a[3]挖出填到上一个坑a[8]中,a[8] = a[3],j--,之后数组变为
0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 |
48 | 6 | 57 | 88 | 60 | 42 | 83 | 73 | 88 | 85 |
此时i = 3, j = 7, x = 72. a[3]处有一个坑。
重复上述步骤,先从后向前找,再从前向后找,从j开始找一个小于x的数,当j = 5时复合条件,将a[5]挖出填到上一个坑中,a[3] = a[5],i++,
从i开始向后找一个大于x的数,由于i = 5时i == j,循环退出,此时 i = j = 5,a[5]正好是上一个挖出的坑,将x的值填入a[5],此时数组变为
0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 |
48 | 6 | 57 | 42 | 60 | 72 | 83 | 73 | 88 | 85 |
观察这个数组,以a[5] = 72为分界线,72左边的数全部小于72,右边的数全部大于72,那么72当前所在的位置应该就是排序之后它最终的位置吧?因此接下来只需要对左边和右边的区间分别重复上述挖坑填数的步骤即可完成对整个数组的排序。
总结:
- 首先选取基准数,这里取的是数组的第一个数;
- 设置索引i和j分别指向数组的首和尾的位置;
- j--向前找小于基准数的数,找到后挖出这个数填到上一个坑a[i++]中;
- i++向后找大于基准数的数,找到后挖出这个数填到上一个坑a[j--]中;
- 当i == j 时循环结束,挖坑结束,将基准数填入坑a[i]中
- 对i左右两边的区间分别重复执行以上步骤
代码:
template<typename T>
int QSort(T k[], int low, int high) {
if (low < high) {
T temp = k[low];
int l = low, r = high;
while (low < high) {
while (low < high && k[high] >= temp)
high--;
if (low < high)
k[low++] = k[high];
while (low < high && k[low] <= temp)
low++;
if (low < high)
k[high--] = k[low];
}
k[low] = temp;
QSort(k, l, low - 1);
QSort(k, low + 1, r);
}
}
template<typename T>
void QuickSort(T k[], int len) {
QSort(k, 0, len - 1);
}
关于快速排序针对不同情况下的各种优化方法,下一节介绍。