插入、快排、堆排、归并、计数和桶排序详解以及测试

==> 学习汇总(持续更新)
==> 从零搭建后端基础设施系列(一)-- 背景介绍


一、代码

package com.example.sort;

import java.util.ArrayList;
import java.util.Comparator;
import java.util.Random;

public class Sort {
    public static void main(String[] args) {
        int n = 1000000;
        int BUCKET_COUNT = 1000;
        int bound = n;
        int[] array1 = genRandArray(n, bound);
        int[] array2 = new int[n];
        System.arraycopy(array1, 0, array2, 0, n);
        int[] array3 = new int[n];
        System.arraycopy(array1, 0, array3, 0, n);
        int[] array4 = new int[n];
        System.arraycopy(array1, 0, array4, 0, n);
        int[] array5 = new int[n];
        System.arraycopy(array1, 0, array5, 0, n);
        int[] array6 = new int[n];
        System.arraycopy(array1, 0, array6, 0, n);
        int[] array7 = new int[n];
        System.arraycopy(array1, 0, array7, 0, n);
        int[] array8 = new int[n];
        System.arraycopy(array1, 0, array8, 0, n);
        int[] array9 = new int[n];
        System.arraycopy(array1, 0, array9, 0, n);
        int[] array10 = new int[n];
        System.arraycopy(array1, 0, array10, 0, n);
        int[] array11 = new int[n];
        System.arraycopy(array1, 0, array11, 0, n);

        long b , e;
        //printArray(array1);

        System.out.println("数据量: " + n + ", 桶个数:" + BUCKET_COUNT + ", 数据范围:[0, " + bound + "]\n---------------------");
//        b = System.currentTimeMillis();
//        insertSort(array1);
//        e = System.currentTimeMillis() - b;
//        System.out.println("插入:" + e + " ms");

        b = System.currentTimeMillis();
        quickSort(array2);
        e = System.currentTimeMillis() - b;
        System.out.println("普通快排:" + e + " ms");

        b = System.currentTimeMillis();
        quickSort2(array3);
        e = System.currentTimeMillis() - b;
        System.out.println("基准数随机快排:" + e + " ms");

        b = System.currentTimeMillis();
        quickSort3(array4);
        e = System.currentTimeMillis() - b;
        System.out.println("基准数三取中快排:" + e + " ms");

        b = System.currentTimeMillis();
        heapSort1(array5);
        e = System.currentTimeMillis() - b;
        System.out.println("递归堆排序:" + e + " ms");

//        b = System.currentTimeMillis();
//        heapSort2(array2);
//        e = System.currentTimeMillis();
//        System.out.println(e - b);

        b = System.currentTimeMillis();
        heapSort3(array6);
        e = System.currentTimeMillis() - b;
        System.out.println("循环堆排序:" + e + " ms");

        b = System.currentTimeMillis();
        mergeSort(array7);
        e = System.currentTimeMillis() - b;
        System.out.println("归并排序:" + e + " ms");

        b = System.currentTimeMillis();
        countSort(array8);
        e = System.currentTimeMillis() - b;
        System.out.println("计数排序:" + e + " ms");

        b = System.currentTimeMillis();
        bucketSort(array9, BUCKET_COUNT);
        e = System.currentTimeMillis() - b;
        System.out.println("桶排序(使用TimSort排序桶):" + e + " ms");

        b = System.currentTimeMillis();
        bucketSort2(array10, BUCKET_COUNT);
        e = System.currentTimeMillis() - b;
        System.out.println("桶排序(使用插入排序桶):" + e + " ms");

        b = System.currentTimeMillis();
        bucketSort3(array11, BUCKET_COUNT);
        e = System.currentTimeMillis() - b;
        System.out.println("桶排序(使用插入和归并排序桶--自定义数组结构):" + e + " ms");

        if(checkResult(array2, array3, array4, array5, array6, array7, array8, array9, array10, array11)) {
            System.out.println("结果校验一致");
        } else {
            System.out.println("结果校验错误");
        }
//        printArray(array1);
//        printArray(array2);
//        printArray(array3);
//        printArray(array4);
//        printArray(array5);
//        printArray(array6);
//        printArray(array7);
    }

