java快速排序理解及实现

快速排序

快速排序是对冒泡排序的一种改进, 它是不稳定的。由C. A. R. Hoare在1962年提出的一种划分交换排序,采用的是分治策略(一般与递归结合使用),以减少排序过程中的比较次数,它的最好情况O(nlogn),最坏情况O(n^2),平均时间复杂度为O(nlogn)。

基本思想

快速排序使用分治法策略来把一个序列分为两个子序列

1.将未排序序列看成一个元素的集合

2.在集合中选出一个主元

3.通过主元,将集合元素分为两个子集(子集:如果集合A的任意一个元素都是集合B的元素,那么集合A称为集合B的子集)

  • 左边的子集元素均小于主元

  • 右边的子集元素均大于主元

4.在子集中递归地执行上述操作,直到子集为空、或者仅有1个元素为止
在这里插入图片描述

实现过程

每一趟排序中找一个点pivot,将表分割成独立的两部分,其中一部分的所有都比pivot小,另一部分比pivot大,然后再按此方法对这两部分数据分别进行快速排序。为了方便,我们这里通常选择数组的第一个元素作为主元,再与最后一个元素交换。

  • 设置两个下标变量i,j,i指向第一个元素,j指向倒数第二个元素

  • 先让j从右到左扫描,如果发现比主元小的元素,则停止;然后让i从左到右扫描,如果发现比主元大的元素,则停止

  • 如果i <= j,则交换i和j所在的元素

  • 重复上两步,直到i > j为止,最后交换主元和i所在的元素

在这里插入图片描述
代码实现:

public class QuickSort {
    public static void main(String[] args) {
        int[] array = {6, 2, 4, 8, 9, 5, 7, 3, 1, 10};
        System.out.println("排序之前的数组: " + Arrays.toString(array));
        quickSort(array, 0, array.length - 1);
        System.out.println("排序之后的数组: " + Arrays.toString(array));
    }
    public static void quickSort(int[] array,int start,int end){
        if (start<end){
            //选取数组第一个数做主元
            int pivot = array[start];
            int i = start;
            int j = end;
            while (i<j) {
                //j从右往左扫描
                while ((i<j)&&array[j]>=pivot) {
                    j--;
                }
                //i从左往右扫描
                while ((i<j)&&array[i]<=pivot) {
                    i++;
                }
                if((array[i]==array[j])&&(i<j)){
                    i++;
                }
                else {
                    int temp = array[i];
                    array[i] = array[j];
                    array[j] = temp;
                }
            }
            //将主元为与i的位置交换
            array[start]=array[i];
            array[i]=pivot;
            quickSort(array,start,j-1);
            quickSort(array,j+1,end);
        }else{
            return;
        }
    }
}

输出的排序结果为:

排序之前的数组: [6, 2, 4, 8, 9, 5, 7, 3, 1, 10]
排序之后的数组: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]

,这里基本实现了快速排序,还有许多优化和改进的地方。下面从几个方面来继续寻找更好的排序方式

主元的选择

如果随意选择一个主元,可能造成两个子集大小相差悬殊

在这里插入图片描述
我们期望的是每次选择主元都能把集合平均分成两个大致的子集合,但世界上做不到,比较好的方法是三数中值分割法(取最开始、最后、中间三个元素的中值作为主元)

  • 将三个元素调整为: 最左元≤中值 ≤最右元。

  • 然后让中值与Right-1交换(因为最右元必然大于中值,无需再比较)


相等的情形

如果遇到相等的元素不停止,在特殊情况下
在这里插入图片描述

因此我们选择遇到相等元素时,停止并交换。虽然多了很多无谓的交换,但可以让两个子集大小均衡。


另外,若集合元素很少,快速排序递归求解效率很低,甚至比插入排序低很多因此,在递归过程中,若集合元素个数少于某个值,就使用插入排序

下面是改进后的代码:

/**
     * 选择排序
     * @param array
     * @param start
     * @param end
     */
    public static void quickSort(int[] array, int start, int end) {
        //数组元素不小于cutoff时才使用快速排序
         int cutoff=3;
         if(start+cutoff<=end){
        //主元,位置为right-1
        int privot = median3(array, start, end);
        int i = start, j = end - 1;
        for (; ; ) {
            while (array[++i] < privot) {
            }//i向右遍历
            while (array[--j] > privot) {
            }//j向左遍历
            if (i < j)
                swap(array, i, j);
            else {
                break;
            }
        }
        //for循环终止条件为i和j相遇,此时再将主元归位
        swap(array, i, (end - 1));
        quickSort(array, start, i - 1);//对左半部进行递归
        quickSort(array, i + 1, end);//对右半部进行递归
            }else{
                //可以使用插入排序
                array=insertionSort(array);
            }
    }

插入排序代码:

/**
 * 插入排序
 * @param array
 * @return
 */
public static int[] insertionSort(int[] array) {
    int len;
    // 基本情况下的数组可以直接返回
    if(array == null || (len = array.length) == 0 || len == 1) {
        return array;
    }
    int current;
    for (int i = 0; i < len - 1; i++) {
        // 第一个数默认已排序,从第二个数开始
        current = array[i + 1];
        // 前一个数的下标
        int preIdx = i;

        // 拿当前的数与之前已排序序列逐一往前比较,
        // 如果比较的数据比当前的大,就把该数往后挪一步
        while (preIdx >= 0 && current < array[preIdx]) {
            array[preIdx + 1] = array[preIdx];
            preIdx--;
        }
        // while循环跳出说明找到了位置
        array[preIdx + 1] = current;
    }
    return array;
}

median3和swap方法的代码:

/**
 * 取最开始、最后、中间三个元素的中值
 * @param array
 * @param left
 * @param right
 * @return
 */
private static int median3(int[] array, int left, int right) {
    int center = (left + right) / 2;
    if (array[left] > array[center])
        swap(array, left, center);
    if (array[left] > array[right])
        swap(array, left, right);
    if (array[center] > array[right])
        swap(array, center, right);
        swap(array, center, right - 1);
        return array[right - 1];
    }
/**
 * 交换数组中两个索引的值
 * @param array
 * @param i
 * @param j
 */
private static void swap(int[] array, int i, int j) {
    int temp;
    temp = array[i];
    array[i] = array[j];
    array[j] = temp;
}

效率分析

  • 最好情形下,每次划分都将原序列划分成两个基本等长的序列

  • 随着递归层次的加深,子序列的数量翻倍

  • 但在每一递归层次上(可以将递归过程看成一棵树),比较总次数都O(n)次

  • 递归层次(深度)是log2n因此,快速排序的最好情形时间复杂度为O(nlogn)

    • 平均时间复杂度也是O(nlogn)

    • 最坏情形可能导致效率为O(n2)

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值