一. 普通快速排序

找一个基准值base,然后一趟排序后让base左边的数都小于base,base右边的数都大于等于base。再分为两个子数组的排序。如此递归下去。

public class QuickSort {

    public static <T extends Comparable<? super T>> void sort(T[] arr) {
sort(arr, 0, arr.length - 1);
} public static <T extends Comparable<? super T>> void sort(T[] arr, int left, int right) {
if (left >= right) return;
int p = partition(arr, left, right);
sort(arr, left, p - 1);
sort(arr, p + 1, right);
} private static <T extends Comparable<? super T>> int partition(T[] arr, int left, int right) {
T base = arr[left];
int j = left;
for (int i = left + 1; i <= right; i++) {
if (base.compareTo(arr[i]) > 0) {
j++;
swap(arr, j, i);
}
}
swap(arr, left, j);
return j;//返回一躺排序后基准值的下角标
} public static void swap(Object[] arr, int i, int j) {
if (i != j) {
Object temp = arr[i];
arr[i] = arr[j];
arr[j] = temp;
}
} private static void printArr(Object[] arr) {
for (Object o : arr) {
System.out.print(o);
System.out.print("\t");
}
System.out.println();
} public static void main(String args[]) {
Integer[] arr = {3, 5, 1, 7, 2, 9, 8, 0, 4, 6};
printArr(arr);//3 5 1 7 2 9 8 0 4 6
sort(arr);
printArr(arr);//0 1 2 3 4 5 6 7 8 9
}
}

  

二. 快速排序优化:随机选取基准值base

在数组几乎有序时,快排性能不好(因为每趟排序后,左右两个子递归规模相差悬殊,大的那部分最后很可能会达到O(n^2))。

解决:基准值随机地选取,而不是每次都取第一个数。这样就不会受“几乎有序的数组”的干扰了。但是对“几乎乱序的数组”的排序性能可能会稍微下降,至少多了排序前交换的那部分,乱序时这个交换没有意义...有很多“运气”成分..

public class QuickSort {

    public static <T extends Comparable<? super T>> void sort(T[] arr) {
sort(arr, 0, arr.length - 1);
} public static <T extends Comparable<? super T>> void sort(T[] arr, int left, int right) {
if (left >= right) return;
int p = partition(arr, left, right);
sort(arr, left, p - 1);
sort(arr, p + 1, right);
} private static <T extends Comparable<? super T>> int partition(T[] arr, int left, int right) {
//排序前,先让基准值和随机的一个数进行交换。这样,基准值就有随机性。
//就不至于在数组相对有序时,导致左右两边的递归规模不一致,产生最坏时间复杂度
swap(arr,left,(int)(Math.random()*(right - left + 1)+left)); T base = arr[left];
int j = left;
for (int i = left + 1; i <= right; i++) {
if (base.compareTo(arr[i]) > 0) {
j++;
swap(arr, j, i);
}
}
swap(arr, left, j);
return j;//返回一躺排序后,基准值的下角标
} public static void swap(Object[] arr, int i, int j) {
if (i != j) {
Object temp = arr[i];
arr[i] = arr[j];
arr[j] = temp;
}
} private static void printArr(Object[] arr) {
for (Object o : arr) {
System.out.print(o);
System.out.print("\t");
}
System.out.println();
} public static void main(String args[]) {
Integer[] arr = {3, 5, 1, 7, 2, 9, 8, 0, 4, 6};
printArr(arr);//3 5 1 7 2 9 8 0 4 6
sort(arr);
printArr(arr);//0 1 2 3 4 5 6 7 8 9
}
}

  

三. 快速排序继续优化:配合着使用插入排序

快排是不断减小问题规模来解决子问题的,需要不断递归。但是递归到规模足够小时,如果继续采用这种 不稳定+递归 的方式执行下去,效率不见得会很好。

所以当问题规模较小时,近乎有序时,插入排序表现的很好。Java自带的Arrays.sort()里经常能看到这样的注释:“Use insertion sort on tiny arrays”,“Insertion sort on smallest arrays”

public class QuickSort {

