八大内部排序算法-归并\快速\基数排序

目录

五、归并排序(MergeSort) 

六、快速排序(QuickSort)

七、基数排序(RadixSort)


五、归并排序(MergeSort) 

归并排序思想:

        归并排序(MERGE-SORT)是利用归并的思想实现的排序方法,该算法采用经典的分治(divide-and-conquer)策略,简单理解就是将大问题变为小问题,然后把所有小问题都解决掉,大问题就迎刃而解了,Java对象排序专用TIM SORT就是改进的归并排序,所以归并排序还是应用比较广泛。其中主要包括两个步骤:

  1. 切分步骤:将大问题变为小问题,通过递归解决更小的子问题。
  2. 解决步骤:将小问题的结果合并,以此找到大问题的答案。

        基本思路:先使每个子序列有序,然后将有序的子序列合并,最后使整个序列有序。使用递归将原始数组递归对半分隔,直到不能再分(只剩下一个元素)后,开始从最小的数组向上归并排序

 分而治之

       使用递归不断对数组进行分割,当分割到每组都只有1个元素时,对于每一组来说当然是有序的,然后往上归并。

  1. 向上归并排序的时候,需要一个暂存数组用来排序,
  2. 将待合并的两个数组,从第一位开始比较,小的放到暂存数组,指针向后移,
  3. 直到一个数组空,这时,不用判断哪个数组空了,直接将两个数组剩下的元素追加到暂存数组里,
  4. 再将暂存数组排序后的元素放到原数组里,两个数组合成一个,这一趟结束。 

        

        以部分数组  [1,4,7,8,3,6,9]为例,当成2个半截数组来排,前半截数组[1,4,6,7,10]为有序的,后半截数组[2,3,5,8,9]为有序的,i指针指前 半截数组的第一个位置,j指针指后半截数组的第一个位置,我们新开辟一个空间temp,k指针指temp的第一个位置。然后i位置与j位置进行比较,谁小就把谁挪到k位置上,然后i或者j往后移动,k往后移动。不过会出现一种情况,可能最后前半截或者后半截会有多的,最后我们直接加入到temp后面去。

把一个两个有序部分合并到一起 

 public static void main(String[] args) {
        int[] arr = {8, 4, 5, 7, 1, 3, 6, 2, 10, 3, 2, 543, 23, 44, 1, 3, 43, 4};
        mergeSort(arr,0,arr.length-1);
        System.out.println(Arrays.toString(arr));
    }
    //分+治
    public static void mergeSort(int[] arr,int left,int right){
        if (left == right) return;
        int mid = (left + right) >> 1;
        mergeSort(arr,left,mid);//向左递归分解
        mergeSort(arr,mid+1,right);//向右递归分解
        merge(arr,left,mid,right);//合并
    }
    //治:合并2个有序数组
    public static void merge(int[] arr,int left,int mid,int right){
        int[] temp = new int[right-left+1];
        int i = left;//左边有序序列的初始索引
        int j = mid + 1;//右边有序序列的初始索引
        int k = 0;//暂存数组下标
        //把左边右两边的数据填充到temp数组,直到左右两边的有序序列,有一边处理完。
        while (i <= mid && j <= right) temp[k++] = arr[i]<=arr[j] ? arr[i++] : arr[j++];
        //以下2个while只有一个会执行,左边或右边有剩余元素全部直接填充到暂存数组后面
        while (i<=mid) temp[k++] = arr[i++];
        while (j<=right) temp[k++] = arr[j++];
        //把最终排序结果复制到原数组
        k = 0;
        while (k<temp.length) arr[left++] = temp[k++];
    }

六、快速排序(QuickSort)

