十大排序算法-Java版

十大排序算法

image-20210318163840219

常见的递推式与复杂度

image-20210318163925259

Sort抽象类

定义排序的抽象父类,具体的排序算法延迟到sort()方法中实现

/**
 * @Description:定义排序的一些统计属性(如比较次数、交换次数、时间)
 * @author: cyb
 * @date:  2021-03-15 14:41
 * @version V1.0
 */
public abstract class Sort<T extends Comparable<T>> implements Comparable<Sort<T>> {
    protected T[] array;
    private int cmpCount;
    private int swapCount;
    private long time;
    private DecimalFormat fmt = new DecimalFormat("#.00");


    public void sort(T[] array) {
        if (array == null || array.length < 2) {
            return;
        }

        this.array = array;

        long begin = System.currentTimeMillis();
        sort();
        time = System.currentTimeMillis() - begin;
    }

    @Override
    public int compareTo(Sort<T> o) {
        int result = (int)(time - o.time);
        if (result != 0) {
            return result;
        }

        result = cmpCount - o.cmpCount;
        if (result != 0) {
            return result;
        }

        return swapCount - o.swapCount;
    }

    /**
     * 排序算法具体实现
     */
    protected abstract void sort();

    /**
     * 返回值等于0,代表 array[i1] == array[i2]
     * 返回值小于0,代表 array[i1] < array[i2]
     * 返回值大于0,代表 array[i1] > array[i2]
     */
    protected int cmp(int i1, int i2) {
        cmpCount++;
        return array[i1].compareTo(array[i2]);
    }

    protected int cmp(T v1, T v2) {
        cmpCount++;
        return v1.compareTo(v2);
    }

    protected void swap(int i1, int i2) {
        swapCount++;
        T tmp = array[i1];
        array[i1] = array[i2];
        array[i2] = tmp;
    }

    @Override
    public String toString() {
        String timeStr = "耗时:" + (time / 1000.0) + "s(" + time + "ms)";
        String compareCountStr = "比较:" + numberString(cmpCount);
        String swapCountStr = "交换:" + numberString(swapCount);
        String stableStr = "稳定性:" + isStable();
        return "【" + getClass().getSimpleName() + "】\n"
                + stableStr + " \t"
                + timeStr + " \t"
                + compareCountStr + "\t "
                + swapCountStr + "\n"
                + "------------------------------------------------------------------";

    }

    private String numberString(int number) {
        if (number < 10000) {
            return "" + number;
        }

        if (number < 100000000) {
            return fmt.format(number / 10000.0) + "万";
        }
        return fmt.format(number / 100000000.0) + "亿";
    }

    private boolean isStable() {
        if (this instanceof RadixSort) {
            return true;
        }
        if (this instanceof CountingSort) {
            return true;
        }
        if (this instanceof ShellSort) {
            return false;
        }
        if (this instanceof SelectionSort) {
            return false;
        }
        Student[] students = new Student[20];
        for (int i = 0; i < students.length; i++) {
            students[i] = new Student(i * 10, 10);
        }
        sort((T[]) students);
        for (int i = 1; i < students.length; i++) {
            int score = students[i].score;
            int prevScore = students[i - 1].score;
            if (score != prevScore + 10) {
                return false;
            }
        }
        return true;
    }
}

基于比较的排序

冒泡排序(Bubble Sort)

从头开始比较每一对相邻元素,如果第1个比第2个大,就交换它们的位置 。执行完一轮后,最末尾那个元素就是最大的元素

public class BubbleSort1 extends Sort{
    @Override
    protected void sort() {
        for (int end = array.length - 1; end > 0; end--) {
            //每一轮比较的范围是[0,end)
            for (int begin = 0; begin < end; begin++) {
                if (cmp(begin+1,begin)<0) {
                    swap(begin+1,begin);
                }
            }
        }
    }
}

优化1

如果序列已经完全有序,可以提前终止冒泡排序

