常见的8中排序算法

包含冒泡排序,选择排序,插入排序,希尔排序,快速排序,归并排序,基数排序,堆排序

声明:下面的代码只是一个最基础的实现,没有经过严格的测试。

/**
 * 排序
 */
public class Sort {
    public static void main(String[] args) {
/*        int[] arr=new int[8000000];
        for (int i = 0; i < arr.length; i++) {
            arr[i]=(int) (Math.random()*80000000);

        }
        long start=System.currentTimeMillis();*/
        //int[] temp=new int[arr.length];
        //mergeSort(arr,0,arr.length-1,temp);
        //quickSort(arr,0,arr.length-1);
        //shellSort(arr);
/*        heapSort(arr);
        long end=System.currentTimeMillis();
        System.out.println("执行时间:"+(end-start)/1000+"秒");*/
        int[] arr={5,7,8,3,1,2,4,6,0,-3,0};
        shellSort(arr);

        System.out.println(Arrays.toString(arr));
    }

    /**
     * 冒泡排序
     * 基本原理:从尾至头循环一遍,相邻的两个元素进行比较,如果前面的元素比后面大,则两个元素进行交换。
     * 每进行一次循环,则会将一个“最大”的元素移至尾部,类似水池冒泡,每进行一次循环,一个水泡就会冒上来。
     *
     * 上面的操作循环length-1次时,所有元素排序完毕。
     * 执行时间(本电脑中);8w随机数花费16秒
     * 时间复杂度:O(n^2)
     * @param arr
     */
    static void bubbleSort(int[] arr){
        for (int j = 0; j < arr.length-1; j++) {

            //循环次数-j的原因:因为每循环一次,就会将一个“最大”的元素移至尾部,这些元素不用再参与比较了。只有前面的元素才参与比较。
            //可以不用-j依然可以实现排序,即已经排序好的元素继续参与排序,但效率低。
            for (int i = 0; i < arr.length-1-j; i++) {
                if(arr[i]>arr[i+1]){
                    int temp=arr[i+1];
                    arr[i+1]=arr[i];
                    arr[i]=temp;
                }
            }

        }
    }


    /**
     * 选择排序,第一次循环寻找最小元素,放到第一个位置上。第二次循环寻找次小元素,放到第二个位置上。以此类推
     *  执行时间(本电脑中);8w随机数花费7秒
     *  时间复杂度:O(n^2)
     * @param arr
     */
    static void selectionSort(int[] arr){
        //1,第一次循环寻找最小元素,第二次循环寻找次小元素,以此类推,循环。循环次数为length-1次。当循环length-1次时,只剩最后一个元素没排序,
        //而该元素肯定是最大的
        //j是每次比较时的起始指针,从该起始指针开始往后查找最小值,查找到之后和起始指针对应的值交换。起始指针移向下一位
        for (int j = 0; j < arr.length-1; j++) {
            //从第j个元素开始比较,每轮比较前都认为j是最小元素对应的下标
            int minIndex=j;
            for (int i = j+1; i < arr.length; i++) {
                //循环获取最小的元素的下标
                if(arr[i]<arr[minIndex]){
                    minIndex=i;
                }
            }
            //找到最小的元素后,和第j个元素交换位置(第一次循环是和第一个元素交换,第二次循环是和第二个元素交换,第三次循环是和第三个元素交换,依次类推)
            int temp=arr[minIndex];
            arr[minIndex]=arr[j];
            arr[j]=temp;
        }
    }

    /**
     * 插入排序(逻辑上认为数组分两部分,前面的有序部分和后面的无序部分,循环遍历无序部分,将无序部分的元素插入到有序部分中(需找到合适的插入位置))
     * 寻找插入位置的同时需要移动元素
     * 特点:序列的有序性越高,则每次移动次数越少,效率越高
     * 运行时间(本电脑中);8w随机数花费1秒,80w随机数超过100秒
     * 时间复杂度:O(n^2)
     * @param arr
     */
    static void insertionSort(int[] arr){

        //默认认为第一个元素是有序的,从第二个元素开始才是无序的部分
        for (int i = 1; i < arr.length; i++) {
            //无序部分需要插入的元素(无序部分的第一个元素)
            int insertVal=arr[i];
            //待插入的位置。初始值为有序部分的最后一个元素的下标
            int insertIndex=i-1;
            //如果待插入元素比有序部分的元素小(有效部分从右向左遍历,因此insertIndex--),则说明还没有找到待插入的位置,继续寻找。直到大于或者等于为止。
            //如果没有找到,说明待插入的位置在前面,每次循环需要移动元素(这种移动方式会导致移动的相邻的两个元素相同(值覆盖),因此最后需要重新赋值)
            while(insertIndex>=0 && insertVal<arr[insertIndex]){
                //如果待插入的值比当前有序序列的值小,说明待人插的值一定在当前有序序列的值的前面,当前有序序列的值后移一位,给待插入的值让出地方
                arr[insertIndex + 1] = arr[insertIndex];
                insertIndex--;
            }

            //当while循环结束时,说明待插入的位置已经找到
            arr[insertIndex + 1]=insertVal;

        }
    }