    public static <T extends Comparable<? super T>> void sort(T[] arr) {
sort(arr, 0, arr.length - 1, 16);
} /**
* @param arr 待排序的数组
* @param left 左闭
* @param right 右闭
* @param k 当快排递归到子问题的规模 <= k 时,采用插入排序优化
* @param <T> 泛型,待排序可比较类型
*/
public static <T extends Comparable<? super T>> void sort(T[] arr, int left, int right, int k) {
// 规模小时采用插入排序
if (right - left <= k) {
insertionSort(arr, left, right);
return;
}
int p = partition(arr, left, right);
sort(arr, left, p - 1, k);
sort(arr, p + 1, right, k);
} public static <T extends Comparable<? super T>> void insertionSort(T[] arr, int l, int r) {
for (int i = l + 1; i <= r; i++) {
T cur = arr[i];
int j = i - 1;
for (; j >= 0 && cur.compareTo(arr[j]) < 0; j--) {
arr[j + 1] = arr[j];
}
arr[j + 1] = cur;
}
} private static <T extends Comparable<? super T>> int partition(T[] arr, int left, int right) {
//排序前,先让基准值和随机的一个数进行交换。这样,基准值就有随机性。
//就不至于在数组相对有序时,导致左右两边的递归规模不一致,产生最坏时间复杂度
swap(arr, left, (int) (Math.random() * (right - left + 1) + left)); T base = arr[left];
int j = left;
for (int i = left + 1; i <= right; i++) {
if (base.compareTo(arr[i]) > 0) {
j++;
swap(arr, j, i);
}
}
swap(arr, left, j);
return j;//返回一躺排序后,基准值的下角标
} public static void swap(Object[] arr, int i, int j) {
if (i != j) {
Object temp = arr[i];
arr[i] = arr[j];
arr[j] = temp;
}
} private static void printArr(Object[] arr) {
for (Object o : arr) {
System.out.print(o);
System.out.print("\t");
}
System.out.println();
} public static void main(String args[]) {
Integer[] arr = {3, 5, 1, 7, 2, 9, 8, 0, 4, 6};
printArr(arr);//3 5 1 7 2 9 8 0 4 6
sort(arr);
printArr(arr);//0 1 2 3 4 5 6 7 8 9
}
}

  

四. 快速排序继续优化:两路快排

在最开始的普通快速排序说过,让基准值base左边的都比base小,而base右边的都大于等于base。等于base的这些会聚集到右侧(或者稍微改改大小关系就会聚集到左侧)。总之就会聚集到一边。这样在数组中重复数字很多的时候,就又会导致两边子递归规模差距悬殊的情况。这时想把等于base的那些数分派到base两边,而不是让他们聚集到一起。

(注:测试代码的时候,最好把插入排序那部分注视掉,向我下面代码中那样...不然数据量小于k=16的时候执行的是插入排序.....)

public class QuickSort {

    public static <T extends Comparable<? super T>> void sort(T[] arr) {
sort(arr, 0, arr.length - 1, 16);
} /**
* @param arr 待排序的数组
* @param left 左闭
* @param right 右闭
* @param k 当快排递归到子问题的规模 <= k 时,采用插入排序优化
* @param <T> 泛型,待排序可比较类型
*/
public static <T extends Comparable<? super T>> void sort(T[] arr, int left, int right, int k) {
// 规模小时采用插入排序
// if (right - left <= k) {
// insertionSort(arr, left, right);
// return;
// } if (left >= right) return; int p = partition(arr, left, right);
sort(arr, left, p - 1, k);
sort(arr, p + 1, right, k);
} public static <T extends Comparable<? super T>> void insertionSort(T[] arr, int l, int r) {
for (int i = l + 1; i <= r; i++) {
T cur = arr[i];
int j = i - 1;
for (; j >= 0 && cur.compareTo(arr[j]) < 0; j--) {
arr[j + 1] = arr[j];
}
arr[j + 1] = cur;
}
} private static <T extends Comparable<? super T>> int partition(T[] arr, int left, int right) {
//排序前,先让基准值和随机的一个数进行交换。这样,基准值就有随机性。
//就不至于在数组相对有序时,导致左右两边的递归规模不一致,产生最坏时间复杂度
swap(arr, left, (int) (Math.random() * (right - left + 1) + left)); T base = arr[left];//基准值,每次都把这个基准值抛出去,看成[left+1.....right]左闭右闭区间的排序 int i = left + 1; //对于上一行提到的[left+1.....right]区间,i表示 [left+1......i)左闭右开区间的值都小于等于base。 int j = right;//对于上二行提到的[left+1.....right]区间,j表示 (j......right]左开右闭区间的值都大于等于base。 while (true) {
//从左到右扫描,扫描出第一个比base大的元素,然后i停在那里。
while (i <= right && arr[i].compareTo(base) < 0) i++; //从右到左扫描,扫描出第一个比base小的元素,然后j停在那里。
while (j >= left && arr[j].compareTo(base) > 0) j--; if (i > j) {//虽说是i>j,但其实都是以j=i-1为条件结束的
break;
}
swap(arr, i++, j--);
} swap(arr, left, j);
return j;//返回一躺排序后,基准值的下角标
} public static void swap(Object[] arr, int i, int j) {
if (i != j) {
Object temp = arr[i];
arr[i] = arr[j];
arr[j] = temp;
}
} private static void printArr(Object[] arr) {
for (Object o : arr) {
System.out.print(o);
System.out.print("\t");
}
System.out.println();
} public static void main(String args[]) {
Integer[] arr = {3, 5, 1, 7, 2, 9, 8, 0, 4, 6};
printArr(arr);//3 5 1 7 2 9 8 0 4 6
sort(arr);
printArr(arr);//0 1 2 3 4 5 6 7 8 9
}
}

  

