(笔记整合)排序优化

一、如何选择合适的排序算法?

1.排序算法一览表
在这里插入图片描述
2.为什选择快速排序?
1)线性排序时间复杂度很低但使用场景特殊,如果要写一个通用排序函数,不能选择线性排序。
2)为了兼顾任意规模数据的排序,一般会首选时间复杂度为O(nlogn)的排序算法来实现排序函数。
3)同为O(nlogn)的快排和归并排序相比,归并排序不是原地排序算法,所以最优的选择是快排。

二、如何优化快速排序?

导致快排时间复杂度降为O(n)的原因是分区点选择不合理,最理想的分区点是:被分区点分开的两个分区中,数据的数量差不多。如何优化分区点的选择?有2种常用方法,如下:

1.三数取中法
①从区间的首、中、尾分别取一个数,然后比较大小,取中间值作为分区点。
②如果要排序的数组比较大,那“三数取中”可能就不够用了,可能要“5数取中”或者“10数取中”。

2.随机法:每次从要排序的区间中,随机选择一个元素作为分区点。
优化前:

public class QuickSort {
    // 快速排序,data是数组
    public static void sort(int[] data) {
        quickSortInternally(data, 0, data.length - 1);
    }

    // 快速排序递归函数,start,end为下标
    private static void quickSortInternally(int[] data, int start, int end) {
        if (start >= end) { return; }

        int index = partition(data, start, end); // 获取分区点
        quickSortInternally(data, start, index - 1);
        quickSortInternally(data, index + 1, end);
    }
    
    private static int partition(int[] data, int start, int end) {
        int pivot = data[end];
        int index = start - 1;
        for (int j = start; j < end; ++j) {
            if (data[j] < pivot) {
                index++;
                swap(data, index, j);
            }
        }
        // 交换分界点和比较值
        swap(data, ++index, end);

        System.out.println("index = " + index);
        return index;
    }
    
    private static void swap(int[] data, int i, int j) {
        int temp = data[i];
        data[i] = data[j];
        data[j] = temp;
    }

优化后:

public class QuickSort {
    // 快速排序,data是数组
    public static void sort(int[] data) {
        quickSortInternally(data, 0, data.length - 1);
    }

    // 快速排序递归函数,start,end为下标
    private static void quickSortInternally(int[] data, int start, int end) {
        if (start >= end) { return; }

        int index = partition(data, start, end); // 获取分区点
        quickSortInternally(data, start, index - 1);
        quickSortInternally(data, index + 1, end);
    }
    
    private static int partition(int[] data, int start, int end) {
        int index = start + (int)(Math.random() * (end - start + 1));
        swap(data, index, end);
        int pivot = data[end];
        index = start - 1;
        for (int j = start; j < end; ++j) {
            if (data[j] < pivot) {
                index++;
                swap(data, index, j);
            }
        }
        // 交换分界点和比较值
        swap(data, ++index, end);

        System.out.println("index = " + index);
        return index;
    }

    private static void swap(int[] data, int i, int j) {
        int temp = data[i];
        data[i] = data[j];
        data[j] = temp;
    }

3.警惕快排的递归发生堆栈溢出,有2中解决方法,如下:
①限制递归深度,一旦递归超过了设置的阈值就停止递归。
②在堆上模拟实现一个函数调用栈,手动模拟递归压栈、出栈过程,这样就没有系统栈大小的限制。

三、通用排序函数实现技巧

1.数据量不大时,可以采取用时间换空间的思路
2.数据量大时,优化快排分区点的选择
3.防止堆栈溢出,可以选择在堆上手动模拟调用栈解决
4.在排序区间中,当元素个数小于某个常数是,可以考虑使用O(n2)级别的插入排序
5.用哨兵简化代码,每次排序都减少一次判断,尽可能把性能优化到极致

四、思考

1.Java中的排序函数都是用什么排序算法实现的?有有哪些技巧?
Arrays.sort的源码,主要采用TimSort算法, 大致思路是这样的:
1 元素个数 < 32, 采用二分查找插入排序(Binary Sort)
2 元素个数 >= 32, 采用归并排序,归并的核心是分区(Run)
3 找连续升或降的序列作为分区,分区最终被调整为升序后压入栈
4 如果分区长度太小,通过二分插入排序扩充分区长度到分区最小阙值
5 每次压入栈,都要检查栈内已存在的分区是否满足合并条件,满足则进行合并
6 最终栈内的分区被全部合并,得到一个排序好的数组

Timsort的合并算法非常巧妙:
1 找出左分区最后一个元素(最大)及在右分区的位置
2 找出右分区第一个元素(最小)及在左分区的位置
3 仅对这两个位置之间的元素进行合并,之外的元素本身就是有序的

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值