    /**
     * 插入排序
     * @param array
     */
    public static void insertSort(int[] array){
        /**
         * 假设排序5 7 2 3 4 1 8 6 9 10
         * 我们可以依次从序列的第一个拿出来,从后往前插
         * 第一次:[5] 7 2 3 4 1 8 6 9 10
         * 第二次:[5 7] 2 3 4 1 8 6 9 10
         * 第三次:[2 5 7] 3 4 1 8 6 9 10
         * 第四次:[2 3 5 7] 4 1 8 6 9 10
         * 第五次:[2 3 4 5 7] 1 8 6 9 10
         * 第六次:[1 2 3 4 5 7] 8 6 9 10
         * 第七次:[1 2 3 4 5 7 8] 6 9 10
         * 第八次:[1 2 3 4 5 6 7 8] 9 10
         * 第九次:[1 2 3 4 5 6 7 8 9] 10
         * 第十次:[1 2 3 4 5 6 7 8 9 10]
         *
         */
        int pre, cur;
        for (int i = 1; i < array.length; i++) {
            cur = array[i];     //从数组中拿出第一个未排序的出来
            pre = i -1;         //默认第一个是已经排好序的
            while (pre >=0 && cur < array[pre]){
                array[pre + 1] = array[pre--]; //后移
            }
            //插入
            //就算没有进循环,例如7 > 5,保持原地不动,那么pre+1就是7的位置
            array[pre + 1] = cur; 
        }
    }

    public static void insertSort2(int[] array, int len){
        int pre, cur;
        for (int i = 1; i < len; i++) {
            cur = array[i];     //从数组中拿出第一个未排序的出来
            pre = i -1;         //默认第一个是已经排好序的
            while (pre >=0 && cur < array[pre]){
                array[pre + 1] = array[pre--]; //后移
            }
            //插入
            //就算没有进循环,例如7 > 5,保持原地不动,那么pre+1就是7的位置
            array[pre + 1] = cur; 
        }
    }

    /**
     * 快速排序算法
     * 核心思想:选取数组中的一个值为基准值,将比它小的放到左边,比它大的放到右边,
     *         分成两个子数组,然后子数组按照同样的步骤递归下去即可
     * 例如:5 7 2 3 4 1 8 6 9 10
     * 选取5作为基准数,从后找小于5的数交换,从前找大于5的数交换
     * 第一次:[1] 7 2 3 4 [5] 8 6 9 10
     * 第二次:1 [5] 2 3 4 [7] 8 6 9 10
     * 第三次:1 [4] 2 3 [5] 7 8 6 9 10
     * 经过三次,将数组分为两个子数组[1 4 2 3] 和 [7 8 6 9 10]
     * 选取1作为基准数
     * 第四次:[1] 和 [4 2 3]
     * 选取4作为基准数
     * 第五次:[3 2] 和 [4]
     * 选取3作为基准数
     * 第六次:[2 3]
     * 选取7作为基准数
     * 第七次:[6 8] 和 [9 10]
     * 结束
     * @param array
     */
    public static void quickSort(int[] array){
        quickSort(array, 0, array.length);
    }

    public static void quickSort(int[] array, int left, int right){
        if(left < right) {
            int mid = partition(array, left, right);
            quickSort(array, left, mid);
            quickSort(array, mid + 1, right);
        }
    }

    public static void quickSort2(int[] array){
        quickSort2(array, 0, array.length);
    }

    public static void quickSort2(int[] array, int left, int right){
        if(left < right) {
            //从区间[lef, right)随机取一个基准值,并且和left交换
            swap(array, left, (getRandNum(right) % (right - left)) + left);
            int mid = partition(array, left, right);
            quickSort2(array, left, mid);
            quickSort2(array, mid + 1, right);
        }
    }

    public static void quickSort3(int[] array){
        quickSort3(array, 0, array.length);
    }

