数据结构与算法-线性排序

桶排序:首先把数据分到对应的桶中,其中每个桶的容量要注意动态扩容,使用快排将每个桶的顺序排好,之后写回原数组中。如果要保证数据的稳定性,那么桶的排序就不能用快排了,估计可以用插入排序等稳定排序算法。

计数排序:特殊的桶排序,把数据分入如干个桶中,每个桶里的数据是一样的,不过桶里记录的是该数据有多少个,之后将每个桶里的数据个数从前往后叠加起来,再使用一个有序数组,从后往前遍历原数组,将原数组按照在桶里的索引值存入有序数组。

基数排序:一组比较多位数的数组,可以按照个位,十位,百位各个位置一一比较排序,按照低位到高位的数据使用计数排序比较,最后就可实现所有数据的排序。代码如下:

package com.freshbin.dataStructAndAlgo.chapter11.mycode;

/**
 * 线性排序
 * @author freshbin
 * @date 2020/4/16 18:16
 */
public class MyLineSorts {
    /**
     * 桶排序
     * 1、首先判断需要多少个桶;
     * 那么就要确定待排序数组的范围,假设最大值为maxValue,最小值为minValue,
     * 假设设定每个桶的范围是10,那么就需要,(maxValue-minValue)/10+1个桶,
     * 2、将待排数组分别放入相应的桶中;
     * 3、对每个桶使用快排排好序;
     * 4、将每个桶中的数据按照顺序放入原数组中。
     *
     * 遇到问题:每个桶的大小如何确定,上面假定10大小为10,那么多出来的空位就会用0补充
     * 如果用0补充,那么快排之后0就会都在前面.
     * 解决:一个桶装好数据之后,把桶的容量再修改一下,修改成实际装的数据容量。
     * 改进解决:在第2步时,用一个数组记录每个桶的数据量,如果为0那么就不进行快排,
     * 如果不为0,表示有数据,那么传入快排的数据范围为0到(该桶的数据量大小-1)
     *
     * 总结:1、一开始将原数组分到桶里的这步,我是先遍历每个桶,其实是可以直接遍历原数组
     * 然后用数学知识,判断这个数是属于哪个桶,那这样就可以知道当前桶索引,
     * 而且桶需要扩容的话,直接定位到该桶索引进行扩容即可。
     * 2、将桶快排这一步,需要在把原数组分到桶里这一步,用一个数组记录每个桶实际数据量
     * 之后快排的时候就可以知道start和end了,快排完毕即可将桶元素搬回原数组。
     *
     * @param arr
     */
    public static void bucketSort(int[] arr, int oneBucketSize) {
        int bucketNum = 0;
//        int oneBucketSize = 10;
        int bucketRange = 10;
        int maxValue = arr[0];
        int minValue = arr[0];
        for(int value : arr) {
            if(value > maxValue) {
                maxValue = value;
            } else if(value < minValue) {
                minValue = value;
            }
        }
        ///1、桶的个数
        bucketNum = (maxValue - minValue) / bucketRange + 1;

        /*
        // 2、将数组数据放入对应的桶中
        int[][] bucketArrs = new int[bucketNum][];
        int bucketIndex = 0;
        while(bucketIndex < bucketNum) {
            // 设置一个桶能装多少数据,如果装不下,后面还要扩容
            int[] bucketArr = new int[oneBucketSize];
            int oneBucketIndex = 0;
            for(int i = 0; i < arr.length; i++) {
                if(arr[i] >= minValue + bucketIndex*bucketRange && arr[i] < minValue + (bucketIndex+1)*bucketRange) {
                    if(oneBucketIndex >= oneBucketSize) {
                        // 桶扩容
                        bucketArr = dilatationBucketSize(bucketArr, oneBucketSize);
                    }
                    bucketArr[oneBucketIndex++] = arr[i];
                }

            }
            // 将桶的容量修改为实际容量
            bucketArr = modifyBucketSize(bucketArr, oneBucketIndex);


            // 3、对每个桶使用快排,这里直接用之前写过的快排方法了
            MySimpleSorts.quickSort(bucketArr, 0, bucketArr.length-1);

            bucketArrs[bucketIndex++] = bucketArr;
        }

        // 4、将每个桶的数组都取出来放入原数组
        int arrIndex = 0;
        for(int i = 0; i < bucketNum; i++) {
            int[] bucketArr = bucketArrs[i];

            for(int j = 0; j < bucketArr.length; j++) {
                if(bucketArr[j] == 0) {
                    break;
                }
                arr[arrIndex++] = bucketArr[j];
            }
        }
        */

        // 2、将数组数据放入对应的桶中
        int[][] bucketArrs = new int[bucketNum][oneBucketSize];
        int[] oneBucketRealSize = new int[bucketNum];
        for(int i = 0; i < arr.length; i++) {
            // 判断该数是放入哪个桶里,数学知识
            int currentBucketIndex = (arr[i]-minValue)/oneBucketSize;
            if(oneBucketRealSize[currentBucketIndex] == bucketArrs[currentBucketIndex].length) {
                // 扩容该桶的容量
                ensureBucketCapacity(bucketArrs, currentBucketIndex);
            }
            bucketArrs[currentBucketIndex][oneBucketRealSize[currentBucketIndex]++] = arr[i];
        }

        // 3、遍历每个桶使用快排,
        // 4、并把快排后的数据直接写回原数组
        int arrIndex = 0;
        for(int i = 0; i < bucketNum; i++) {
            if(oneBucketRealSize[i] == 0) {
                continue;
            }
            // 对有数据的桶进行快排
            MySimpleSorts.quickSort(bucketArrs[i], 0, oneBucketRealSize[i] - 1);
            for(int j = 0; j < oneBucketRealSize[i]; j++) {
                arr[arrIndex++] = bucketArrs[i][j];
            }
        }
    }

