Java排序实现的心得分享_java

1.概述
排序和查找是程序设计里的两类非常基本的问题,而现在也存在很多经典的算法用于解决这两类问题,本文主要对java中排序算法实现进行一个基本的探讨,希望能够起到抛砖引玉的作用。在此之前,首先问各位几个问题:你能写出一个正确的快排吗?快排在什么情况下真正的快?你的快排足够快吗?还可以进一步优化吗?带着这些问题,我们来看看jre7中快排是如何实现的吧。

Jre7中排序的实现类是DualPivotQuickSort.java,相比jre6有一些改变,主要发生在两个地方,一个是insertion sort的实现上,另一个是QuickSort实现中pivot从一个变成了两个。我们以int型的数组为例,该类中有个排序实现的核心方法,该方法的原型为

复制代码 代码如下:

void sort(int[] a, int left, int right, boolean leftmost)

参数a为需要排序的数组,left代表需要排序的数组区间中最左边元素的索引,right代表区间中最右边元素的索引,leftmost代表该区间是否是数组中最左边的区间。举个例子:
数组:[2, 4, 8, 5, 6, 3, 0, -3, 9]可以分成三个区间(2, 4, 8){5, 6}<3, 0, -3, 9>
对于()区间,left=0, right=2, leftmost=true
对于 {}区间, left=3, right=4, leftmost=false,同理可得<>区间的相应参数

当区间长度小于47时,该方法会采用插入排序;否则采用快速排序。

2. 插入排序实现
当leftmost为true时,它会采用传统的插入排序(traditional insertion sort),代码也较简单,其过程类似打牌时抓牌插牌:

复制代码 代码如下:

for (int i = left, j = i; i < right; j = ++i) {
                    int ai = a[i + 1];
                    while (ai < a[j]) {
                        a[j + 1] = a[j];
                        if (j-- == left) {
                            break;
                        }
                    }
                    a[j + 1] = ai;
                }

传统插入排序代码
当leftmost为false时,它采用一种新型的插入排序(pair insertion sort),改进之处在于每次遍历前面已排好序的数组需要插入两个元素,而传统插入排序在遍历过程中只需要为一个元素找到合适的位置插入。对于插入排序来讲,其关键在于为待插入元素找到合适的插入位置,为了找到这个位置,需要遍历之前已经排好序的子数组,所以对于插入排序来讲,整个排序过程中其遍历的元素个数决定了它的性能。很显然,每次遍历插入两个元素可以减少排序过程中遍历的元素个数,其实现代码如下:

复制代码 代码如下:

for (int k = left; ++left <= right; k = ++left) {
                    int a1 = a[k], a2 = a[left];

                    if (a1 < a2) {
                        a2 = a1; a1 = a[left];
                    }
                    while (a1 < a[--k]) {
                        a[k + 2] = a[k];
                    }
                    a[++k + 1] = a1;

                    while (a2 < a[--k]) {
                        a[k + 1] = a[k];
                    }
                    a[k + 1] = a2;
                }

现在有个问题:为什么最左边的区间采用传统插入排序,其他的采用成对插入排序呢?加入用上述成对插入排序代码替换传统插入排序代码,会出现什么问题呢?期待大家自己来回答。。。
3. 快速排序实现
Jre7中对快速排序也做了改进,传统的快速排序是选取一个pivot(jre6种选取pivot的方法是挑选出数组最左边,中间和最右边位置的元素,将其中数值大小排在中间的元素作为pivot),然后分别从两端向中间遍历,把左边遍历过程中遇到的大于pivot的值和右边遍历中遇到的小于等于pivot的值进行交换,当遍历相遇后,插入pivot的值;这样就使得pivot左边的值均小于或等于pivot,pivot右边的值大于pivot;接下来再采用递归的方式对左边和右边分别进行排序。