    public static void quickSort3(int[] array, int left, int right){
        if(left < right) {
            //取区间[left,right),left,中间,right-1的中间数作为基准值
            swap(array, left, getMidNum(array, left, (left + right) / 2, right-1));
            int mid = partition(array, left, right);
            quickSort3(array, left, mid);
            quickSort3(array, mid + 1, right);
        }
    }

    public static int partition(int[] array, int left, int right){
        int mid = left;
        int i = left, j = right - 1;
        while (i < j) {
            //从后往前找第一个小于基准数的
            while (i < j && array[j] >= array[mid]){
                --j;
            }

            //防止自己和自己交换
            if(i < j){
                mid = swap(array, mid, j);
            }

            //从前往后找第一个大于基准数的
            while (i < j && array[i] <= array[mid]) {
                ++i;
            }

            if(i < j){
                mid = swap(array, mid, i);
            }
        }
        return mid;
    }

    //递归下沉式构建堆
    public static void heapSort1(int[] array){
        //构造大顶堆
        for (int i = array.length / 2 - 1; i >= 0; i--) {
            buildHeadDown(array, array.length, i);
        }

        //堆排序,每循环一次,将root放到数组后面
        for (int i = 0; i < array.length - 1; i++) {
            swap(array, 0, array.length - 1 - i);
            buildHeadDown(array, array.length - 1 - i, 0);
        }
    }

    //递归上浮+下沉构建堆
    public static void heapSort2(int[] array){
        //构造大顶堆
        for (int i = 0; i < array.length; i++) {
            buildHeadUp(array, i + 1, 0);
        }

        //堆排序,每循环一次,将root放到数组后面
        for (int i = 0; i < array.length - 1; i++) {
            swap(array, 0, array.length - 1 - i);
            buildHeadDown(array, array.length - 1 - i, 0);
        }
    }

    //for循环下沉式构建堆
    public static void heapSort3(int[] array){
        //构造大顶堆
        for (int i = array.length / 2 - 1; i >= 0; i--) {
            buildHeadDown2(array, array.length, i);
        }

        //堆排序,每循环一次,将root放到数组后面
        for (int i = 0; i < array.length - 1; i++) {
            swap(array, 0, array.length - 1 - i);
            buildHeadDown2(array, array.length - 1 - i, 0);
        }
    }

    //构建堆,上浮法,效率极差(知道有这么个方法就行了)
    public static void buildHeadUp(int[] array, int end, int root) {
        int lChild = 2 * root + 1, rChild = 2 * root + 2;
        if (root < end && (lChild < end || rChild < end)) {
            if(lChild < end && (2 * lChild + 1) < end){
                buildHeadUp(array, end, lChild);
            }
            if(rChild < end && (rChild < end || rChild < end)){
                buildHeadUp(array, end, rChild);
            }
            if(lChild < end && rChild < end){
                if(array[root] < array[lChild] && array[root] < array[rChild]){
                    swap(array, root, array[lChild] < array[rChild] ? rChild : lChild);
                } else if(array[root] < array[lChild]){
                    swap(array, root, lChild);
                } else if(array[root] < array[rChild]){
                    swap(array, root, rChild);
                }
            } else if(lChild < end){
                if(array[root] < array[lChild]){
                    swap(array, root, lChild);
                }
            } else if(rChild < end) {
                if(array[root] < array[rChild]){
                    swap(array, root, rChild);
                }
            }
        }
    }

    //递归构建堆,下沉法
    public static void buildHeadDown(int[] array, int end, int root) {
        int lChild = 2 * root + 1, rChild = 2 * root + 2;
        //此根节点不是叶节点、并且左右孩子也不是叶节点就可以继续递归下去
        //如果是叶子节点,递归进去,还是什么都不做,直接返回
        if (root < end && (lChild < end || rChild < end)) {
            //该节点有左右孩子
            if(lChild < end && rChild < end && (array[root] < array[lChild] || array[root] < array[rChild])){
                buildHeadDown(array, end, swap(array, root, array[lChild] < array[rChild] ? rChild : lChild));
            } else if(lChild < end && array[root] < array[lChild]){
                //该节点只有左孩子
                buildHeadDown(array, end, swap(array, root, lChild));
            } else if(rChild < end && array[root] < array[rChild]) {
                //该节点只有右孩子
                buildHeadDown(array, end, swap(array, root, rChild));
            }
        }
    }