    /**
     * 希尔排序:遵循先小组进行排序,每一组内先有序。然后大组排序,整体上的有序性随着组的增大而提升,当步长为0时,则排好序了
     * 采用冒泡法(效率非常低,极少使用。平常说的希尔排序都是指希尔-插入法)
     * @param arr
     */
    static void shellTestSort(int[] arr){
        int length=arr.length;
        //gap为每次分组之后的步长(增量)
        for (int gap = length/2; gap >0;gap/=2) {
            //遍历各组中的所有元素(共有gap组,每个组有length/2个元素),步长为gap。举例当length=10,gap=2时,10个元素分
            //2组,每个组两两进行比较,恰好需要8次比较
            for(int i=gap;i<length;i++){
                //为什么是j-=gap?因为无论是冒泡还是插入算法,需要两层循环。而j-=gap保证了当j>gap时,会开始第二轮循环
                for(int j=i-gap;j>=0;j-=gap){
                    //如果当前元素大于加上步长后的那个元素,则进行交换
                    if(arr[j]>arr[j+gap]){
                        int temp=arr[j];
                        arr[j]=arr[j+gap];
                        arr[j+gap]=temp;
                    }
                }

            }
        }

    }

    /**
     * 希尔排序,采用插入法
     * 遵循先小组进行排序,每一组内先有序。然后大组排序,整体上的有序性随着组的增大而提升,当步长为0时,则排好序了
     * 分组是为了提升序列的有序性,对于插入排序,有序性越高,效率越高。
     * 运行时间(本电脑中);80w随机数花费小于1秒,800w随机数4秒
     * 时间复杂度:O(n^(1.3-2))
     * 参考链接https://blog.csdn.net/qq_39207948/article/details/80006224
     * @param arr
     */
    static void shellSort(int[] arr){
        int length=arr.length;
        //gap为每次分组之后的步长(增量)
        for (int gap = length/2; gap >0;gap/=2) {

            //1,直接对照插入排序,将+1,-1的操作,替换成+gap和-gap的操作即可。因为逻辑分组后,每组序列的相邻元素下标不再相差为1,而是相差gap
            //2,为什么i是从gap开始,因为插入排序从1开始,即第二个元素。分组后,第二个元素对应的下标就是gap
            //3,i++而不是i+=gap,说明并不是一个组排序结束之后再去对另一个组进行排序,而是各组轮流排序。比如第一组4个元素,第二组4个元素,第一次循环比较
            //第一组的前2个元素,和第二组的前2个元素。第二次循环比较第一组的前3个元素和第二组的前3个元素。第三次循环时,第一组和第二组都比较完毕。
            for(int i=gap;i<length;i++){
                //无序部分需要插入的元素(无序部分的第一个元素)
                int insertVal=arr[i];
                //待插入的位置。初始值为有序部分的最后一个元素的下标
                int insertIndex=i-gap;
                while(insertIndex>=0 && insertVal<arr[insertIndex]){
                    //如果待插入的值比当前有序序列的值小,说明待人插的值一定在当前有序序列的值的前面,当前有序序列的值后移一位,给待插入的值让出地方
                    arr[insertIndex + gap] = arr[insertIndex];
                    insertIndex-=gap;
                }
                //当while循环结束时,说明待插入的位置已经找到
                arr[insertIndex + gap]=insertVal;
            }
        }

    }

    /**
     * 1,快速排序:每一轮处理其实就是将这一轮的基准数归位,直到所有的数都归位为止,排序就结束了
     * 2,本程序设定“基准数”为待排序列中最左边的数
     * 参考链接:https://blog.csdn.net/shujuelin/article/details/82423852
     * 运行时间(本电脑中);800w随机数不到2秒
     * 时间复杂度:平均,最好,都是O(nlogn),最坏是O(n^2)
     * @param arr
     * @param start
     * @param end
     */
    static void quickSort(int[] arr, int start,int end){
        //右指针,每次递减
        int right=end;
        //左指针,每次递增 left 一定不能大于right。当left和right相等时,left或者right下标对应的数和基准数交换位置
        int left=start;
        //递归退出条件
        if(left>right){
            return;
        }
        //基准数,第一次排序时,基准数是数组中的第一个数
        int pivot=arr[start];


        while (left<right){
            //右指针从右向左查找比基准数小的数(如果以最左边的数为基准数,则一定是右指针先查找)
            while(pivot<=arr[right] && left<right){
                right--;
            }

            //左指针从左向右查找比基准数大的数
            while(pivot>=arr[left] && left<right){
                left++;
            }
            //代码执行到这里时,右指针和左指针一定找到了一个比基准数小和比基准数大的数,交换位置
                int temp=arr[left];
                arr[left]=arr[right];
                arr[right]=temp;

        }

        //大循环结束时(left<right不满足),left一定等于right,左指针和右指针相遇,这时相遇位置对应的元素和基准数交换位置
        int temp=arr[left];
        arr[left]=pivot;
        arr[start]=temp;

/*        至此,第一排序结束。此时数组被分成两部分,一部分是基准数右边都比基准数大的数,一部分是基准数左边比基准数都小的数。
        对基准数两边的序列重复上边的步骤,使用递归*/

        //右半部分进行递归排序
        quickSort(arr,right+1,end);
        //左半部分进行递归排序
        quickSort(arr,start,right-1);

    }