通过上述分析,我们可以看到:插入排序的每一步是使数组的一个子区间绝对有序,而每一次循环的本质是使这个子区间不断扩大,所以我们可以看到其优化的方向是使每次循环遍历尽可能的使子区间扩大的速度变快,所以上面把每次遍历插入一个元素优化成每次插入两个元素。当然肯定有人会问,那为什么不把这个数字变得更大一点呢?比如每次遍历插入5个,10个。。。很显然,这样是不行,它的一个极端情况就是每次遍历插入n个(n为数组长度)。。。至于为什么,大家自己回答吧。

对于快速排序来讲,其每一次递归所做的是使需要排序的子区间变得更加有序,而不是绝对有序;所以对于快速排序来说,其性能决定于每次递归操作使待排序子区间变得有序的程度,另一个决定因素当然就是递归次数。快速排序使子区间变得相对有序的关键是pivot,所以我们优化的方向也应该在于pivot,那就增加pivot的个数吧,而且我们可以发现,增加pivot的个数,对递归次数并不会有太大影响,有时甚至可以使递归次数减少。和insert sort类似的问题就是,pivot增加为几个呢?很显然,pivot的值也不能太大;记住,任何优化都是有代价的,而增加pivot的代价就隐藏在每次交换元素的位置过程中。关子貌似卖的有点大了。。。下面我们就来看看pivot的值为2时,快速排序是如何实现的吧。其实现过程其实也不难理解:
1.  首先选取两个pivot,pivot的选取方式是将数组分成近视等长的六段,而这六段其实是被5个元素分开的,将这5个元素从小到大排序,取出第2个和第4个,分别作为pivot1和pivot2;
2.  Pivot选取完之后,分别从左右两端向中间遍历,左边遍历停止的条件是遇到一个大于等于pivot1的值,并把那个位置标记为less;右边遍历的停止条件是遇到一个小于等于pivot2的值,并把那个位置标记为great
3.  然后从less位置向后遍历,遍历的位置用k表示,会遇到以下几种情况:
a.  k位置的值比pivot1小,那就交换k位置和less位置的值,并是less的值加1;这样就使得less位置左边的值都小于pivot1,而less位置和k位置之间的值大于等于pivot1
b.  k位置的值大于pivot2,那就从great位置向左遍历,遍历停止条件是遇到一个小于等于pivot2的值,假如这个值小于pivot1,就把这个值写到less位置,把less位置的值写道k位置,把k位置的值写道great位置,最后less++,great--;加入这个值大于等于pivot1,就交换k位置和great位置,之后great—
4.  完成上述过程之后,带排序的子区间就被分成了三段(<pivot1, pivot1<=&&<=pivot2,>pivot2),最后分别对这三段采用递归就行了。

复制代码 代码如下:

/*
             * Partitioning:
             *
             *   left part           center part                   right part
             * +--------------------------------------------------------------+
             * |  < pivot1  |  pivot1 <= && <= pivot2  |    ?    |  > pivot2  |
             * +--------------------------------------------------------------+
             *               ^                          ^       ^
             *               |                          |       |
             *              less                        k     great
             *
             * Invariants:
             *
             *              all in (left, less)   < pivot1
             *    pivot1 <= all in [less, k)     <= pivot2
             *              all in (great, right) > pivot2
             *
             * Pointer k is the first index of ?-part.
             */

Jre7中对排序实现的核心内容就如上所述,具体细节可参见相应类中的代码,如发现错误或不妥之处,望指正。

时间: 2024-11-05 18:43:42

Java排序实现的心得分享_java的相关文章

Java排序算法总结之插入排序_java

本文实例讲述了Java插入排序方法.分享给大家供大家参考.具体分析如下: 有一个已经有序的数据序列,要求在这个已经排好的数据序列中插入一个数,但要求插入后此数据序列仍然有序,这个时候就要用到插入排序法.本文主要介绍的是插入排序的java实现.   插入排序的基本操作就是将一个数据插入到已经排好序的有序数据中,从而得到一个新的.个数加一的有序数据.比较和交换的时间复杂度为O(n^2),算法自适应,对于数据已基本有序的情况,时间复杂度为O(n),算法稳定,开销很低.算法适合于数据已基本有序或者数据量

