JAVA常见8大排序小结

前言:整理一下常用排序,包括插入(另带希尔)、选择、冒泡、快排、归并、堆、基数排序。
补充:时间有限,直接附上java代码,有少量注释,链接

参考链接:
1、常见几种java排序算法
2、八大常用排序算法详细分析 包括复杂度,原理和实现

排序对比

注:掌握常用的排序,冒泡、快速、直接插入、直接选择、归并(递归,适合计算逆序)
1、稳定性排序,比如冒泡、插入、归并
2、O(n2),比如冒泡,直接插入、直接选择
3、需要辅助空间,比如归并
4、O(nlogn),比如快排、归并、堆排序
在这里插入图片描述
注:快排最好nlogn, 最差n方,平均nlogn,不稳定
快速排序最好,最坏,平均复杂度分析

注:选择排序时间复杂度全部n方,且不稳定,虽说和插入排序相似都是将前半部分有序,但插入稳定,选择不稳定的

注:
堆排序时间复杂度全部nlogn,不稳定
相比,归并排序时间复杂度也全部是nlogn,但稳定

一、插入排序,附带希尔

package sort;

// 通过移动先使前半部有序
public class InsertSort {
    // 直接插入排序
    public static void insertSort(int[] arr){
        // 遍历数组,若前一个数大于当前元素tmp,则将前面序列中大于tmp的每个数依次后移,再将tmp插入到前面未移动的最后元素后一个
        for (int i = 0; i < arr.length-1; i++) {
            if(arr[i] > arr[i+1]){
                int tmp = arr[i+1];
                int j;
                for(j = i; j >= 0 && arr[j] >tmp; j--){
                    arr[j+1] = arr[j];
                }
                arr[j+1] = tmp; // 此时j位置就是前面未移动的最后元素
            }
        }
    }
    // 希尔排序,分组排序
    public static void shellSort(int[] arr){
        int len = arr.length;
        int step; // 步长
        for(step = len/2; step > 0; step/=2){
            for(int i = step; i < len; i++){
                for(int j = i -step; j >= 0; j-=step){
                    if(arr[j] > arr[j+step]){
                        int tmp = arr[j];
                        arr[j] = arr[j+step];
                        arr[j+step] = tmp;
                    }
                }

            }
        }
    }
}

二、选择排序

package sort;

// 通过交换先使前半部有序
public class SelectSort {
    public static void sort(int[] arr){
        // 选择排序,遍历数组与标记位元素比较,若小于标记位元素就交换,标记位后移,重复,使得序列前部慢慢有序
        for(int i = 0; i < arr.length-1; i++){
            int flagElement = arr[i]; // 设置标志位
            for(int j = i+1; j <arr.length; j++){
                if(arr[j] < flagElement){
                    flagElement = arr[j];// 重置标志位值
                    arr[j] = arr[i];
                    arr[i] = flagElement;
                }
            }
        }
    }
}

三、冒泡排序,附带改进

package sort;

// 通过交换先使后半部有序,冒泡排序也可以更改,若一次遍历中没有交换,则说明已经排序,后面直接退出
public class BubbleSort {
    public static void sort(int[] arr) {
        // 两层遍历,若当前元素大于后一个元素,则交换位置,冒泡思想
        for (int i = 0; i < arr.length; i++) {
            for (int j = 0; j < arr.length - 1 - i; j++) {
                if (arr[j] > arr[j + 1]) {
                    int tmp = arr[j + 1];
                    arr[j + 1] = arr[j];
                    arr[j] = tmp;
                }
            }
        }
    }

    public static void sortPlus(int[] arr) {
        int len = arr.length;
        boolean alreadySort = false; // 初始无序
        for (int i = 0; i < len; i++) {
            alreadySort = true; // 先假设本次遍历不存在前后交换
            for (int j = 0; j < len - 1; j++) {
                if (arr[j] > arr[j + 1]) {
                    int tmp = arr[j + 1];
                    arr[j + 1] = arr[j];
                    arr[j] = tmp;
                    alreadySort = false; // 确实发生了前后交换,即未排好序
                }
            }
            // 若一次遍历中没有发生交换,则可以认为后面都是有序的,退出
            if (alreadySort)
                break;
        }
    }
}