五. 快速排序继续优化:两路快排 不用swap, 用直接赋值

上面的两路在找到大于base的值和小于base的值时,用的是swap()方法来进行交换。两数交换涉及到第三个变量temp的操作,多了读写操作。接下来用直接赋值的方法,把小于的放到右边,大于的放到左边,当i和j相遇时,那个位置就是base该放的地方。至此一趟完成。递归即可。

public class QuickSort {

    public static <T extends Comparable<? super T>> void sort(T[] arr) {
sort(arr, 0, arr.length - 1, 16);
} /**
* @param arr 待排序的数组
* @param left 左闭
* @param right 右闭
* @param k 当快排递归到子问题的规模 <= k 时,采用插入排序优化
* @param <T> 泛型,待排序可比较类型
*/
public static <T extends Comparable<? super T>> void sort(T[] arr, int left, int right, int k) {
// 规模小时采用插入排序
// if (right - left <= k) {
// insertionSort(arr, left, right);
// return;
// } if (left >= right) return; int p = partition(arr, left, right);
sort(arr, left, p - 1, k);
sort(arr, p + 1, right, k);
} public static <T extends Comparable<? super T>> void insertionSort(T[] arr, int l, int r) {
for (int i = l + 1; i <= r; i++) {
T cur = arr[i];
int j = i - 1;
for (; j >= 0 && cur.compareTo(arr[j]) < 0; j--) {
arr[j + 1] = arr[j];
}
arr[j + 1] = cur;
}
} private static <T extends Comparable<? super T>> int partition(T[] arr, int left, int right) {
//排序前,先让基准值和随机的一个数进行交换。这样,基准值就有随机性。
//就不至于在数组相对有序时,导致左右两边的递归规模不一致,产生最坏时间复杂度
swap(arr, left, (int) (Math.random() * (right - left + 1) + left)); T base = arr[left];//基准值,每次都把这个基准值抛出去,看成[left+1.....right]左闭右闭区间的排序 int i = left; //对于上一行提到的[left+1.....right]区间,i表示 [left+1......i)左闭右开区间的值都小于等于base。 int j = right;//对于上二行提到的[left+1.....right]区间,j表示 (j......right]左开右闭区间的值都大于等于base。 while (i < j) {
//从右到左扫描,扫描出第一个比base小的元素,然后j停在那里。
while (j > i && arr[j].compareTo(base) > 0) j--; arr[i] = arr[j]; //从左到右扫描,扫描出第一个比base大的元素,然后i停在那里。
while (i < j && arr[i].compareTo(base) < 0) i++; arr[j] = arr[i]; } arr[j] = base;
return j;//返回一躺排序后,基准值的下角标
} public static void swap(Object[] arr, int i, int j) {
if (i != j) {
Object temp = arr[i];
arr[i] = arr[j];
arr[j] = temp;
}
} private static void printArr(Object[] arr) {
for (Object o : arr) {
System.out.print(o);
System.out.print("\t");
}
System.out.println();
} public static void main(String args[]) {
Integer[] arr = {3, 5, 1, 7, 2, 9, 8, 0, 4, 6};
printArr(arr);//3 5 1 7 2 9 8 0 4 6
sort(arr); printArr(arr);//0 1 2 3 4 5 6 7 8 9
}
}

  

六. 快速排序继续优化:当大量数据,且重复数多时,用三路快排