public class BubbleSort2 extends Sort{
    @Override
    protected void sort() {
        for (int end = array.length - 1; end > 0; end--) {
            boolean sorted = true;
            for (int begin = 0; begin < end; begin++) {
                if (cmp(begin+1,begin)<0) {
                    swap(begin+1,begin);
                    sorted = false;
                }
            }
             //如果经过一轮排序,sort依然为true,说明没有发生一次交换,序列已经有序了
            if (sorted) {
                break;
            }
        }
    }
}

优化2

如果序列尾部已经局部有序,可以记录最后1次交换的位置,减少比较次数

public class BubbleSort3 extends Sort{
    @Override
    protected void sort() {
        for (int end = array.length - 1; end > 0; end--) {
            int sortedIndex = 0;
            for (int begin = 0; begin < end; begin++) {
                if (cmp(begin+1,begin)<0) {
                    swap(begin+1,begin);
                    sortedIndex = begin + 1;
                }
            }
            end = sortedIndex;
        }
    }
}
  • 最坏、平均时间复杂度:O(n 2 )
  • 最好时间复杂度:O(n)
  • 空间复杂度:O(1)

选择排序(Selection Sort)

从序列中找出最大的那个元素,然后与最末尾的元素交换位置 。执行完一轮后,最末尾的那个元素就是最大的元素

public class SelectionSort<T extends Comparable<T>> extends Sort<T> {
    @Override
    protected void sort() {
        for (int end = array.length - 1; end > 0; end--) {
            int maxIndex = 0;
            for (int begin = 1; begin <= end; begin++) {
                if (cmp(begin, maxIndex) > 0) {
                    maxIndex = begin;
                }
            }
            swap(maxIndex, end);
        }
    }
}

堆排序(Heap Sort)

可以认为是对选择排序的优化

执行流程

  1. 对序列进行原地建堆(heapify)–大顶堆
  2. 重复执行以下操作,直到堆的元素数量为 1
    1. 交换堆顶元素与尾元素
    2. 堆的元素数量减 1
    3. 对 0 位置进行 1 次 siftDown() ,即下滤操作
public class HeapSort<E extends Comparable<E>> extends Sort<E>{
    private int heapSize;
    @Override
    protected void sort() {
        //原地建堆,有两种形式,自上而下的上滤,或者自下而上的下滤
        //这里是自下而上的下滤
        heapSize=array.length;
        //(heapSize >> 1) - 1只有非叶子节点
        for (int i = (heapSize >> 1) - 1; i >= 0; i--) {
            siftDown(i);
        }
        while (heapSize>1){
            //交换堆顶元素和尾部
            swap(0,--heapSize);
            //对0位置下滤
            siftDown(0);
        }
    }
    /**
     * 下滤
     * @param index
     */
    private void siftDown(int index) {
        E element = array[index];
        //第一个叶子节点的索引=非叶子节点的数量
        int half = heapSize >> 1;
        while (index < half) { //必须保证index非叶子节点
            //index的子节点有两种情况
            //1、只有左节点
            //2、同时有左右节点
            //默认为左节点
            //找出左右节点中大的那个
            int childIndex = (index << 1) + 1;
            E child = array[childIndex];
            //右子节点
            int rightIndex = childIndex + 1;
            if (rightIndex < heapSize && cmp(array[rightIndex], child) > 0) {
                childIndex = rightIndex;
                child = array[rightIndex];
            }
            if (cmp(element, child) >= 0) {
                break;
            }
            //将子节点存放到index
            array[index] = child;
            index = childIndex;
        }
        array[index] = element;
    }
}

插入排序(Insertion Sort)

在执行过程中,插入排序会将序列分为2部分,头部是已经排好序的,尾部是待排序的 。从头开始扫描每一个元素 ,每当扫描到一个元素,就将它插入到头部合适的位置,使得头部数据依然保持有序

public class InsertionSort1<T extends Comparable<T>> extends Sort<T> {