    //for循环构建堆,下沉法
    public static void buildHeadDown2(int[] array, int end, int root) {
        int lChild = 0, rChild = 0;
        //i一直跟着传进来的root节点
        for (int i = root; i < end && (lChild < end || rChild < end); ) {
            lChild = 2 * i + 1;
            rChild = 2 * i + 2;
            //该节点有左右孩子
            if(lChild < end && rChild < end && (array[i] < array[lChild] || array[i] < array[rChild])){
                i = swap(array, i, array[lChild] < array[rChild] ? rChild : lChild);
            } else if(lChild < end && array[i] < array[lChild]){
                //该节点只有左孩子
                i = swap(array, i, lChild);
            } else if(rChild < end && array[i] < array[rChild]){
                //该节点只有右孩子
                i = swap(array, i, rChild);
            } else {
                break;
            }
        }
    }


    //归并排序
    public static void mergeSort(int[] array){
        if(array == null || array.length == 1){
            return;
        }
        //直接在最外面创建一个大小一样的临时数组,避免递归创建临时数组带来的空间时间损耗
        int[] tmp = new int[array.length];
        mergeSort(array, tmp, 0, array.length - 1);
        tmp = null;
    }

    public static void mergeSort(int[] array, int[] tmp, int left, int right){
        if(left < right){
            //计算拆分中间位置
            int mid = left + (right - left) / 2;
            mergeSort(array, tmp, left, mid);
            mergeSort(array, tmp, mid + 1, right);
            merge(array, tmp, left, mid, right);
        }
    }

    public static void merge(int[] array, int[] tmp, int left, int mid, int right) {
        int i = mid + 1,j = 0 , k = left;
        //两个有序子数组合并
        while (k <= mid && i <= right) {
            tmp[j++] = array[k] < array[i] ? array[k++] : array[i++];
        }
        //如果左子数组有剩余元素,直接加到临时数组即可
        while (k <= mid) {
            tmp[j++] = array[k++];
        }
        //如果左右子数组有剩余元素,直接加到临时数组即可
        while (i <= right) {
            tmp[j++] = array[i++];
        }
        //将排好序的元素,覆盖回去
        for (i = left, j = 0; i <= right; i++) {
            array[i] = tmp[j++];
        }
    }

    //计数排序
    public static void countSort(int[] array){
        //找范围[min, max]
        int max = Integer.MIN_VALUE, min = Integer.MAX_VALUE;
        for (int i = 0; i < array.length; i++) {
            max = array[i] > max ? array[i] : max;
            min = array[i] < min ? array[i] : min;
        }
        //创建桶,个数为max - min + 1
        int[] buckets = new int[max - min + 1];

        //将数放入桶中
        for (int i = 0; i < array.length; i++) {
            //如果数字重复,那么计数累加就好
            buckets[array[i] - min]++;
        }

        //顺序遍历桶则为从小到大排序,逆序遍历则为从大到小排序
        for (int i = 0, j = 0; i < buckets.length; i++) {
            while (buckets[i]-- != 0) {
                array[j++] = i + min;
            }
        }
    }