把数组分为三路,第一路都比base小,第二路都等于base,第三路都大于base。

用指针从前到后扫描,如果:

1.cur指向的数小于base,那么:交换arr[cur]和arr[i]的值,然后i++,cur++。

2.cur指向的数等于base,  那么:cur++

3.cur指向的数大于base,那么:交换arr[cur]和arr[j]的值,然后j--。

当cur > j的时候说明三路都已经完成。

public class QuickSort {

    public static <T extends Comparable<? super T>> void sort(T[] arr) {
sort(arr, 0, arr.length - 1, 16);
} /**
* @param arr 待排序的数组
* @param left 左闭
* @param right 右闭
* @param k 当快排递归到子问题的规模 <= k 时,采用插入排序优化
* @param <T> 泛型,待排序可比较类型
*/
public static <T extends Comparable<? super T>> void sort(T[] arr, int left, int right, int k) {
// 规模小时采用插入排序
// if (right - left <= k) {
// insertionSort(arr, left, right);
// return;
// } if (left >= right) return;
int[] ret = partition(arr, left, right);
sort(arr, left, ret[0], k);
sort(arr, ret[1], right, k);
} public static <T extends Comparable<? super T>> void insertionSort(T[] arr, int l, int r) {
for (int i = l + 1; i <= r; i++) {
T cur = arr[i];
int j = i - 1;
for (; j >= 0 && cur.compareTo(arr[j]) < 0; j--) {
arr[j + 1] = arr[j];
}
arr[j + 1] = cur;
}
} /**
* @param arr 待排序的数组
* @param left 待排序数组的左边界
* @param right 待排序数组的右边界
* @param <T> 泛型
* @return
*/
private static <T extends Comparable<? super T>> int[] partition(T[] arr, int left, int right) {
//排序前,先让基准值和随机的一个数进行交换。这样,基准值就有随机性。
//就不至于在数组相对有序时,导致左右两边的递归规模不一致,产生最坏时间复杂度
swap(arr, left, (int) (Math.random() * (right - left + 1) + left)); T base = arr[left];//基准值,每次都把这个基准值抛出去,看成[left+1.....right]左闭右闭区间的排序 //三路快排分为下面这三个路(区间)
int i = left; // left表示,[lleft...left) 左闭右开区间里的数都比base小
int j = right;// left表示,(rright...right] 左开右闭区间里的数都比base大
int cur = i;//用cur来遍历数组。[left...cur)左闭右开区间里的数都等于base while (cur <= j) {
if (arr[cur].compareTo(base) == 0) {
cur++;
} else if (arr[cur].compareTo(base) < 0) {
swap(arr, cur++, i++);
} else {
swap(arr, cur, j--);
}
}
return new int[]{i - 1, j + 1};//[i...j]都等于base,子问题就只需要解决i左边和j右边就行了
} public static void swap(Object[] arr, int i, int j) {
if (i != j) {
Object temp = arr[i];
arr[i] = arr[j];
arr[j] = temp;
}
} private static void printArr(Object[] arr) {
for (Object o : arr) {
System.out.print(o);
System.out.print("\t");
}
System.out.println();
} public static void main(String args[]) {
Integer[] arr = {3, 5, 1, 7, 2, 9, 8, 0, 4, 6};
printArr(arr);//3 5 1 7 2 9 8 0 4 6
sort(arr); printArr(arr);//0 1 2 3 4 5 6 7 8 9
}
}

  

  

