十大排序算法总结

package aNew;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

public class Sort {
    public static void main(String[] args) {
        int[] array = {63, 157, 189, 51, 101, 47, 141, 121, 157, 156, 194, 117, 98, 139, 67, 133, 181, 13, 28, 109};
        bucketSort(array,10);
        System.out.println(Arrays.toString(array));
    }

    /**
     *冒泡排序(交换)
     * 思路:从头开始两个两个比较,谁小就往前移动,每次能把最大的数排好,进行n次遍历从而排好n个数
     * 稳定排序:因为交换是相邻两个数交换,所以原来大小一样的两个数次序不变
     * 原址排序
     * 时间复杂度:最好O(n),最差O(n2),平均O(n2)
     * 空间复杂度:O(1)
     */
    public static void bubbleSort(int[] array){
        for (int i=1;i< array.length;i++){
            int flag=1; // 设置一个标志位,若在一次遍历中没有数要交换位置,表明这个数组已经排好序,可以使其最好时间复杂度变为O(n)
            for (int j=0;j< array.length-i;j++){
                if (array[j]>array[j+1]){ // 若前一个数比后一个数大,则进行交换
                    flag=0;
                    int tmp=array[j];
                    array[j]=array[j+1];
                    array[j+1]=tmp;
                }
            }
            if (flag==1)
                break;
        }
    }
    /**
     *快速排序(交换)
     * 思路:采用分治的思想,使用partation进行分区,选取第一个数为基准数,经过一次遍历后
     * 比它小的都在它前面,比它大的数都在它后面,也就是说这个基准数已经排好序了
     * 之后再对它前面的区间和后面的区间进行排序
     * partation时间复杂度为O(n)空间复杂度为O(1)
     * 不稳定排序:想象有两个相等的数,partation时后面那个数正好在基准值的位置上,与第一个基准值交换位置后,原来在后面的数现在跑到了前面
     * 原址排序
     * 时间复杂度:最好O(n),最坏O(n2),平均O(nlgn)
     */
    public static int Partation(int[] array,int left,int right){ // [left,right]
        int tmp,pivot,i;
        pivot=array[left]; // 选取最左边的数为基准值
        i=left; // i表示array[i]小于基准值的最大下标
        for (int j=i+1;j<=right;j++){
            if (array[j]<pivot){ // 若找到了比基准值小的数,将其与array[i+1]的数进行交换,array[i+1]要么就是array[j]要么就比基准值大
                i++;
                tmp=array[i];
                array[i]=array[j];
                array[j]=tmp;
            }
        }
        array[left]=array[i]; // 将基准值与array[i]进行交换,这样基准值前面的数都比它小,后面的数都比它大
        array[i]=pivot;
        return i;
    }
    public static void quickSort(int[] array,int left,int right){ // [left,right]
        if (left<right){
            int positon= Partation(array,left,right); // 通过partation函数下标为positon的数已经在正确位置上,下面分别对其前面和后面的数进行排序
            quickSort(array,left,positon-1);
            quickSort(array,positon+1,right);
        }
    }

    /**
     * 插入排序(插入)
     *思路:想象你在打扑克,j为你抽到的牌,在一个遍历中,你每抽到一张牌你就把它放到相应的位置上
     * 你会把j与它前面的牌比较,如果前面的牌大,则把前面的牌后移一位,直到遇到比它小的或已经到了-1位
     * 然后将其插入到比它小的这张牌的后面
     * 稳定排序:当遇到比摸到的牌大的两个相同的数时会依次被后移,而不会打乱顺序
     * 原址排序
     * 时间复杂度:最好O(n) 最坏O(n2),平均O(n2)
     * 空间复杂度:O(1)
     */

    public static void insertionSort(int[] array){
        for (int j=0;j<array.length;j++){
            int key=array[j]; // 摸到的牌,j前面的牌都已经排好序
            int i=j-1; // 前一张牌
            while (i>=0 && array[i]>key){
                array[i+1]=array[i]; // 如果前面的牌与摸到的大,就把前面的牌向后移一位,因为i是从后往前遍历,所以只要向后赋值就行
                i--; // 再看前面的牌
            }
            array[i+1]=key; // 插入到第一张比摸到的牌小的后面
        }
    }