快速排序思想:

        会先把数组中的一个数当做基准数,一般会把数组中最左边的数当做基准数。然后从两边进行检索。先从右边检索(辅助指针i)比基准数小的。 再从左边检索(辅助指针j)比基准数大的。如果检索到了,就停下,然后交换这两个元素。然后再继续检索。

    i和j一旦相遇,就停止检索。把基准数和相遇位置的元素交换,基准数和相遇位置的数交换完成,表示第一轮排序结束。
    特点:基准数左边比它小,基准数右边比它大,先排基准数左边,排完之后再排基准数右边。方式和第一轮一样。

 /*
        快排思路:
        会先把数组中的一一个数当做基准数,一般会把数组中最左边的数当做基准数。
        然后从两边进行检索。先从右边检索比基准数小的。 再从左边检索比基准数大的。
        如果检索到了,就停下,然后交换这两个元素。然后再继续检索。
        i和j一旦相遇,就停止检索。把基准数和相遇位置的元素交换
        基准数和相遇位置的数交换完成,表示第一轮排序结束
        特点:基准数左边比它小,基准数右边比它大,先排基准数左边,排完之后再排基准数右边。方式和第一轮一样。

     */
    /**
     *
     * @param arr
     * @param left 表示从哪个位置开始排
     * @param right 表示排到哪个位置
     */
    public static void quickSort(int[] arr,int left,int right){
        //进行判断,如果左边索引比右边索引要大,是不合法的,直接结束这个方法
        if (left>right)return;
        //定义变量保存基础数
        int base = arr[left];
        //定义变量i指向最左边
        int i = left;
        //定义变量j,指向最右边
        int j = right;
        //当i和j不相遇的时候,在循环中进行检索
        while (i != j){
            //先由j从右往左检索比基准数小的,如果检索到比基准数小的就停下。
            // 如果检索到比基准数大或者相等的,就继续检索
            while (arr[j]>=base && i < j){
                j--;//j从右往左移动
            }
            //i从左往右检索/此时arr[j]小于基准数,此时j停下
            while (arr[i]<=base && i < j){//
                i++;
            }
            //代码走到这里。i停下,j也停下,然后交换i和j位置的元素
            int temp = arr[i];
            arr[i] = arr[j];
            arr[j] = temp;
        }
        //如果上面while循环的条件不成立,会跳出这个循环,往下执行
        //如果这个条件不成立说明i和j相遇了
        //如果i和j相遇了,就交换基准数这个元素和相遇位置的元素
        //把相遇位置的元素赋值给基准数这个位置的元素
        arr[left] = arr[i];
        //把基准数赋值给相遇位置的元素
        arr[i] = base;
        //基准数在这里就归位了,左边的数字都比它小,右边的都比它大。
        //疯狂套娃
        //排基准数的左边
        quickSort(arr,left,i-1);
        //排基准数的右边
        quickSort(arr,i+1,right);
    }

七、基数排序(RadixSort)

基数排序思想:

       基数排序( radix sort )属于“分配式排序”( distribution sort ),又称“桶子法”( bucket sort )或 bin sort ,顾名思义,它是通过键值的各个位的值,将要排序的元素分配至某些“桶”中,达到排序的作用。
       将所有待比较数值统一为同样的数位长度,数位较短的数前面补零。然后,从最低位开始,依次进行一次排序。这样从最低位排序一直到最高位排序完成以后, 数列就变成一个有序序列

 下面是推导过程

    public static void radix(int[] arr){
        //定义一个二维数组,表示10个桶
        //1.10表示10个桶
        //2.表示桶的容量,为了防止在放入数的时候,数据溢出,大小定为arr.length,每个桶最多可装arr.length个元素
        //显然基数排序是典型的空间换时间的算法
        int[][] bucket = new int[10][arr.length];
        //为了记录每个桶中,实际存放了多少个数据,也方便从每个桶取出,所以必须定义一个一维数组来记录每个桶的每次放入的数据个数
        int[] bucketElementCounts = new int[10];//比如bucketElementCounts[0]记录的就是第一个桶bucket[0]里面元素的个数

        /*         第一轮            */
        for (int i = 0;i<arr.length;i++){
            //取出每个元素的个位
            int digitOfElement = arr[i] % 10;
            //放入到对应的桶中
            //bucketElementCounts[digitOfElement]  正好初始默认值为0,可以用于记录每个桶里面元素的个数,加入一个,自增一次,方便下次加入
            bucket[digitOfElement][bucketElementCounts[digitOfElement]++] = arr[i];
        }
        //按照桶的顺序(一维数组的下标依次取出数据,放入原来数组)
        int index = 0;
        //遍历每一个桶并将桶中的数据放入到原数组
        for (int j = 0;j < 10;j++){
            //如果桶中有数据,我们才放入到原数组
            if (bucketElementCounts[j] != 0){
                //循环该桶即第k个桶(即第k个一维数组)
                for (int k = 0; k < bucketElementCounts[j]; k++) {
                    //取出元素放入到arr中
                    arr[index++] = bucket[j][k];
                }
            }
            bucketElementCounts[j] = 0;//必须将表示桶中个数的计数数组清空,超级重要的一步
        }
        System.out.println("第一轮"+Arrays.toString(arr));

        /*         第二轮            */
        for (int i = 0;i<arr.length;i++){
            //取出每个元素的十位
            int digitOfElement = arr[i] / 10 % 10;
            //放入到对应的桶中
            //bucketElementCounts[digitOfElement]  正好初始默认值为0,可以用于记录每个桶里面元素的个数,加入一个,自增一次,方便下次加入
            bucket[digitOfElement][bucketElementCounts[digitOfElement]++] = arr[i];
        }
        //按照桶的顺序(一维数组的下标依次取出数据,放入原来数组)
        index = 0;
        //遍历每一个桶并将桶中的数据放入到原数组
        for (int j = 0;j < 10;j++){
            //如果桶中有数据,我们才放入到原数组
            if (bucketElementCounts[j] != 0){
                //循环该桶即第k个桶(即第k个一维数组)
                for (int k = 0; k < bucketElementCounts[j]; k++) {
                    //取出元素放入到arr中
                    arr[index++] = bucket[j][k];
                }
            }
            bucketElementCounts[j] = 0;//必须将表示桶中个数的计数数组清空,超级重要的一步
        }
        System.out.println("第二轮"+Arrays.toString(arr));

        /*         第三轮            */
        for (int i = 0;i<arr.length;i++){
            //取出每个元素的百位
            int digitOfElement = arr[i] / 100 % 10;
            //放入到对应的桶中
            //bucketElementCounts[digitOfElement]  正好初始默认值为0,可以用于记录每个桶里面元素的个数,加入一个,自增一次,方便下次加入
            bucket[digitOfElement][bucketElementCounts[digitOfElement]++] = arr[i];
        }
        //按照桶的顺序(一维数组的下标依次取出数据,放入原来数组)
        index = 0;
        //遍历每一个桶并将桶中的数据放入到原数组
        for (int j = 0;j < 10;j++){
            //如果桶中有数据,我们才放入到原数组
            if (bucketElementCounts[j] != 0){
                //循环该桶即第k个桶(即第k个一维数组)
                for (int k = 0; k < bucketElementCounts[j]; k++) {
                    //取出元素放入到arr中
                    arr[index++] = bucket[j][k];
                }
            }
            bucketElementCounts[j] = 0;//必须将表示桶中个数的计数数组清空,超级重要的一步
        }
        System.out.println("第三轮"+Arrays.toString(arr));

    }

 然后如何找出进行的轮数,显然是根据该数组中最大的值决定的,如果最大数有5位,则就要进行5轮排序。

