Java学习必会的七大排序算法

七大排序

冒泡排序

原理:在无序区间,通过相邻数的比较,将最大的数冒泡到无需区间的最后,持续这个过程,直到整体数组有序

时间复杂度:O(n^2)

稳定性:稳定

public static void bubbleSort(int[] arr){
    //排序次数为arr.length-1,最后一个元素已经有序不用排
    for (int i = 0; i < arr.length-1; i++) {
        //i每+1就有一个元素落在最终位置,j无需再访问
        for (int j = 0; j < arr.length-1-i; j++) {
            //若前面一个元素大于后面一个元素,交换两个元素的位置
            if(arr[j] > arr[j+1]){
                swap(arr,j,j+1);
            }
        }
    }
}

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

选择排序

原理:每次从无序区间选出最大(或者最小)的一个元素,存放在无序区间的最后(或最前),知道全部待排序的数据元素排完

时间复杂度:O(n^2)

稳定性:不稳定

public static void selectionSort(int[] arr){
    //无序区间[i,arr.length)
    //有序区间[0,i)
    for (int i = 0; i < arr.length-1; i++) {
        int min=i;
        for (int j =i; j < arr.length; j++) {
            if(arr[j]<arr[min]){
                min=j;
            }
        }
        //走完这个循环,min指向了无序区间的最小值的索引
        swap(arr,i,min);

    }

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

双向选择排序

原理:每次从无序区间中选出最小+最大的元素,存放在区间的最前和最后,直到全部待排序的数据元素排完

时间复杂度:O(n^2)

稳定性:不稳定

public static void selectionOp(int[] arr){
    //无序区间[low,high]
    //有序区间[0,low)和[high+1,arr.length)
    int low=0;
    int high=arr.length-1;

    while (low<high){
        int max=low;
        int min=low;
        //遍历无序数组,找到最大值和最小值对应的下标
        for (int j = low+1; j <= high; j++) {
            if(arr[j]>arr[max]){
                max=j;
            }
            if(arr[j]<arr[min]){
                min=j;
            }
        }
        //将最小值交换到low位置
        swap(arr,min,low);
        low++;
        //如果最大值在low位置,经过上一步后low位置上为最小值,此时最大值在min位置
        if(max==low){
            max=min;
        }
        //将最大值交换到max位置
        swap(arr,high,max);
        high--;
    }

}

插入排序

原理:每次选择无序区间的第一个元素,在有序区间内的合适位置插入

时间复杂度:O(n^2)

稳定性:稳定

  • 经常用在高价排序的优化
  • 最好情况O(n) =>内层循环一次也不走,当已排序集合的最后一个元素<无序区间的第一个元素,内层循环直接退出
public static void insertionSort(int[] arr){
    //无序区间[i,arr.length)
    //有序区间[o,i)
    //默认第一个元素有序
    for (int i = 1; i < arr.length; i++) {
        // 选择无序区间的第一个元素,不断向前看
        // 注意看内层循环的终止条件 j >= 1而不是 j >= 0 ?
        // 因为此时arr[j] 不断向前看一个元素  j - 1 要合法 j - 1 >= 0
        for (int j = i; j >=1 && arr[j]<arr[j-1]; j--) {
            swap(arr,j,j-1);
        }
    }
}
private static void swap(int[] arr, int j, int i) {
    int temp=arr[i];
    arr[i]=arr[j];
    arr[j]=temp;
}

注意:插入排序,初始数据越接近有序,时间效率越高

希尔排序

思想:希尔排序又称缩小增量法。希尔排序法的基本思想是:缩小增量排序,按照gap将原数组分为gap个子数组,子数组内部先排序,不断缩小gap值,直到gap = 1

时间复杂度:O(n^1.3)

稳定性:不稳定

  • 希尔排序是对直接插入排序的优化
  • 当gap>1时都是预排序,目的时让数组更接近有序。当gap==1时,数组已经接近有序的,这样就会很快。这样整体而言,可以达到优化的效果。我们实现后可以性能测试的对比。
public static void shellSort(int[] arr){
    int gap=arr.length/2;
    while (gap>=1){
        insertionSortByGap(arr,gap);
        gap=gap/2;
    }
}

//跟插入排序是一样的思路
private static void insertionSortByGap(int[] arr, int gap) {
    for (int i = gap; i < arr.length; i++) {
        for (int j = i; j >= gap && arr[j]<arr[j-gap]; j-=gap) {
            swap(arr,j,j-gap);
        }
    }
}
private static void swap(int[] arr, int j, int i) {
    int temp=arr[i];
    arr[i]=arr[j];
    arr[j]=temp;
}

归并排序

原理:

  • 归:不断将原数组拆分为子数组,直到每个子数组只剩下一个元素 => 归过程结束
  • 并:不断合并相邻的两个子数组为一个大数字

时间复杂度:O(nlog(n))

稳定性:稳定

 /**
     * 归并排序
     */
    public static void mergeSort(int[] arr){
        mergeSortInternal(arr,0,arr.length - 1);
    }

    /**
     * 在arr[l...r]上进行归并排序
     * @param arr
     * @param l
     * @param r
     */
    private static void mergeSortInternal(int[] arr, int l, int r) {
        if(r-l<=15){
            insertionSort(arr,l,r);
            return;
        }
        int mid=l+(r-l)/2;

        mergeSortInternal(arr,l,mid);
        mergeSortInternal(arr,mid+1,r);
        if(arr[mid]>arr[mid+1]){
            merge(arr,l,mid,r);
        }
    }

    /**
     * 在arr[l..r]上进行插入排序
     * @param arr
     * @param l
     * @param r
     */
    private static void insertionSort(int[] arr, int l, int r) {
        for (int i = l + 1; i <= r; i++) {
            for (int j = i; j >= l + 1 && arr[j] < arr[j - 1]; j--) {
                swap(arr,j,j - 1);
            }
        }
    }

    private static void merge(int[] arr, int l, int mid, int r) {
        int[] aux=new int[r-l+1];
        for (int i = 0; i < aux.length; i++) {
            aux[i] = arr[l + i];
        }
            //数组1的下标
            int i=l;
            //数组2的下标
            int j=mid+1;
            for (int k = l; k <= r ; k++) {
                if(i>mid){
                    arr[k] =aux[j-l];
                    j++;
                }else if(j>r){
                    arr[k]=aux[i-l];
                    i++;
                }else if(aux[i-l]<=aux[j-l]){
                    arr[k]=aux[i-l];
                    i++;
                }else {
                    arr[k]=aux[j-l];
                    j++;
                }

            }
        }
    }