    /**
     * 希尔排序(插入)
     * 思路:希尔排序是对插入排序的优化,它对要排序的数组进行分组,每次在各个分组中进行插入排序
     * 采用希尔增量gap=length/2,并且每次gap/=2,使排序的次数变为lgn次
     * 不稳定:采用分组排序的方法,相同的数在不同的组中排序,可能会被打乱
     * 原址排序
     * 时间复杂度:最好O(nlgn)  最坏O(n2)
     * 空间复杂度O(1)
     */
    public static void shellSort(int[] array){
        int gap=array.length/2; // 希尔增量,每次除2
        while (gap>0){
            for (int j=gap;j<array.length;j++){ // 对间隔为gap的同一组数进行插入排序
                int key=array[j]; // 抽出的牌的大小,第一个j其实是第二张牌,后面他会和第一张牌比较大小
                int i=j-gap; // 前一张牌的位置
                while (i>=0 && array[i]>key){
                    array[i+gap]=array[i]; // 如果前一张牌更大,则将其往后移动
                    i-=gap; // 再看看前面的牌
                }
                array[i+gap]=key; // 插入到第一张比抽出的牌小的后面
            }
            gap/=2;
        }
    }

    /**
     * 选择排序(选择)
     * 思路:前面是已排序的,后面是未排序的。每次遍历时从未排序的数选出最小的值,与已排序数的后面一个数交换
     * 总执行次数为n+(n-1)+...+2+1
     * 不稳定排序:每次选出最小的交换位置,两个相同大小的数,前面那个可能会被交换到后面去
     * 原址排序
     * 时间复杂度:都是O(n2)
     * 空间复杂度:O(1)
     */
    public static void selectionSort(int[] array){
        for (int i=0;i<array.length-1;i++){
            int minIndex=i; // i为将要被排序的那个数的下标
            for (int j=i+1;j<array.length;j++){
                if (array[j]<array[minIndex]){ // 遍历还未被排序好的数组,找出最小数的下标
                    minIndex=j;
                }
            }
            if (i!=minIndex){ // 如果最小数的下标不是原来那个数,则进行交换,交换后这个未排序数组中最小数就放到了正确的位置上
                int tmp=array[i];
                array[i]=array[minIndex];
                array[minIndex]=tmp;
            }
        }
    }

    /**
     * 堆排序(选择)->就是每次都选择出最大值
     * 思路:定义一个大堆化的函数,它可以使传入的那个结点比它的两子结点都大,这个函数是递归调用的,
     *      即如果原来就已经是最大堆,改变一个结点,此时对这个结点调用大堆化的函数,那么可以重新让其变为最大堆。
     *      在堆排序时,先从后往前调用大堆化函数,因为大堆化函数在满足当前结点比两个子结点都那时,就停止递归调用了,这个就获得了一个最大堆
     *      交换第一个数与最后一个数,将堆排序的范围减1并对第一个数调用大堆化函数,当堆排序范围变为0时,整个数据就按正序排好了
     * 不稳定排序
     * 原址排序
     * 时间复杂度:总的都为O(nlgn) ,大堆化与高度有关所以为O(lgn) 建堆O(n)
     * 空间复杂度:O(1)
     */

    public static int Parent(int i){ // 获取父结点下标
        return (i+1)/2-1;
    }
    public static int Left(int i){ // 获取左子结点下标
        return (i+1)*2-1;
    }
    public static int Right(int i){ // 获取右子结点下标
        return (i+1)*2;
    }
    public static void maxHeapify(int[] array,int i,int heapSize){
        int left=Left(i);
        int right=Right(i);
        int largest=i;
        if (left<=heapSize && array[largest]<array[left]){ // 判断左子结点是不是最大的
            largest=left;
        }
        if (right<=heapSize && array[largest]<array[right]){ // 判断右子结点是不是最大的
            largest=right;
        }
        if (largest!=i){ // 若最大结点不是当前结点,则与其交换位置后,再对其进行大堆化。就是看看原来那个小的数是不是还要再往下移动
            int tmp=array[i];
            array[i]=array[largest];
            array[largest]=tmp;
            maxHeapify(array,largest,heapSize);
        }
    }
    public static void buildMaxHeap(int[] array){ // 从后往前构建最大堆,因为大堆化遇到父结点比两个子结点都大时就停止了
        for (int i=array.length-1;i>=0;i--){
            maxHeapify(array,i,array.length-1);
        }
    }
    public static void heapSort(int[] array){
        buildMaxHeap(array); // 先构建最大堆
        for (int heapSize=array.length-1;heapSize>0;){
            int tmp=array[0]; // 将第一个数与最后一个数进行交换
            array[0]=array[heapSize];
            array[heapSize]=tmp;
            heapSize--; // 将堆排序范围缩小1
            maxHeapify(array,0,heapSize); // 重新对第一个数进行大堆化
        }
    }