    /**
     * 逆序对越多,时间复杂度越高
     */
    @Override
    protected void sort() {
        for (int begin = 1; begin < array.length; begin++) {
            int cur = begin;
            while (cur > 0 && cmp(cur, cur - 1) < 0) {
                swap(cur, cur - 1);
                cur--;
            }
        }
    }
}

优化

思路是将【交换】转为【挪动】

public class InsertionSort2<T extends Comparable<T>> extends Sort<T> {
    @Override
    protected void sort() {
        for (int begin = 1; begin < array.length; begin++) {
            int cur = begin;
            T v=array[cur];
            while (cur > 0 && cmp(v, array[cur - 1]) < 0) {
//                swap(cur, cur - 1);
                array[cur]=array[cur-1];
                cur--;
            }
            array[cur]=v;
        }
    }
}

二分搜索优化

在元素 v 的插入过程中,可以先二分搜索出合适的插入位置,然后再将元素 v 插入

public class InsertionSort3<T extends Comparable<T>> extends Sort<T> {
    @Override
    protected void sort() {
        for (int begin = 1; begin < array.length; begin++) {
            insert(begin,search(begin));
        }
    }

    /**
     * 将source位置的元素插入到dest位置
     * @param source
     * @param dest
     */
    private void insert(int source, int dest) {
        T v=array[source];
        for (int i = source; i >dest ; i--) {
            array[i]=array[i-1];
        }
        array[dest]=v;
    }

    /**
     * @param index
     * @Description: 利用二分搜索找到index位置元素的待插入位置
     * @Return int
     * @Exception 
     * @author: cyb
     * @date:  2021-03-17 14:24
     */
    private int search(int index){
        T v = array[index];
        int begin=0;
        int end=index;
        while (begin<end){
            int mid=(begin+end)>>1;
            if (cmp(v,array[mid])<0){
                end=mid;
            }else {
                begin=mid+1;
            }
        }
        return begin;
    }
}

归并排序(Merge Sort)

  1. 不断地将当前序列平均分割成2个子序列 , 直到不能再分割(序列中只剩1个元素)
  2. 不断地将2个子序列合并成一个有序序列 ,直到最终只剩下1个有序序列
public class MergeSort<T extends Comparable<T>> extends Sort<T> {
    private T[] leftArray;

    // T(n) = T(n/2) + T(n/2) + O(n)
    
    @Override
    protected void sort() {
        leftArray = (T[]) new Comparable[array.length >> 1];
        sort(0, array.length);
    }

    /**
     * @param begin
     * @param end
     * @Description: 对[begin, end)范围内的数据归并排序
     */
    private void sort(int begin, int end) {
        if (end - begin < 2) {
            return;
        }
        int mid = (begin + end) >> 1;
        sort(begin, mid);
        sort(mid, end);
        merge(begin, mid, end);
    }

    /**
     * @param begin
     * @param mid
     * @param end
     * @Description: 将[begin, mid),[mid,end)合并
     */
    private void merge(int begin, int mid, int end) {
        //左边数组的起始
        int li = 0;
        int le = mid - begin;
        //右边数组的起始
        int ri = mid;
        int re = end;
        //备份的左边数组的index
        int ai = begin;
        //备份左边数组
        for (int i = li; i < le; i++) {
            leftArray[i] = array[begin + i];
        }
        //如果左边还没结束
        while (li < le) {
            if (ri < re && cmp(array[ri], leftArray[li]) < 0) {
                array[ai++] = array[ri++];
            } else {
                array[ai++] = leftArray[li++];
            }
        }
    }
}

快速排序(Quick Sort)

  • 从序列中选择一个轴点元素(pivot)
    • 假设每次选择 0 位置的元素为轴点元素
  • 利用 pivot 将序列分割成 2 个子序列
    • 将小于 pivot 的元素放在pivot前面(左侧)
    • 将大于 pivot 的元素放在pivot后面(右侧)
    • 等于pivot的元素放哪边都可以
  • ③ 对子序列进行 ① ② 操作
    • 直到不能再分割(子序列中只剩下1个元素)