四、快速排序

package sort;

// 快速排序,以基准值辅助交换,划分为左右两个区间,右部分都大于左部分
public class QuickSort {
    public static void sort(int[] array, int low, int high){
        // 若只有一个元素,不需要排序
        if(array == null || low >= high){
            return;
        }
        int i = low, j = high, base = array[low];
        while(i < j){
            // 右边游标先移动,直到小于base基准值停止
            while(i < j && array[j] >= base){
                j--;
            }
            // 在j位置找到一个比base小的值
            if(i < j){
                array[i] = array[j];
                i++; // 排序起始位位置后移一位
            }
            // 左边游标后移动,直到大于base基准值为止
            while(i < j && array[i] <= base){
                i++;
            }
            // 在i位置找到一个比base大的值
            if(i < j){
                array[j] = array[i];
                j--; // 排序的终止位置前移一位
            }
        }
        // 两个游标走到一起,只看j,将基准值与j位置元素对调,则以j位置的基准值就可以将数组分为大于和小于基准值的两部分
        array[j] = base;
        // 再次调整左右两部分数组
        sort(array, low, j-1);
        sort(array, j+1, high);
    }
}

五、归并排序

package sort;

// 归并排序,分治,辅助数组
public class MergeSort {
    public static void sort(int[] array, int low, int high){
        if(array == null || low >= high){
            return;
        }
        int mid = (low+high)/2;
        sort(array, low, mid); // 将左半部分排序
        sort(array, mid+1, high); // 将右半部分排序
        merge(array, low, mid, high); 	// 归并
    }

    private static void merge(int[] array, int low, int mid, int high) {
        // 创建辅助数组
        int[] tmp = new int[high-low+1];
        // p1表示左边部分数组的起点,p2表示右边部分数组的起点,p临时数组添加元素的指向
        int p1 = low, p2 = mid+1, p = 0;
        while (p1 <= mid && p2 <= high){
            if(array[p1] < array[p2]){
                tmp[p++] = array[p1++];
            }else{
                tmp[p++] = array[p2++];
            }
        }
        // 添加两个数组的剩余部分,谁剩余就添加谁
        while(p1 <= mid){
            tmp[p++] = array[p1++];
        }
        while (p2 <= high){
            tmp[p++] = array[p2++];
        }
        // 因为创建了临时数组,所以要覆盖原数组
        for (int i = 0; i < tmp.length; i++) {
            array[low+i] = tmp[i];
        }
    }
}

六、堆排序

注:堆排序的核心就在调整堆函数,至于建堆则从最后一个非叶子结点向前调整即可,建堆也就是调整堆的过程

package sort;

class HeapSort {
    public static void sort(int[] arr) {
        createHeap(arr, arr.length);
        // 排序逻辑,将堆顶元素与尾部元素交换,调整堆,反复执行使得	有序
        for (int j = arr.length - 1; j > 0; j--) {
            swap(arr, 0, j);
            adjustHeap(arr, 0, j);
        }
    }
    // 建堆
    private static void createHeap(int[] arr, int border) {
        for (int i = border / 2 - 1; i >= 0; i--) {
            adjustHeap(arr, i, border); // 从最后一个非叶子加点i,调整,使得左子节点值小于右子节点,右子节点值小于父结点值
        }
    }
    // 核心在这,构建大顶堆,		若构建小顶堆,只需改两个符号
    private static void adjustHeap(int[] arr, int p, int b) {
        int tmp = arr[p]; // P表示父结点,c表示子节点, b表示边界
        for (int c = 2 * p + 1; c < b; c = 2 * c + 1) {
            if (c + 1 < b && arr[c + 1] > arr[c]) // 若构建小顶堆,则右边>这里变为<
                c++;
            if (arr[c] > arr[p]) { // 若构建小顶堆,则这里>变为<
                arr[p] = arr[c]; // 往上传
                p = c; // 改变父结点指向
            }
        }
        arr[p] = tmp;
    }