快速排序

核心思路:分区

分区值默认选择最左侧元素pivot,从无序区间选择一个值作为分界点pivot开始扫描原数组,将数组中索引小于pivot的元素放在分区点的右侧。经过本轮交换,pivot放在了最终位置,pivot的左侧都是小于该值的元素,pivot的右侧都是大于该元素的值,在这两个子区间重复上述过程,直到整个数组有序

时间复杂度:O(nlog(n))

空间复杂度:O(log(n))

稳定性:不稳定

private static void quickSortInternal(int[] arr, int l, int r) {

    //区间内不存在元素或者只有一个元素
    if(l >= r){
        return;
    }
    //将元素p放到最终位置
    int p=partaition(arr,l,r);
    //继续排序左右区间
    quickSortInternal(arr,l,p-1);
    quickSortInternal(arr,p+1,r);

}
private static ThreadLocalRandom random=ThreadLocalRandom.current();
private static int partaition(int[] arr, int l, int r) {

    //每次递归时选择数组任意一个位置的元素,生成[l...r上的随机数,每次
    int randIndex=random.nextInt(l,r+1);
    swap(arr,l,randIndex);
    int v=arr[l];
    //小于v的区间为[l+1,j]
    //大于v的区间为[j+1,i)
    int j=l;
    for (int i = l+1; i <= r; i++) {
        //arr[i]>v,i直接++,扩大大于v的区间
        if(arr[i]<v){
            //索引j指向了最后一个小于v的元素,j+1恰好是第一个>= v的元素
            swap(arr,i,j+1);
            j++;
        }
    }
    //走完这个循环,j就是最后一个小于v的元素,交换v与arr[j]
    swap(arr,l,j);
    return j;
}
private static void swap(int[] arr, int j, int i) {
    int temp=arr[i];
    arr[i]=arr[j];
    arr[j]=temp;
}

Hoare挖坑法

private static int partition(int[] arr,int l,int r){
    int randomIndex= random.nextInt(l,r+1);
    swap(arr,l,randomIndex);
    int v=arr[l];
    int i=l,j=r;
    while (i<j){
        //1.先让j从后往前走,直到碰到第一个<v的元素索引停下
        while (i<j && arr[j]>=v){
            j--;
        }
        arr[i]=arr[j];
        //2.让i从前往后走,直到碰到第一个>v的元素索引停下
        while (i<j && arr[i]<=v){
            i++;
        }
        arr[j]=arr[i];
    }
    //3.当i=j时,数组已经有序,让arr[i或j]=v即可
    arr[i]=v;
    return i;
}

堆排序

原理:

  • 任意数组都可以看作一个完全二叉树,然后将其调整为最大堆
  • 不断交换堆顶元素和最后一个元素的位置,将堆顶元素不断进行siftdown操作,将最大值放在最终位置,直到数组只剩下一个元素

时间复杂度:O(nlogn),n是因为要对n个节点进行与根节点交换的操作,logn是由于进行下沉操作的时间复杂度,是基于树操作的,所以是logn

public static void heapSort(int[] arr){
    //1.先将任意数组进行heapify操作调整为最大堆
    for (int i = (arr.length-1)/2; i >=0 ; i--) {
        siftDown(arr,i,arr.length);
    }
    //2.不断交换堆顶元素到数组末尾,每交换一个元素,就有一个元素落在了最终位置
    for (int i = arr.length-1;i > 0; i--) {
        //arr[0]就是未排序数组的最大值,交换到末尾
        swap(arr,0,i);
        //假如i为arr.length-1,交换arr[0]与arr[arr.length-1]后,需要堆化的数组长度为arr.length-1
        siftDown(arr,0,i);
    }

}
private static void siftDown(int[] arr,int k,int length) {
    while (2*k+1 < length){
        //还有左子树
        int j=2*k+1;
        //还存在右子树,且左子树的值小于右子树的值
        if(j+1< length && arr[j]<arr[j+1]){
            j=j+1;
        }
        //j为左右子树最大值对应的索引
        if(arr[k]>arr[j]){
            break;
        }else {
            swap(arr,k,j);
            k=j;
        }
    }
}

总结

在这里插入图片描述

在这里插入图片描述

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值