    //桶排序
    public static void bucketSort(int[] array, int bucketCount){
        //找范围[min, max]
        int max = Integer.MIN_VALUE, min = Integer.MAX_VALUE;
        for (int i = 0; i < array.length; i++) {
            max = array[i] > max ? array[i] : max;
            min = array[i] < min ? array[i] : min;
        }
        //计算桶间间隔
        double size = (max - min + 1) / (double)bucketCount;
        ArrayList<Integer>[] arrayList = new ArrayList[bucketCount];

        //将数放入桶中
        int index;
        for (int i = 0; i < array.length; i++) {
            index = (int)Math.floor((array[i] - min) / size);
            if(arrayList[index] == null) {
                arrayList[index] = new ArrayList<>();
            }
            arrayList[index].add(array[i]);
        }

        //将桶中的数据排序
        for (int i = 0, j = 0; i < bucketCount; i++) {
            if(arrayList[i] == null || arrayList[i].isEmpty()){
                continue;
            }
            if(arrayList[i].size() == 1){
                array[j++] = arrayList[i].get(0);
            } else {
                arrayList[i].sort(new Comparator<Integer>() {
                    @Override
                    public int compare(Integer o1, Integer o2) {
                        return o1 - o2;
                    }
                });
                int len = arrayList[i].size();
                for (int k = 0; k < len; k++) {
                    array[j++] = arrayList[i].get(k);
                }
            }
        }
    }

    public static void bucketSort2(int[] array, int bucketCount){
        //找范围[min, max]
        int max = Integer.MIN_VALUE, min = Integer.MAX_VALUE;
        for (int i = 0; i < array.length; i++) {
            max = array[i] > max ? array[i] : max;
            min = array[i] < min ? array[i] : min;
        }
        //计算桶间间隔
        double size = (max - min + 1) / (double)bucketCount;
        ListNode[] arrayList = new ListNode[bucketCount];

        //将数放入桶中
        //使用链表作为桶,插入的时候即时排序
        int index;
        for (int i = 0; i < array.length; i++) {
            index = (int)Math.floor((array[i] - min) / size);
            if(arrayList[index] == null) {
                arrayList[index] = new ListNode(array[i]);
            } else {
                ListNode pre = arrayList[index], cur = arrayList[index];
                if(array[i] == cur.val){
                    arrayList[index].count++;
                    continue;
                }
                if(array[i] < cur.val){
                    ListNode node = new ListNode(array[i]);
                    node.next = pre;
                    arrayList[index] = node;
                    continue;
                }
                while (cur != null){
                    if(array[i] == cur.val){
                        cur.count++;
                        break;
                    }
                    if(array[i] < cur.val || cur.next == null){
                        ListNode node = new ListNode(array[i]);
                        if(array[i] < cur.val){
                            node.next = pre.next;
                            pre.next = node;
                        } else {
                            cur.next = node;
                        }
                        break;
                    }
                    pre = cur;
                    cur = cur.next;
                }
            }
        }

        //遍历
        for (int i = 0, j = 0; i < arrayList.length; i++) {
            while (arrayList[i] != null){
                while (arrayList[i].count-- > 0){
                    array[j++] = arrayList[i].val;
                }
                arrayList[i] = arrayList[i].next;
            }
        }
    }

    public static void bucketSort3(int[] array, int bucketCount){
        //找范围[min, max]
        int max = Integer.MIN_VALUE, min = Integer.MAX_VALUE;
        for (int i = 0; i < array.length; i++) {
            max = array[i] > max ? array[i] : max;
            min = array[i] < min ? array[i] : min;
        }
        //计算桶间间隔
        double size = (max - min + 1) / (double)bucketCount;
        Array[] arrayList = new Array[bucketCount];

        //将数放入桶中
        int index;
        for (int i = 0; i < array.length; i++) {
            index = (int)Math.floor((array[i] - min) / size);
            if(arrayList[index] == null) {
                arrayList[index] = new Array();
            }
            arrayList[index].add(array[i]);
        }

        //将桶中的数据排序
        for (int i = 0, j = 0; i < bucketCount; i++) {
            if(arrayList[i] == null || arrayList[i].isEmpty()){
                continue;
            }
            if(arrayList[i].size() == 1){
                array[j++] = arrayList[i].get(0);
            } else {
                arrayList[i].sort();
                int len = arrayList[i].size();
                for (int k = 0; k < len; k++) {
                    array[j++] = arrayList[i].get(k);
                }
            }
        }
    }