    /**
     * 归并排序
     * 思路:取一个中间数,对前面的所有数调用归并排序,求后面的所有数也调用归并排序,之后对排好序的两组数进行归并
     * 用到了分治的思想,归并时只要遍历一次,但要额外的空间
     * 稳定排序:注意在归并操作当两个数相等时,要让左边数组的数先放入原数组中
     * 非原址排序
     * 时间复杂度:都是O(nlgn), 归并操作O(n),递归使其操作次数为O(lgn)
     * 空间复杂度:O(n)
     */
    public static void merge(int[] array,int p,int q,int r){ // 范围[p,r],第一个数组[p,q],第二个数组[q+1,r]
        int n1=q-p+1;
        int n2=r-q;
        int[] left=new int[n1+1]; // 根据其大小+1创建数组
        int[] right=new int[n2+1];
        int i,j;
        for (i=0;i<n1;i++){ // 将p到q的数放入左数组中
            left[i]=array[p+i];
        }
        for (j=0;j<n2;j++){ // 将q+1到r的数放入右数组中
            right[j]=array[q+1+j];
        }
        left[n1]= right[n2]=Integer.MAX_VALUE; // 将数组最后一个值设置为int最大值,这样在后面归并到一个数组时方便比较
        i=j=0;
        for (;p<=r;p++){ // 进行归并操作,谁小取谁
            if (left[i]<=right[j]){ // 注意这里一定要取到等于号,这样才能保证归并排序是稳定的。当两个数相等时,左边的数组会先被放入原数组中
                array[p]=left[i++];
            }else {
                array[p]=right[j++];
            }
        }
    }
    public static void mergeSort(int[] array,int p,int r){ // 范围[p,r]
        if (p<r){
            int q=(p+r)/2; // 取一个中间值
            mergeSort(array,p,q); // 对左边的数组进行排序
            mergeSort(array,q+1,r); // 对右边数组进行排序
            merge(array,p,q,r); // 左右数组都排好序后进行归并操作
        }
    }

    /**
     * 计数排序
     * 思路:遍历数组,计算出一个数是在这个数组中是第几大的,然后放入新数组即可,这个新数组就是排好序的数组
     * 稳定排序:与后面放入新数组时,从后往前遍历有关,这个使原来后面的数还是在后面
     * 非原址排序
     * 时间复杂度:O(n+k)
     * 空间复杂度:O(n+k)
     */
    public static void countingSort(int[] A,int[] B,int k){ // A为原数组,B为排好序的新数组,k为A数组的最大值
        int[] C=new int[k+1]; // 创建一个存储每个数出现次数的数组
        for (int i=0;i<A.length;i++){
            C[A[i]]++; // 计算每个数出现的次数
        }
        for (int i=1;i<C.length;i++){
            C[i]+=C[i-1]; // 计算<=i的数的个数
        }
        for (int i=A.length-1;i>=0;i--){ // 从后往前前遍历,两个数相同时会先放后面的数
            B[C[A[i]]-1]=A[i]; // C数组中计算的是<=i的数的次数,因为索引从0开始,所以要-1
            C[A[i]]--; // 将这个数出现的次数-1,这样相同的数就会出现在它前面一个位置
        }
    }

