0.4 快速排序
算法思想:快速排序,顾名思义就是比较快速的排序。快速排序也是典型的使用分治思想的排序算法,但是快速排序的稳定性较低,在编码时如果犯下一些小错误会使快速排序的时间复杂度达到最坏O(N^2)。
快速排序的具体思路是将数组中的一个元素设置为“切分”,然后遍历数组,将比切分小的元素扔到切分左边,将比切分大的元素扔到切分右边,最后将切分左侧和右侧的数组分别排序,最后数组即为有序数。一般我们可以设置数组第一个元素为切分,找到切分后我们从左向右扫描数组,找到第一个大于切分的元素,然后再从右向左找到第一个小于切分的元素,交换他们,以此类推直到我们遍历完整个数组,分治中的分就完成了,下面我们用图例来进一步描绘“分”的过程。
切分方法如下
private static int partition(Comparable[] a, int low, int high){
//"切分"元素
Comparable v = a[low];
//定义左右指针
int i = low;
int j = high;
while (true){
while ( less(a[++i] , v)){
if ( i > high) {
break;
}
}
while (less(v, a[--j])){
if (j < low) {
break;
}
}
if (i >= j){
break;
}
exch(a, i, j);
}
//交换切分元素与左数组的最右面的元素
exch(a, low, j);
//返回切分下标
return j;
}
我们解决了切分后,快速排序就很好解决了,只要切分后在把左右数组切分,直到不能分就可以将数组排好序。
快速排序代码如下:
public class Quick {
/**
* 具体算法实现
*/
public static void sort(Comparable[] a) {
sort(a, 0, a.length-1);
}
public static void sort(Comparable[] a, int low, int high){
if (low >= high){
return;
}
int mid = partition(a, low, high);
sort(a, low ,mid-1);
sort(a, mid+1,high);
}
/**
* 切分函数,将数组按照中间值进行切分
* @param a
* @param low
* @param high
* @return
*/
private static int partition(Comparable[] a, int low, int high){
//"切分"元素
Comparable v = a[low];
//定义左右指针
int i = low;
//+1是为了配合下方的第一次--j
int j = high+1;
while (true){
while ( less(a[++i] , v)){
if ( i >= high) {
break;
}
}
while (less(v, a[--j])){
if (j <= low) {
break;
}
}
if (i >= j){
break;
}
exch(a, i, j);
}
//交换切分元素与左数组的最右面的元素
exch(a, low, j);
//返回切分下标
return j;
}
/**
* 对两个元素进行比较,如果v<w返回true
* @param v
* @param w
* @return
*/
private static boolean less(Comparable v ,Comparable w){
/**
* compareTo 方法
* 返回为正数表示v>w, 返回为负数表示v<w, 返回为0表示v==w
*/
return v.compareTo(w) < 0;
}
/**
* 交换a[i]和a[j]
* @param a
* @param i
* @param j
*/
private static void exch(Comparable[] a, int i, int j){
Comparable t = a[i];
a[i] = a[j];
a[j] = t;
}
/**
* 输出数组
* @param a
*/
private static void show(Comparable[] a){
for (int i = 0; i < a.length; i++) {
System.out.print(a[i] + " ");
}
System.out.println();
}
/**
* 测试数组是否有序
* @param a
* @return
*/
private static boolean isSort(Comparable[] a){
for (int i = 0; i < a.length; i++) {
if (less(a[i],a[i-1])) {
return false;
}
}
return true;
}
/**
* 主函数
* @param args
*/
public static void main(String[] args) {
String[] a = {"5","3","6","5","7","9","2","1","3","8"};
sort(a);
assert isSort(a);
show(a);
}
}
注意事项:
- 如果切分元素是数组的最大或最小的元素,要保证扫描指针不能越界
- 要考虑到数组中可能包含和切分元素相同的元素
- 左侧扫描最好在遇到大于等于切分元素时停下,右侧扫描最好在遇到小于等于切分元素时停下,这样虽然可能导致等值元素交换,但是可以避免算法运行时间变为平方级别
- 保证切分元素的正确位置,放置程序在切分元素正好是子数组的最大或最小元素时进入无限递归循环中。
特点:
- 算法是不稳定的
- 时间复杂度最低为O(NlgN)
- shi yong
0.4.1 快速排序的改进
- 小数组采用插入排序
根据上文希尔排序的经验,使用递归的方式进行排序的算法在小数组的时候使用插入排序可以提高效率。
- 三取样切分
-
什么是三取样切分,其实就是使用子数组的一小部分元素的中位数作为“切分”,这样做可以避免“切分”是最小或最大的元素,从而使切分的效果更好,但是也要付出计算中位数的代价。通过大量的实验证明,将取样大小设为3并用大小居中的元素切分的效果最好。
-
熵最优的排序同时还有一种情况就是我们需要排序的数组有大量的重复元素,而我们之前的算法即使遇到了重复数据依然会进行切分排序,但是这是不必要的。最简单的办法就是将我们的数组切分成三部分:“小于切分”“等于切分”“大于切分”。这样实现起来虽然比上方的二分法切分要复杂一点,但是更具有效率!具体实现思路如下:
假设我们需要排序的数组为a[]。我们在遍历数组的时候维护一个指针lt使得
a[low]~a[lt-1]
的元素都小于切分v;维护一个指针gt使得a[gt+1]~a[high]
的元素都大于切分v;维护一个指针i使得a[lt]~a[i-1]
的元素都等于切分v;
算法实现如下
public class Qucik3way {
/**
* 具体算法实现
*/
public static void sort(Comparable[] a) {
sort(a, 0 ,a.length-1);
}
public static void sort(Comparable[] a ,int low ,int high){
if (low >= high) {
return;
}
int lt = low;
int i = low + 1;
int gt = high;
Comparable v = a[low];
while (i <= gt) {
//比较当前元素和"切分"
int cmp = a[i].compareTo(v);
//如果当前元素小于"切分",则当前元素和a[lt]交换,同时i++。lt++。
if (cmp < 0) {
exch(a, lt++, i++);
}
//如果当前元素大于"切分",则当前元素和a[gt]交换,同时gt--。i不变
else if (cmp > 0) {
exch(a, i ,gt--);
}
//如果当前元素等于"切分",则不动,即i++。
else {
i++;
}
}
/**
* 此时
* a[low]~a[lt-1] < v
* a[lt]~a[gt] == v
* a[gt+1]~a[high-1] > v
*/
//对左右进行切分
sort(a ,low ,lt-1);
sort(a ,gt+1 ,high);
}
/**
* 对两个元素进行比较,如果v<w返回true
* @param v
* @param w
* @return
*/
private static boolean less(Comparable v ,Comparable w){
/**
* compareTo 方法
* 返回为正数表示v>w, 返回为负数表示v<w, 返回为0表示v==w
*/
return v.compareTo(w) < 0;
}
/**
* 交换a[i]和a[j]
* @param a
* @param i
* @param j
*/
private static void exch(Comparable[] a, int i, int j){
Comparable t = a[i];
a[i] = a[j];
a[j] = t;
}
/**
* 输出数组
* @param a
*/
private static void show(Comparable[] a){
for (int i = 0; i < a.length; i++) {
System.out.print(a[i] + " ");
}
System.out.println();
}
/**
* 测试数组是否有序
* @param a
* @return
*/
private static boolean isSort(Comparable[] a){
for (int i = 0; i < a.length; i++) {
if (less(a[i],a[i-1])) {
return false;
}
}
return true;
}
/**
* 主函数
* @param args
*/
public static void main(String[] args) {
String[] a = {"5","3","6","5","7","9","2","1","3","8"};
sort(a);
assert isSort(a);
show(a);
}
}
特点:
- 对于拥有大量重复数据的排序友好
- 算法运行时间与输入的N倍成正比,将算法从对数级别降低到了线性级别。