    static class ListNode {
        public int val;
        public int count;
        public ListNode next = null;
        public ListNode(int val) {
            this.val = val;
            this.count = 1;
        }
    }

    public static int swap(int[] array, int pos1, int pos2) {
        if(pos1 < 0 || pos2 < 0 || pos1 >= array.length || pos2 >= array.length){
            throw new RuntimeException("数组越界");
        }
        if(pos1 == pos2) {
            return pos1;
        }
        array[pos1] = array[pos1] ^ array[pos2];
        array[pos2] = array[pos1] ^ array[pos2];
        array[pos1] = array[pos1] ^ array[pos2];
        return pos2;
    }

    public static void printArray(int[] array){
        for (int i = 0; i < array.length; i++) {
            System.out.print(array[i] + " ");
        }
        System.out.println();
    }

    public static int[] genRandArray(int n, int bound){
        int[] array = new int[n];
        Random random = new Random();
        for (int i = 0; i < n; i++) {
            array[i] = random.nextInt(bound);
        }
        return array;
    }

    public static boolean checkResult(int[] ... arrs){
        for (int i = 1; i < arrs.length; i++) {
            for (int j = 0; j < arrs[0].length; j++) {
                if(arrs[0][j] != arrs[i][j]) {
                    return false;
                }
            }
        }
        return true;
    }

    public static int getMidNum(int[] array, int a, int b, int c){
       if((array[b] - array[a] >= 0 && array[c] - array[a] <= 0 ) || (array[b] - array[a] <= 0 && array[c] - array[a] >= 0 )) {
            return a;
        } else if((array[a] - array[b] >= 0 && array[c] - array[b] <= 0 ) || (array[a] - array[b] <= 0 && array[c] - array[b] >= 0 )){
            return b;
        } else {
            return c;
        }
    }

    private static Random random = new Random();
    public static int getRandNum(int n){
        return random.nextInt(n);
    }

    static class Array {
        int[] array;
        int size;
        int cap;
        public Array(){
            array = new int[16];
            size = 0;
            this.cap = 16;
        }
        public Array(int cap){
            array = new int[cap];
            size = 0;
            this.cap = cap;
        }

        public void add(int e){
            if(size >= cap){
                reAlloc();
            }
            array[size++] = e;
        }

        public int get(int index){
            return array[index];
        }

        private void reAlloc(){
            if(size >= cap) {
                int[] tmp = new int[cap];
                System.arraycopy(array, 0, tmp, 0, size);
                cap = 2 * cap;
                array = new int[cap];
                System.arraycopy(tmp, 0, array, 0, size);
                tmp = null;
            }
        }
        public void sort(){
            if(size < 10) {
                insertSort2(array, size);
            } else {
                int[] tmp = new int[size];
                mergeSort(array, tmp, 0, size - 1);
            }
        }

        public int size(){
            return size;
        }

        public boolean isEmpty(){
            return size < 1 ;
        }
    }
}

二、核心思想

1.插入排序
  • 从后往前比较
  • 比较的同时将元素往后移
  • 找到要插入的位置时,直接array[i]=val即可
例如:5 7 2 3 4 1 8 6 9 10
第一个元素肯定是已排序的
第一步:从7开始,从后往前比较,7比5大,位置不变
5 7
第二步:2比7小,7往后移(2这个值是已经被拿出来的了,所以可以覆盖2的位置)
5 x 7
x 5 7
2 5 7
第三步:和第二步类似
2 5 x 7
2 x 5 7
2 3 5 7
第四步:同理
……

2.快速排序

  • 找基准值,将数组一分为2,比基准值小的在左边,大的在右边
    • 基准值一般要么在最左边,要么在最右边,假设在左边,那么从右往左找比基准值小的,交换,从左往右找比基准值大的,交换,直到首尾指针相遇
  • 对划分出来的子数组执行同样的操作,直到数组不能划分为止

快速排序可以说是非常简单的一种排序了,就是选择排序的升级版而已。