    /**
     * 桶扩容
     * @param bucketArrs
     * @param currentBucketIndex
     */
    private static void ensureBucketCapacity(int[][] bucketArrs, int currentBucketIndex) {
        int[] tempArr = new int[bucketArrs[currentBucketIndex].length*2];
        for(int i = 0; i < bucketArrs[currentBucketIndex].length; i++) {
            tempArr[i] = bucketArrs[currentBucketIndex][i];
        }
        bucketArrs[currentBucketIndex] = tempArr;
    }

    private static int[] modifyBucketSize(int[] bucketArr, int oneBucketIndex) {
        int[] realBucketSizeArr = new int[oneBucketIndex];
        for(int i = 0; i < oneBucketIndex; i++) {
            realBucketSizeArr[i] = bucketArr[i];
        }
        return realBucketSizeArr;
    }

    /**
     * 桶扩容
     * @param bucketArr
     * @param oneBucketSize
     * @return
     */
    private static int[] dilatationBucketSize(int[] bucketArr, int oneBucketSize) {
        oneBucketSize = oneBucketSize * 2;
        int[] tempArr = new int[oneBucketSize];
        for(int i = 0; i < bucketArr.length; i++) {
            tempArr[i] = bucketArr[i];
        }
        return tempArr;
    }

    /**
     * 计数排序
     *
     * 1、判断待排序数据的范围,比如从0-100,那么就需要101个桶,每个桶代表装一样的数;
     * 2、将一样数据的个数记录在桶中;比如数据为0的有6个,那么第0个桶就记录6;
     * 3、遍历所有的桶,将桶里装的数据个数一直往后叠加;
     * 比如第0个桶记录6,第1个桶记录2,第2个桶记录0,第3个桶记录5,
     * 那么经过遍历叠加之后,第0个桶记录6,第1个桶记录为6+2=8,第2个桶记录为6+2+0=8,第3个桶记录为6+2+0+5=13
     * 4、开辟一个新数组空间,用来保存有序数组;
     * 从后往前遍历原数组,将其值与桶的值对应起来,比如该数为3,那么就取桶[3]里的值,取到为13,
     * 那么就把该数放入有序数组下标值为13的位置,同时桶[3]的值-1;
     * 5、将有序数组的数据全部搬回原数组,那么原数组就是排好序后的数组了
     *
     * @param arr
     */
    public static void countingSort(int[] arr) {
        // 1、确定需要多少个桶
        int maxValue = arr[0];
        int minValue = arr[1];
        for(int value : arr) {
           if(value > maxValue) {
               maxValue = value;
           } else if(value < minValue) {
               minValue = value;
           }
        }
        // 桶的个数
        int bucketNum = maxValue + 1;
        int[] bucketArrs = new int[bucketNum];

        /*
        // 2、记录每个桶里数据的个数
        int bucketIndex = 0;
        for(int i = 0; i < bucketNum; i++) {
            int[] bucketArr = new int[1];
            int count = 0;
            for(int j = 0; j < arr.length; j++) {
                if(arr[j] == i) {
                    count++;
                }
            }
            bucketArrs[bucketIndex++] = count;
        }
        */

        // 2、改写
        for(int i = 0; i < arr.length; i++) {
            bucketArrs[arr[i]]++;
        }

        // 3、叠加桶里数据个数
        int sum = bucketArrs[0];
        for(int i = 1; i < bucketNum; i++) {
            bucketArrs[i] = bucketArrs[i-1] + bucketArrs[i];
        }

        // 4、保存有序数组,为了保证稳定性,需要从后往前遍历原数组
        int[] sortArr = new int[arr.length];
        for(int i = arr.length - 1; i >= 0; i--) {
            int index = bucketArrs[arr[i]] - 1;
            sortArr[index] = arr[i];
            bucketArrs[arr[i]] = index;
        }

        // 5、将有序数组搬回原数组
        for(int i = 0; i < arr.length; i++) {
            arr[i] = sortArr[i];
        }
    }