快速排序及优化(Java实现)的更多相关文章

  1. 怎么优化JAVA程序的执行效率和性能?

    现在java程序已经够快的了,不过有时写出了的程序效率就不怎么样,很多细节值得我们注意,比如使用StringBuffer或者StringBuilder来拼接或者操作字符串就比直接使用String效率高 ...

  2. Tomcat 优化 java.lang.OutOfMemoryError: Java heap space 的解决方法

    Tomcat 优化 java.lang.OutOfMemoryError: Java heap space 的解决方法 java.lang.OutOfMemoryError: Java heap sp ...

  3. jvm系列(十):如何优化Java GC「译」

    本文由CrowHawk翻译,是Java GC调优的经典佳作. 本文翻译自Sangmin Lee发表在Cubrid上的"Become a Java GC Expert"系列文章的第三 ...

  4. jvm系列(七):如何优化Java GC「译」

    本文由CrowHawk翻译,地址:如何优化Java GC「译」,是Java GC调优的经典佳作. Sangmin Lee发表在Cubrid上的”Become a Java GC Expert”系列文章 ...

  5. jvm系列(十):如何优化Java GC「

    转自:https://www.cnblogs.com/ityouknow/p/7653129.html 本文由CrowHawk翻译,地址:如何优化Java GC「译」,是Java GC调优的经典佳作. ...

  6. 【Java】 大话数据结构(15) 排序算法(2) (快速排序及其优化)

    本文根据<大话数据结构>一书,实现了Java版的快速排序. 更多:数据结构与算法合集 基本概念 基本思想:在每轮排序中,选取一个基准元素,其他元素中比基准元素小的排到数列的一边,大的排到数 ...

  7. 冒泡排序优化JAVA

    本文对传统的冒泡排序进行了一些优化,减少了循环次数. 时间复杂度 若文件的初始状态是正序的,一趟扫描即可完成排序.所需的关键字比较次数 C 和记录移动次数 M 均达到最小值: C(min)=n-1 , ...

  8. 如何优化 Java 性能?

    对于 Java 性能比较关心的同学大概都知道<Java Performance>这本书,一般而言,很多同学在日常写 Java Code 的时候很少去关心性能问题,但是在我们写 Code 的 ...

  9. 成为Java GC专家(3)—如何优化Java垃圾回收机制

    为什么需要优化GC 或者说的更确切一些,对于基于Java的服务,是否有必要优化GC?应该说,对于所有的基于Java的服务,并不总是需要进行GC优化,但前提是所运行的基于Java的系统,包含了如下参数或 ...

随机推荐

  1. ES6的介绍和常用语法

    本文最初发表于博客园,并在GitHub上持续更新前端的系列文章.欢迎在GitHub上关注我,一起入门和进阶前端. 以下是正文. 前言 ECMAScript 是 JS 的语言标准.而 ES6 是新的 J ...

  2. chmod 与大写 X

    chmod(1) 手册页中对权限位的描述中提及到 rwxXst 六个权限标记, rwx 是几乎所有 Linux 初学者都会学到的,更进者会了解到 st 两个标记,但 X 却少有提起.所以笔者大致了解了 ...

  3. Frequent Pattern (FP Growth算法)

    FP树构造 FP Growth算法利用了巧妙的数据结构,大大降低了Aproir挖掘算法的代价,他不需要不断得生成候选项目队列和不断得扫描整个数据库进行比对.为了达 到这样的效果,它采用了一种简洁的数据 ...

  4. 1000多个项目中的十大JavaScript错误以及如何避免

    通过统计数据库中的1000多个项目,我们发现在 JavaScript 中最常出现的错误有10个.下面会向大家介绍这些错误发生的原因以及如何防止. 对于这些错误发生的次数,我们是通过收集的数据统计得出的 ...

  5. Shell——数学计算

    shell中的赋值和操作默认都是字符串处理,在此记下shell中进行数学运算的几个特殊方法,以后用到的时候可以来看,呵呵1.错误方法举例 a) var=1+1 echo $var 输出的结果是1+1, ...

  6. 左连接条件与where条件的区别

    Sql 查询语句应用左连接时的链接条件中经常加一些常量值在里面如: "On a.id= b.id and b.is_del =0 and b.is_old =1" 这种条件如果加在 ...

  7. simhash类的使用

    首先感谢作者yanyiwu贡献的开源项目https://github.com/yanyiwu/simhash. 在做项目过程中,翻了一遍<这就是搜索引擎  核心技术详解>这本书的查重算法, ...

  8. 经典卷积神经网络(LeNet、AlexNet、VGG、GoogleNet、ResNet)的实现(MXNet版本)

    卷积神经网络(Convolutional Neural Network, CNN)是一种前馈神经网络,它的人工神经元可以响应一部分覆盖范围内的周围单元,对于大型图像处理有出色表现. 其中 文章 详解卷 ...

  9. CBitmap的使用

    MFC提供了位图处理的基础类CBitmap,可以完成位图(bmp图像)的创建.图像数据的获取等功能.虽然功能比较少,但是在对位图进行一些简单的处理时,CBitmap类还是可以胜任的.很多人可能会采用一 ...

  10. R语言︱数据分组统计函数族——apply族用法与心得

    每每以为攀得众山小,可.每每又切实来到起点,大牛们,缓缓脚步来俺笔记葩分享一下吧,please~ --------------------------- 笔者寄语:apply族功能强大,实用,可以代替 ...