    /**
     * 基数排序
     * 思路:按位进行排序,先排序低位后排序高位
     * 稳定排序:中间的排序算法使用计数排序,这样就能使基数排序也是稳定的
     * 时间复杂度:O(dn)
     * 空间复杂度:O(n) 因为桶的数量远小于n所以就省略了,原来时O(n+10),只有0-9这10个桶
     */

    public static int findD(int x,int d){ // 返回一个数第d位的值
        String s = String.valueOf(x);
        if (s.length()<d){ // 如果没有第d位则返回0
            return 0;
        }
        return Character.digit(s.charAt(s.length()-d),10); // 先获取第d位的char值,再将其转化为十进制的int值
    }
    public static void radixCountingSort(int[] A,int[] B,int d){ // 基数排序使用稳定的计数排序,A位原数组,B位接受排序结果的新数组,d为排序第几位
        int[] C=new int[10]; // 每一位都只有0-9
        for (int i=0;i<A.length;i++){
            C[findD(A[i],d)]++; // 计算第d位的次数
        }
        for (int i=1;i<C.length;i++){
            C[i]+=C[i-1]; // 计算<=i的次数
        }
        for (int i=A.length-1;i>=0;i--){
            B[C[findD(A[i],d)]-1]=A[i]; // 将原数组的值按找第d位排第几进行赋值
            C[findD(A[i],d)]--; // 赋值完后将其次数-1
        }
    }
    public static int[] radixSort(int[] A,int d){ // A位原数组,d位数组中最大数的位数
        for (int i=1;i<=d;i++){ // 从低位开始排序
            int[] B=new int[A.length];
            radixCountingSort(A,B,i);
            A=B; // 将排好序的B数组地址赋值给A,进行下一轮的排序
        }
        return A; // 因为java是按值传递的,所以这里必须返回排好序的数组并进行赋值
    }

    /**
     * 桶排序
     * 思路:根据一定的规则将数组中的数映射到不同的桶中,前面的桶中的数一定小于后面的桶中的数,
     *      使用排序算法分别对每个桶中的数进行排序,之后遍历一次将其赋值给原数组即可
     * 稳定排序:主要看中间采用了什么排序算法,这个使用插入排序
     * 非原址排序:排序过程中创建了一定数量的桶,且桶里放数
     * 时间复杂度:平均和最好时间复杂度O(n+k),这里假设k个桶,每个桶排序时间为O(1);最差O(n2)
     * 空间复杂度:O(n+k),用于存放n个数和k桶
     */
    public static int[] findMinMax(int[] array){ // 返回array中最小和最大值,时间复杂度O(n)
        int min=array[0];
        int max=array[0];
        for (int i:array){
            if (i<min){
                min=i;
            }else if (i>max){
                max=i;
            }
        }
        return new int[]{min,max};
    }
    public static void bucketSort(int[] array,int bucketSize){ // bucketSize为桶的容量,自己设置,之后桶的数量依据这个计算得出
        int[] minMax = findMinMax(array); // 获取到最小最大值
        int min=minMax[0];
        int max=minMax[1];
        int bucketCount=(max-min)/bucketSize+1; // 计算桶的数量
        List<Integer>[] buckets=new List[bucketCount]; // buckets为一个数组,数组中存放所有桶,每个桶是一个链表
        for (int i=0;i<buckets.length;i++){
            buckets[i]=new ArrayList<>(); // 初始化每个桶为链表,注意foreach中不能改变元素的值,所以不能使用foreach
        }
        for (int i:array){
            int idx=(i-min)/bucketSize; // 计算出这个数应该放在第几个桶中
            buckets[idx].add(i);
        }
        int k=0;
        for (List<Integer> bucket:buckets){
            if (!bucket.isEmpty()) { // 如果桶为空则直接跳过
                int[] bucketArray = new int[bucket.size()]; // 将链表转换为数组,方便调用其他排序算法
                for (int i = 0; i < bucket.size(); i++) {
                    bucketArray[i] = bucket.get(i);
                }
                insertionSort(bucketArray); // 使用插入排序对每个桶中的数进行排序
                for (int i = 0; i < bucketArray.length; i++) {
                    array[k++] = bucketArray[i]; // 将排好序的数赋值到原来的数组中
                }
            }
        }
    }
    

}

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值