    /**
     * 基数排序
     * 针对比较大的一组数进行排序,比如手机号码这类。
     * 基数排序是把这组数从最低位开始比较,一直比较到最高位,所以低位排序之后需要保证算法的稳定性,因此可以使用计数排序。
     * 思路:首先获取该组数据的最大值,从最低位开始遍历该数组,即从个位开始,到最高位
     * 获取到该组数据同样的位数数据之后,开始使用计数排序比较即可,每次比较完都写回原数组
     *
     * @param arr
     */
    public static void radixSort(int[] arr) {
       // 获取最大值
       int maxValue = getMaxValue(arr);
       // 从最低位往高位进行比较
        for(int exp = 1; maxValue / exp > 0; exp*=10) {
            countingSort(arr, exp);
        }
    }

    /**
     * 计数排序
     * 遇到卡壳的地方:1、需要多少个桶?2、如何确定计数桶的下标值
     * 解决:1、因为是从0-9比较,所以可以假定是10个桶
     * 2、使用a[i]/exp % 10,这个表达式算出来的值就是桶的下标值,
     * 比如数据a[0]为13,exp此时为1,那么结果就为3,如果exp为10,那么结果就是1
     *
     * @param arr
     * @param exp
     */
    private static void countingSort(int[] arr, int exp) {
        if(arr.length <= 1) {
            return;
        }

        // 将数据个数记录在桶中
        int[] bucketArrs = new int[10];
        for(int i = 0; i < arr.length; i++) {
            bucketArrs[arr[i] / exp % 10]++;
        }

        // 叠加桶中记录的个数
        for(int i = 1; i < bucketArrs.length; i++) {
            bucketArrs[i] += bucketArrs[i-1];
        }

        // 使用一个有序数组保存数据,从后往前获取原数组数据,保持稳定性
        int[] sortArr = new int[arr.length];
        for(int i = arr.length - 1; i >= 0; i--) {
            sortArr[--bucketArrs[arr[i] / exp % 10]] = arr[i];
        }

        // 将有序数组写回原数组
        for(int i = 0; i < arr.length; i++) {
            arr[i] = sortArr[i];
        }
    }

    private static int getMaxValue(int[] arr) {
        int maxValue = arr[0];
        for(int value : arr) {
            if(value > maxValue) {
                maxValue = value;
            }
        }
        return maxValue;
    }

    public static void main(String[] arg) {
        int size = 10;
        int[] arr = null;


        System.out.println("桶排序前:");
        arr = initArray(size);
        display(arr);
        bucketSort(arr, 10);
        System.out.println("桶排序后:");
        display(arr);

        System.out.println("===========================");

        System.out.println("计数排序前:");
        arr = initArray(size);
        display(arr);
        countingSort(arr);
        System.out.println("计数排序后:");
        display(arr);

        System.out.println("===========================");
        System.out.println("基数排序前:");
        arr = initArray(size);
        display(arr);
        countingSort(arr);
        System.out.println("基数排序后:");
        display(arr);
    }

    public static int[] initArray(int size) {
        int[] arr = new int[size];
        for(int i = 0; i < size; i++) {
            arr[i] = (int)(Math.random()*100);
        }
        return arr;
    }

    public static void display(int[] arr) {
        for(int i = 0; i < arr.length; i++) {
            System.out.print(arr[i] + " ");
        }
        System.out.println();
    }
}

github地址:https://github.com/freshbin/dataStructAndAlgo

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值