public class QuickSort<T extends Comparable<T>> extends Sort<T> {

    @Override
    protected void sort() {
        sort(0, array.length);
    }

    /**
     * @param begin
     * @param end
     * @Description: 对[begin, end)范围内的元素快速排序
     */
    private void sort(int begin, int end) {
        if (end - begin < 2) {
            return;
        }
        //确定轴点位置
        int mid = pivotIndex(begin, end);
        //对子序列快速排序
        sort(begin, mid);
        sort(mid + 1, end);
    }

    /**
     * 构造轴点,以第一个元素作为轴点
     * @param begin
     * @param end
     * @return
     */
    private int pivotIndex(int begin, int end) {
        //随机选择一个元素跟begin交换
        swap(begin, begin+(int) (Math.random()*(end-begin)));

        //备份begin位置的元素
        T pivot = array[begin];
        end--;
//        boolean turn=true;
        //类似左右切换的可以用三个while循环加break解决
        while (begin < end) {
            while (begin < end) {
                if (cmp(pivot, array[end]) < 0) { // 右边元素 > 轴点元素
                    end--;
                } else { // 右边元素 <= 轴点元素
                    array[begin++] = array[end];
                    break;
                }
            }
            while (begin < end) {
                if (cmp(pivot, array[begin]) > 0) { // 左边元素 < 轴点元素
                    begin++;
                } else { // 左边元素 >= 轴点元素
                    array[end--] = array[begin];
                    break;
                }
            }
        }
        //将轴点元素放到最终的位置
        array[begin] = pivot;
        return begin;
    }
}

希尔排序(Shell Sort)

希尔排序把序列看作是一个矩阵,分成 𝑚 列,逐列进行排序 ,m从某个整数逐渐减为1,当 𝑚 为1时,整个序列将完全有序

因此,希尔排序也被称为递减增量排序(Diminishing Increment Sort)

矩阵的列数取决于步长序列(step sequence),比如,如果步长序列为{1,5,19,41,109,…},就代表依次分成109列、41列、19列、5列、1列进行排序。不同的步长序列,执行效率也不同

希尔本人给出的步长序列是 𝑛/2 𝑘,比如 𝑛 为16时,步长序列是{1, 2, 4, 8}

目前已知的最好的步长序列,最坏情况时间复杂度是 O(n 4/3 ) ,1986年由Robert Sedgewick提出

image-20210318163536160

public class ShellSort<T extends Comparable<T>> extends Sort<T> {

    @Override
    protected void sort() {
        //步长序列
        List<Integer> stepSequence = sedgewickStepSequence();
        for (Integer step : stepSequence) {
            sort(step);
        }
    }

    /**
     * 分成step列进行排序
     *
     * @param step
     */
    private void sort(int step) {
        for (int col = 0; col < step; col++) {
            //第col列排序
            //需要排序的元素 col、col+step、col+2step
            for (int begin = col + step; begin < array.length; begin += step) {
                int cur = begin;
                while (cur > col && cmp(cur, cur - step) < 0) {
                    swap(cur, cur - step);
                    cur -= step;
                }
            }
        }
    }

    private List<Integer> shellStepSequence() {
        ArrayList<Integer> stepSequence = new ArrayList<>();
        int step = array.length;
        while ((step >>= 1) > 0) {
            stepSequence.add(step);
        }
        return stepSequence;
    }