    /**
     * 归并排序
     * 采用分治法。通过递归的方式,将数组分成两部分,然后每一部分继续二分,直到最小的序列只有两个元素(start<end)。然后合并。借助
     * 一个临时数组,存放两个合并的序列数据,序列合并完毕后,将临时数组的中的元素再复制到这两个序列构成的长序列中。
     * 运行时间(本电脑中);800w随机数2秒
     * 时间复杂度:平均,最好,最差都是O(nlogn)
     * @param arr
     * @param start 拆分(逻辑上)序列的起始下标
     * @param end  拆分(逻辑上)序列的结束下标
     * @param temp
     */
    static void mergeSort(int[] arr, int start,int end,int[] temp){
        if(start<end){
            int middle=(start+end)/2;
            mergeSort(arr, start, middle, temp);
            mergeSort(arr, middle+1, end, temp);
            mergeArray(arr,start,middle,end,temp);
        }
    }

    /**
     * 参考链接:https://www.runoob.com/w3cnote/sort-algorithm-summary.html
     * 合并 :将两个序列arr[start--middle],arr[middle+1-end]合并(实际上是一个数组,根据下标在逻辑上分成两个数组)
     * @param arr
     * @param start 左半部分序列起点
     * @param middle  middle+1为右半部分序列的起点,middle为左半部分序列最后一个元素的下标
     * @param end 右半部分序列最后一个元素的下标
     * @param temp
     */

    static void mergeArray(int[] arr, int start,int middle,int end,int[] temp){
        //定义两个指针,都从序列的最左边开始
        int startP = start,middleP=middle+1,tPointer=0;
        //循环条件,左半部分序列的指针小于middle且右半部分的指针小于end
        while(startP<=middle && middleP<=end){

            //如果左指针对应的节点小于等于右指针对应的元素,则将左指针对应的元素放到临时数组中。反之亦然
            if(arr[startP]<=arr[middleP]){
                temp[tPointer]=arr[startP];
                tPointer ++;
                startP ++ ;
            }else{
                temp[tPointer]=arr[middleP];
                tPointer ++;
                middleP ++;
            }

        }

        //如果左指针还没移动到头(左半部分的头对应的下标是middle),则将剩下的那部分移动到临时数组
        while(startP<=middle){
            temp[tPointer]=arr[startP];
            tPointer ++;
            startP++;
        }
        //同上
        while(middleP<=end){
            temp[tPointer]=arr[middleP];
            tPointer ++;
            middleP++;
        }


        //临时数组中的值复制回原数组
        for(int i=0;i<tPointer;i++){
            arr[start + i] = temp[i];
        }
    }

    /**
     *  补充内容:合并两个有序的数组,归并排序中就是不断合并两个有序的数组
     * @param a 有序数组a
     * @param aLength 数组a长度
     * @param b 有序数组b
     * @param bLength 数组b长度
     */
    static void merge2Array(int[] a, int aLength,int[] b,int bLength){
        int[] temp=new int[aLength+bLength];
        //a数组和b数组分别定义两个指针,都从最左边开始
        int aPointer = 0,bPointer=0,tPointer=0;

        while(aPointer<aLength && bPointer<bLength){
            if(a[aPointer]<b[bPointer]){
                temp[tPointer]=a[aPointer];
                tPointer++;
                aPointer++;
            }else{
                temp[tPointer]=b[bPointer];
                tPointer++;
                bPointer++;

            }
        }
        //如果b数组的指针还没移动到头,则a数组的所有元素肯定已经放到temp数组了,将b数组剩下的元素按顺序放入temp数组
        while(bPointer<bLength){
            temp[tPointer]=b[bPointer];
            tPointer++;
            bPointer++;
        }
        //a数组的指针还没移动到头,同上
        while(aPointer<aLength){
            temp[tPointer]=a[aPointer];
            tPointer++;
            aPointer++;
        }

        System.err.println(Arrays.toString(temp));

    }