    private static void swap(int[] arr, int i, int j) {
        int tmp = arr[j];
        arr[j] = arr[i];
        arr[i] = tmp;
    }
}
//
// 下面代码略显啰嗦,但为了方便理解,拆分说明
public class HeapSort {
    public static void sort(int[] arr){
        // 1. 升序,构建大顶堆
        int len = arr.length;
        for(int i = len/2-1; i >= 0; i--){
            // 从最后一个非叶子加点i,调整,使得左子节点值小于右子节点,右子节点值小于父结点值
            adjustHeap(arr, i, len);
        }
        // 将堆顶元素与尾部元素交换,调整堆,反复执行
        for(int j = len-1; j > 0; j--){
            swap(arr, 0, j);
            adjustHeap(arr, 0, j);
        }
    }

    private static void adjustHeap(int[] arr, int parent, int len) {
        int leftChild;
        int largetChild;
        int rightChild;
        // 若左子结点存在,则判断是否需要交换父结点与最大值子节点
        while((leftChild = 2 * parent + 1) < len){
            largetChild = leftChild; // 默认左子结点为最大值字结点
            rightChild = leftChild + 1;
            if(rightChild < len && arr[leftChild] < arr[rightChild]){
                // 若存在右结点,且左节点值小于右结点值,则最大值子结点为右子节点
                largetChild = rightChild;
            }
            if(arr[largetChild] >arr[parent]){
                // 若子节点最大值大于父结点值,则需要交换
                swap(arr, largetChild, parent);
            }
            // 交换过后,子节点就可能不是最大堆了,所以继续向下调整
            parent = largetChild;
        }
    }

    private static void swap(int[] arr, int largetIndex, int parent) {
        int tmp = arr[parent];
        arr[parent] = arr[largetIndex];
        arr[largetIndex] = tmp;
    }
}

七、基数排序

package sort;

public class RadixSort {
    public static void sort(int[] arr, int bitCnt) {
        int len = arr.length;
        int divsor = 1; // 除数,目的取出对应位的数字
        int times = 1; // 排序次数,数组中最大值是bitCnt位
        // 注:如下辅助数组可以尝试其他方式,如d长度(最大值位数),基数r(0到9),可以采取int[d][r]
        int[][] tmp = new int[10][len]; // 辅助数组存放数组元素
        int[] order = new int[10]; // 辅助数组,存放当前位i的元素个数,i = 0,1,...,8,9
        while (times <= bitCnt) {
            int lsd;
            for (int i = 0; i < len; i++) {
                lsd = (arr[i] / divsor) % 10;
                tmp[lsd][order[lsd]] = arr[i]; // 依次将元素存入对应lsd行数组中
                order[lsd]++; // 当前lsd位置存入了一个元素,加一
            }
            // 从tmp数组中按照逐行逐列取出元素,覆盖原数组
            int pos = 0; // 数组指示覆盖游标
            for (int i = 0; i < 10; i++) {
                if (order[i] != 0) {
                    // 表示当前lsd有元素
                    for (int j = 0; j < order[i]; j++) {
                        arr[pos++] = tmp[i][j]; // 覆盖
                        tmp[i][j] = 0; // 清空,下次需要用
                    }
                    order[i] = 0; // 清空
                }
            }
            // 开启下一次高位比较
            times++;
            divsor *= 10;
        }
    }
}

八、对比测试

10w数据:
quickSort耗时: 29 ms
mergeSort: 30 ms
insertSort耗时: 1432 ms
shellSort耗时: 12760 ms
selectSort耗时: 20970 ms
bubble耗时: 24366 ms
arraysSort耗时: 23 ms
heapSort耗时: 19 ms
RadixSort耗时: 14 ms

package sort;

import java.time.Clock;
import java.util.Arrays;
import java.util.Random;

public class Main {
    public static void main(String[] args) {
        int bitCnt = 5;
        //method1();
        method2(bitCnt);
    }