例如:5 7 2 3 4 1 8 6 9 10
第一步:
选取5为基准值,从右往左找比5小的,交换
[1] 7 2 3 4 [5] 8 6 9 10
从左往右找比5大的,交换
1 [5] 2 3 4 [7] 8 6 9 10
从右往左找比5小的,交换
1 [4] 2 3 [5] 7 8 6 9 10
划分后的两个数组[1 4 2 3 5]和[7  8  6  9 10]
第二步:
选取1为基准值,从右往左找比1小的,找不到,结束
划分后的两个数组[1]和[4 2 3 5],1不可划分了
第三步:
选取4为基准值,从右往左找比4小的,交换
[3] 2 [4] [5]
从左往右找比4大的,找不到,结束
划分后的两个数组[3 2 4]和[5], 5不可划分了
第四步:
选取3为基准值,从右往左找比3小的,交换
[2] [3] 4
划分后的两个数组[2 3]和[4], 4不可划分了
第五步:
选取2为基准值,从右往左找比2小的,找不到,结束
划分后的两个数组[2]和[3], 2和3都不可划分了
第六步:对[7  8  6  9 10]执行同样的操作
……

3.堆排序

  • 构建堆
    • 将小的数字往下沉,即从根节点开始往下一层层比较
  • 从堆顶拿到最大的元素,放到数组最后
  • 调整堆,重复上述步骤即可
例如:5 7 2 3 4 1 8 6 9 10 下沉法构建堆的过程如下

在这里插入图片描述
排序就是不断从堆顶拿元素,然后再调整的过程,如图
在这里插入图片描述
4.归并排序

  • 将数组一分为二
  • 重复上述步骤,直到数组不可分割
  • 排序、合并
归并排序比较简单,如图:

在这里插入图片描述

5.计数排序

  • 找到数组最小和最大的值(为了节约空间)
  • 创建数组,大小为max - min + 1
  • 将数放入到对应的位置,重复数字个数累加即可,buckets[array[i] - min]++
这种排序是所有排序中最快最快的,因为没有比较,整个过程只是赋值遍历输出而已, 但是空间消耗却是不可控的,容易出现极端情况。 例子如图:

在这里插入图片描述

6.桶排序

  • 找到数组最小和最大的值(为了节约空间)
  • 计算桶间间隔(桶的个数需要人为指定)
  • 将数放入到对应的桶