    /**
     * 基数排序(先不考虑负数和小数的情况),
     * 用空间换时间,是将整数按位数切割成不同的数字,然后按每个位数分别比较。每比较一轮将桶中的数据按顺序赋值给arr。比较的次数取决于最大数的位数(不考虑负数和小数)
     * 运行时间(本电脑中);800w随机数小于1秒,8000w个随机数排序报OOM,
     * 时间复杂度:平均,最好,最差都是O(n * k,k表示桶的个数)
     * @param arr
     */
    static  void radixSort(int[] arr){
        //1,先获取数组中最大数的位数(长度)
         int max=arr[0];
        for (int i = 1; i < arr.length; i++) {
            if(arr[i]>max){
                max=arr[i];
            }
        }

        int maxLength=(max+"").length();
        //定义10个桶,表示0-9。是一个二维数组。
        int[][] bucket=new int[10][arr.length];



        for(int i=0,n=1;i<maxLength;i++,n*=10){
            //每个桶有多少个元素,即二维数组每行有多少个有效数据
            int[] bucketSize=new int[10];

            /* 取出数组中的每个元素,按照对应位数的值,放到对应的桶中。第一次取的个位上的值,公式是:n/1 % 10。取十位上的值对应的
            公式为n/10 % 10,百位为n/100 % 10      */
            for (int j = 0; j < arr.length; j++) {
                //例如,如果取个位上的值,某个元素个位上的值为3,则将其放入下标为3的桶中,桶的容量+1
                int digit=arr[j]/n % 10;
                bucket[digit][bucketSize[digit]]=arr[j];
                //桶容量自增
                bucketSize[digit]++;
            }

            //放入到桶中后,将桶中的元素按顺序取出,放入到原数组arr中
            int index=0;
            for(int k=0;k<bucketSize.length;k++){
                //获取每一个桶的容量
                int size=bucketSize[k];

                for(int s=0;s<size;s++){
                    //桶中的元素取出,赋值给arr
                    arr[index]=bucket[k][s];
                    index++;
                }

            }

        }
    }

    /**
     * 堆排序
     * 堆的定义:堆是一个近似完全二叉树的结构。如果是大顶堆,则每一个父节点都大于等于其子节点。小顶堆反之。对应元素对应
     * 数组中的第一个元素
     * 运行时间(本电脑中);800w随机数小于4秒.
     * 时间复杂度:平均,最好,最差都是O(nlogn)
     * 参考链接:https://www.cnblogs.com/luomeng/p/10618709.html
     * @param arr 需要排序的数组
     */
    static void heapSort(int arr[]){
        //将无序队列构成一个标准的堆,升序是大顶堆,降序是小顶堆。这里是成一个大顶堆
        // arr.length/2-1(二叉树顺序存储中的公式)为第一个非叶子节点的下标,该节点调整后,再调整上一个节点,直到序列中的第一节点
        //序列[4,2,5,1,6,3,0,8]执行完该代码成一个大顶堆:[8, 6, 5, 2, 4, 3, 0, 1]
        for(int i = arr.length/2-1;i>=0;i--){
            adjustHeap(arr,i,arr.length);
        }

        //将堆顶元素与末尾元素交换,最大元素“沉”到数组末端。“沉”到末端的元素,不再参与排序和堆结构调整,所以j--,堆会越来越小
        for(int j = arr.length-1; j>=0;j--){
            int temp=arr[j];
            arr[j]=arr[0];
            arr[0]=temp;
            //重新调整结构使其满足堆定义,然后继续交换堆顶元素与末尾元素,反复执行,直到序列有序
            //为什么第二个参数是0?因为堆顶元素与最后一个元素交换之后,只有下标为0的元素不满足堆的定义,其他的元素仍然
            //满足堆的定义,因此只调整下标为0的元素顺序即可
            adjustHeap(arr,0,j);
        }

    }
    static void adjustHeap(int[] arr,int i,int length){
        //先取出当前元素的值,保存到临时变量中
        int temp=arr[i];
        // k=i*2+1 ,其中k是i的左子节点 ,顺序存储二叉树中的公式,i*2+2表示i的右子节点
        for(int k=i*2+1;k<length;k=k*2+1){
            //如果右子节点的值比左子节点大,k指向右子节点
            if(k+1<length && arr[k]<arr[k+1]){
                k++;
            }
            //如果子节点(左节点或者右节点)大于父节点,则将该子节点的值赋给父节点
            if(arr[k] > temp){
                arr[i] = arr[k];
                // i指向k,继续循环
                i = k;
            }else{
                break;
            }
        }
        //当for循环结束后,已经将以i为父节点的数的最大值,放到了最顶(局部),
        //将之前temp的值(进入循环之前的以i为下标的值)赋给调整后的位置上
        arr[i]=temp;

    }
}

 

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值