    public static void method1(){
        int len = 13;
        int[] randArr = getRandomArr(len);
        int[] insertArr = randArr.clone();
        int[] selectArr = randArr.clone();
        int[] bubbleArr = randArr.clone();
        int[] quickArr = randArr.clone();
        int[] mergeArr = randArr.clone();
        int[] heapArr = randArr.clone();
        int[] radixArr = randArr.clone();

        System.out.println("原始未排序数组:");
        printArr(randArr);

        System.out.println("插入排序或改进希尔排序:");
        //InsertSort.insertSort(insertArr);
        InsertSort.shellSort(insertArr);
        printArr(insertArr);
        System.out.println("结束...");

        System.out.println("选择排序:");
        SelectSort.sort(selectArr);
        printArr(selectArr);
        System.out.println("结束...");

        System.out.println("冒泡排序或改进冒泡排序:");
        //BubbleSort.sort(bubbleArr);
        BubbleSort.sortPlus(bubbleArr);
        printArr(bubbleArr);
        System.out.println("结束...");

        System.out.println("快速排序起始:");
        QuickSort.sort(quickArr, 0, randArr.length-1);
        printArr(quickArr);
        System.out.println("结束...");

        System.out.println("归并排序起始:");
        MergeSort.sort(mergeArr, 0, randArr.length-1);
        printArr(mergeArr);
        System.out.println("结束...");

        System.out.println("堆排序:");
        HeapSort.sort(heapArr);
        printArr(heapArr);
        System.out.println("结束...");

        System.out.println("基数排序:");
        RadixSort.sort(radixArr, 3);
        printArr(radixArr);
        System.out.println("结束...");

        System.out.println("Arrays自带的排序起始:");
        Arrays.sort(randArr);
        printArr(randArr);
        System.out.println("结束...");
    }


    public static void method2(int bitCnt){
        int len = (int) Math.pow(10, bitCnt);
        int[] randArr =getRandomArr(len);
        int[] quickArr = randArr.clone();
        int[] mergeArr = randArr.clone();
        int[] insertArr = randArr.clone();
        int[] shellArr = randArr.clone();
        int[] selectArr = randArr.clone();
        int[] bubbleArr = randArr.clone();
        int[] heapArr = randArr.clone();
        int[] radixArr = randArr.clone();
        int[] arrays = randArr.clone();

        long s = Clock.systemDefaultZone().millis();
        QuickSort.sort(quickArr, 0, quickArr.length - 1);
        System.out.println("quickSort耗时: " + (Clock.systemDefaultZone().millis() - s) + " ms");

        s = Clock.systemDefaultZone().millis();
        MergeSort.sort(mergeArr, 0, mergeArr.length-1);
        System.out.println("mergeSort: " + (Clock.systemDefaultZone().millis() - s) + " ms");

        s = Clock.systemDefaultZone().millis();
        InsertSort.insertSort(insertArr);
        System.out.println("insertSort耗时: " + (Clock.systemDefaultZone().millis() - s) + " ms");

        s = Clock.systemDefaultZone().millis();
        InsertSort.shellSort(shellArr);
        System.out.println("shellSort耗时: " + (Clock.systemDefaultZone().millis() - s) + " ms");

        s = Clock.systemDefaultZone().millis();
        SelectSort.sort(selectArr);
        System.out.println("selectSort耗时: " + (Clock.systemDefaultZone().millis() - s) + " ms");

        s = Clock.systemDefaultZone().millis();
        BubbleSort.sort(bubbleArr);
        System.out.println("bubble耗时: " + (Clock.systemDefaultZone().millis() - s) + " ms");

        s = Clock.systemDefaultZone().millis();
        Arrays.sort(arrays);
        System.out.println("arraysSort耗时: " + (Clock.systemDefaultZone().millis() - s) + " ms");

        s = Clock.systemDefaultZone().millis();
        HeapSort.sort(heapArr);
        System.out.println("heapSort耗时: " + (Clock.systemDefaultZone().millis() - s) + " ms");

        s = Clock.systemDefaultZone().millis();
        RadixSort.sort(radixArr, bitCnt);
        System.out.println("RadixSort耗时: " + (Clock.systemDefaultZone().millis() - s) + " ms");
    }

    // 生成随机数组
    private static int[] getRandomArr(int length) {
        if(length <= 0){
            return null;
        }
        int[] array = new int[length];
        for (int i = 0; i < length; i++) {
            array[i] = new Random().nextInt(length);
        }
        return  array;
    }

    // 打印数组
    private static void printArr(int[] array){
        for(int e : array){
            System.out.print(e + " ");
        }
        System.out.println();
    }
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值