桶排序其实是计数排序的升级版( 这个不是我说的,个人感觉桶排序在速度和空间上没啥大优势,快的话,比不上计数排序,空间消耗上,只会比快排和归并多,所以我觉得只适合特定场景应用需要这种排序算法才有优势。

例子如图:
在这里插入图片描述

三、测试结果

时间单位:ms
1.随机数组测试,范围[0, n]

排序算法n = 1000n = 10000n = 100000n = 1000000n = 10000000
插入排序2171144108900
普通快速排序13261391577
基准值随机快速排序15131521695
基准值三数取中快速排序03121341501
递归堆排序24202073110
for循环堆排序15262023150
归并排序13171511597
计数排序01423223

2.随机+重复数组测试,范围[0, bound], n = 10000000 固定
当bound <= 1000 时,使用JVM默认的栈大小会栈溢出,需要调大 -Xss10m

排序算法bound = 1000000bound = 100000bound = 10000bound = 1000bound = 100
普通快速排序14341269216214236117685
基准值随机快速排序15821538252514322143182
基准值三数取中快速排序14861392222113937128836
递归堆排序30292942270120011443
for循环堆排序30563024252020221590
归并排序15711518138512151060
计数排序9264464142

3.随机+重复+部分有序数组测试,范围[0, bound], n = 10000000 固定,有序序列长度 = bound

排序算法bound = 10000000bound = 1000000bound = 100000bound = 10000bound = 1000bound = 100
普通快速排序-12401248221815401146420
基准值随机快速排序66512581296234116071151542
基准值三数取中快速排序27511661095212315011154526
递归堆排序127218192266209120201467
for循环堆排序118017622059234319491525
归并排序446519488476586678
计数排序744944414143

4.桶排序测试
计数排序的变量只有n
随机数组测试,范围[0, n],n = 10000000,桶个数bc为变量

排序算法bc = 10bc = 100bc = 1000bc = 10000bc = 100000bc = 1000000bc = 10000000
计数排序247296231222224237218
桶排序(使用TimSort排序桶)4756393027312784320741838250
桶排序(使用插入排序桶)----1160637931440
桶排序(使用插入和归并排序桶–自定义数组结构)1730151613431310195023873701

随机+重复数组测试,范围[0, bound], n = 10000000 固定,桶个数bc为变量
bound = 1000000

排序算法bc = 10bc = 100bc = 1000bc = 10000bc = 100000bc = 1000000bc = 10000000
计数排序82827789787981
桶排序(使用TimSort排序桶)4779375328022837323139184375
桶排序(使用插入排序桶)---33850420710091927
桶排序(使用插入和归并排序桶–自定义数组结构)1806146013081211168321292543

bound = 100000

排序算法bc = 10bc = 100bc = 1000bc = 10000bc = 100000bc = 1000000bc = 10000000
计数排序63656563636575
桶排序(使用TimSort排序桶)4208347725502510249330473730
桶排序(使用插入排序桶)--814511262094151167
桶排序(使用插入和归并排序桶–自定义数组结构)1527134011341001163420952749

bound = 10000

排序算法bc = 10bc = 100bc = 1000bc = 10000bc = 100000bc = 1000000bc = 10000000
计数排序44434344444542
桶排序(使用TimSort排序桶)3304295724011609181421572345
桶排序(使用插入排序桶)254422842607116160147369
桶排序(使用插入和归并排序桶–自定义数组结构)134212709909438459701537

bound = 1000

排序算法bc = 10bc = 100bc = 1000bc = 10000bc = 100000bc = 1000000bc = 10000000
计数排序43424243434243
桶排序(使用TimSort排序桶)2912245913551388143217051737
桶排序(使用插入排序桶)126238786111111231201
桶排序(使用插入和归并排序桶–自定义数组结构)119811188578348499351020

bound = 100

排序算法bc = 10bc = 100bc = 1000bc = 10000bc = 100000bc = 1000000bc = 10000000
计数排序44444446434546
桶排序(使用TimSort排序桶)1477405426443441457622
桶排序(使用插入排序桶)443110137137141143239
桶排序(使用插入和归并排序桶–自定义数组结构)1091953832769778803920

bound = 10

排序算法bc = 10bc = 100bc = 1000bc = 10000bc = 100000bc = 1000000bc = 10000000
计数排序46444748485345
桶排序(使用TimSort排序桶)650599512474681519738
桶排序(使用插入排序桶)117134151150135208166
桶排序(使用插入和归并排序桶–自定义数组结构)915807866804885859822

四、总结

  • 插入排序核心思想,从后往前插入已排序序列。插入排序适合数据量小的(5 ~ 20)排序,如果该序列是已排序序列,那么使用 插入排序的时间复杂度是O(n)
  • 快速排序的核心思想,基于基准值,划分数组,直到不能划分为止。普通的快速排序 适合元素重复度不高,且随机的序列。关于快排的各种优化,可以参考该博客三种快速排序以及快速排序的优化
  • 堆排序的核心思想,构建堆和调整堆,弄明白堆的构建就明白了如何利用堆来排序。 堆排序一般适合用来求topN问题,因为快速和稳定性和归并排序都比不了。
  • 归并排序的核心思想,递归分割、回溯合并。它几乎适合所有类型的序列,很稳定,唯一 的缺点只是空间复杂度恒定在O(n)。
  • 计数排序的核心思想,利用数组下标计数,例如10这个数,放入数组中,那么a[10]计数+1。它适合元素均匀分布的序列,如果分布太极端,例如10个元素的数组,最小的是0,最大的是10000000,那么就会创建10000000大小的数组,来排序这10个数。
  • 桶排序的核心思想,分桶排序,即将每个数放入到对应范围的桶中,最后每个桶再单独排序,最后遍历数组输出即可。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值