下面加入最外层循环,进行改进。

public static void radix(int[] arr) {
        //定义一个二维数组,表示10个桶
        //1.10表示10个桶
        //2.表示桶的容量,为了防止在放入数的时候,数据溢出,大小定为arr.length,每个桶最多可装arr.length个元素
        //显然基数排序是典型的空间换时间的算法
        int[][] bucket = new int[10][arr.length];
        //为了记录每个桶中,实际存放了多少个数据,也方便从每个桶取出,所以必须定义一个一维数组来记录每个桶的每次放入的数据个数
        int[] bucketElementCounts = new int[10];//比如bucketElementCounts[0]记录的就是第一个桶bucket[0]里面元素的个数
        //找到最大值
        int max = arr[0];
        for (int i = 1; i < arr.length; i++) {
            if (arr[i] > max) max = arr[i];
        }
        //得到最大数是几位数
        int maxLength = (max + "").length();
        //真正的基数排序开始
        for (int i = 0, n = 1; i < maxLength; i++, n *= 10) {
            for (int j = 0; j < arr.length; j++) {
                //取出每个元素的个位、十位、百位......
                int digitOfElement = arr[j] / n % 10;
                //放入到对应的桶中
                //bucketElementCounts[digitOfElement]  正好初始默认值为0,可以用于记录每个桶里面元素的个数,加入一个,自增一次,方便下次加入
                bucket[digitOfElement][bucketElementCounts[digitOfElement]++] = arr[j];
            }
            //按照桶的顺序(一维数组的下标依次取出数据,放入原来数组)
            int index = 0;
            //遍历每一个桶并将桶中的数据放入到原数组
            for (int j = 0; j < 10; j++) {
                //如果桶中有数据,我们才放入到原数组
                if (bucketElementCounts[j] != 0) {
                    //循环该桶即第k个桶(即第k个一维数组)
                    for (int k = 0; k < bucketElementCounts[j]; k++) {
                        //取出元素放入到arr中
                        arr[index++] = bucket[j][k];
                    }
                }
                //必须将表示桶中个数的计数数组清空,超级重要的一步
                bucketElementCounts[j] = 0;
            }
            System.out.println("第"+(i+1)+"轮排序处理"+Arrays.toString(arr));
        }
    }

 

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值