    /**
     * 目前已知的最好的步长序列,最坏情况时间复杂度是 O(n的4/3次方),1986年由Robert Sedgewick提出
     * @return
     */
    private List<Integer> sedgewickStepSequence() {
        List<Integer> stepSequence = new LinkedList<>();
        int k = 0, step = 0;
        while (true) {
            if (k % 2 == 0) {
                int pow = (int) Math.pow(2, k >> 1);
                step = 1 + 9 * (pow * pow - pow);
            } else {
                int pow1 = (int) Math.pow(2, (k - 1) >> 1);
                int pow2 = (int) Math.pow(2, (k + 1) >> 1);
                step = 1 + 8 * pow1 * pow2 - 6 * pow2;
            }
            if (step >= array.length) break;
            stepSequence.add(0, step);
            k++;
        }
        return stepSequence;
    }
}

计数排序(Counting Sort)

统计每个整数在序列中出现的次数,进而推导出每个整数在有序序列中的索引

public class CountingSort extends Sort<Integer> {
    /**
     * 改进最简单的实现
     */
    @Override
    protected void sort() {
        int max = array[0];
        int min = array[0];
        for (int i = 1; i < array.length; i++) {
            if (array[i] > max) {
                max = array[i];
            }
            if (array[i] < min) {
                min = array[i];
            }
        }
        //开辟内存空间,存储每个整数出现的次数
        int[] counts = new int[max - min + 1];
        for (int i = 0; i < array.length; i++) {
            counts[array[i] - min]++;
        }
        //累加次数
        for (int i = 1; i < counts.length; i++) {
            counts[i] += counts[i - 1];
        }
        //从后往前(保证稳定性)遍历元素,将它放到有序数组的合适位置
        int[] newArray = new int[array.length];
        for (int i = array.length - 1; i >= 0; i--) {
            newArray[--counts[array[i] - min]] = array[i];
        }
        for (int i = 0; i < newArray.length; i++) {
            array[i] = newArray[i];
        }
    }

    /**
     * 最简单的实现
     * 存在以下问题:
     * 1、无法对负整数进行排序
     * 2、极其浪费内存空间
     * 3、是个不稳定的排序
     */
    protected void sort0() {
        //找出最大值
        int max = array[0];
        for (int i = 1; i < array.length; i++) {
            if (array[i] > max) {
                max = array[i];
            }
        }
        //开辟内存空间,存储每个整数出现的次数
        int[] counts = new int[1 + max];
        for (int i = 0; i < array.length; i++) {
            counts[array[i]]++;
        }
        //根据整数出现次数进行排序
        int arrayIndex = 0;
        for (int i = 0; i < counts.length; i++) {
            while (counts[i] > 0) {
                array[arrayIndex++] = i;
                counts[i]--;
            }
        }
    }
}

基数排序(Radix Sort)

依次对个位数、十位数、百位数、千位数、万位数…进行排序(从低位到高位)

个位数、十位数、百位数的取值范围都是固定的0~9,可以使用计数排序对它们进行排序||或者用二维数组实现

public class RadixSort extends Sort<Integer>{
    @Override
    protected void sort() {
        int max = array[0];
        for (int i = 1; i < array.length; i++) {
            if (array[i] > max) {
                max = array[i];
            }
        }
        // 个位数: array[i] / 1 % 10 = 3
        // 十位数:array[i] / 10 % 10 = 9
        // 百位数:array[i] / 100 % 10 = 5
        // 千位数:array[i] / 1000 % 10 = ...
        for (int divider = 1; divider <= max; divider *= 10) {
            countingSort(divider);
        }
    }

    protected void countingSort(int divider) {
        //开辟内存空间,存储每个整数的基数出现的次数
        int[] counts = new int[10];
        for (int i = 0; i < array.length; i++) {
            counts[array[i]/divider%10]++;
        }
        //累加次数
        for (int i = 1; i < counts.length; i++) {
            counts[i] += counts[i - 1];
        }
        //从后往前(保证稳定性)遍历元素,将它放到有序数组的合适位置
        int[] newArray = new int[array.length];
        for (int i = array.length - 1; i >= 0; i--) {
            newArray[--counts[array[i]/divider%10]] = array[i];
        }
        for (int i = 0; i < newArray.length; i++) {
            array[i] = newArray[i];
        }
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值