Java排序算法总结之冒泡排序_java

本文实例讲述了Java排序算法总结之冒泡排序.分享给大家供大家参考.具体分析如下: 前言:冒泡排序(BubbleSort)就是依次比较相邻的两个数,将小数放在前面,大数放在后面. 下面让我们一起    来看冒泡排序在Java中的算法实现. 冒泡排序是计算机的一种排序方法,它的时间复杂度为O(n^2),虽然不及堆排序.快速排序的O(nlogn,底数为2),但是有两个优点: 1."编程复杂度"很低,很容易写出代码: 2.具有稳定性,这里的稳定性是指原序列中相同元素的相对顺序仍然保持到排序后

5种java排序算法汇总工具类_java

工具类简单明了地总结了java的快速排序,希尔排序,插入排序,堆排序,归并排序五种排序算法,代码中并没有对这几种排序算法的一个说明,关于思想部分希望在自行查阅相关说明,这里只是对这几种算法进行一个概括,以供大家使用. public class Sort { public static <AnyType extends Comparable<? super AnyType>> void insertionSort(AnyType[] a) { insertionSort(a, 0,

Java多线程yield心得分享_java

一. Thread.yield( )方法: 使当前线程从执行状态(运行状态)变为可执行态(就绪状态).cpu会从众多的可执行态里选择,也就是说,当前也就是刚刚的那个线程还是有可能会被再次执行到的,并不是说一定会执行其他线程而该线程在下一次中不会执行到了. Java线程中有一个Thread.yield( )方法,很多人翻译成线程让步.顾名思义,就是说当一个线程使用了这个方法之后,它就会把自己CPU执行的时间让掉,让自己或者其它的线程运行. 打个比方:现在有很多人在排队上厕所,好不容易轮到这个人上厕

linux下执行java程序的sh脚本分享_java

今天大概耗费了快一天的时间研究怎么用脚本执行java程序,终于实现了,分享一下 (1)在linux下写一个.sh文件 (2)文件内容如下: 复制代码 代码如下: #!/bin/sh         //bash文件头 APP_HOME=/home/blmcrm/crm/A      //要执行的java文件中bin文件的上一个目录,我的目录是/home/blmcrm/crm/A/bin/blm......(后面不写了),总之就是写bin目录前面的部分,因为jar包在bin目录里面,如果不在bin

java的arrays数组排序示例分享_java

Java API对Arrays类的说明是:此类包含用来操作数组(比如排序和搜索)的各种方法. 1.对基本数据类型的数组的排序 说明: (1)Arrays类中的sort()使用的是"经过调优的快速排序法"; (2)比如int[],double[],char[]等基数据类型的数组,Arrays类之只是提供了默认的升序排列,没有提供相应的降序排列方法. (3)要对基础类型的数组进行降序排序,需要将这些数组转化为对应的封装类数组,如Integer[],Double[],Character[]等

java图片添加水印实例代码分享_java

本文为大家介绍了java图片添加水印实例代码,java实现水印还是非常方便的,水印可以是图片或者文字,具体内容如下 package michael.io.image; import java.awt.AlphaComposite; import java.awt.Graphics2D; import java.awt.Image; import java.awt.RenderingHints; import java.awt.image.BufferedImage; import java.io

java实现动态代理示例分享_java

复制代码 代码如下: import java.lang.reflect.InvocationHandler;import java.lang.reflect.Method;import java.lang.reflect.Proxy; public class LogHandler implements InvocationHandler {    private Object delegate;     public Object bind(Object delegate) {       

java生成随机数(字符串)示例分享_java

用来生成简单的随机java生成随机数,大小+数字.没特符 复制代码 代码如下: package passwords;import java.util.Random;public class pwdGen { private Random rdseed=new Random();  /**  *@param  *length  password length;  *@param  *letters  boolean non-capital letters combination control;