排序算法整理小结(基数排序,计数排序,桶排序)

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/qq_18870127/article/details/84367527

    这次整理的为三个原理上相近的排序算法,便于比较对比分析,通过他们之间的不同理解三个算法的特点,从而能够设计出符合三者的算法。首先呢,这三个算法都不是基于比较的算法,这和前面的那些算法不同,其核心内容不再通过比较然后交换两者的位置了,而这次要说的是在排序的时候将其顺序和“桶”的顺序相关联起来,通过不同的排序规则,进行排序,然后按照顺序进行输出,这些就是此三类算法的大致流程。

计数排序

     解释这一通是为了有个大体印象,不至于在阅读代码的时候产生浑然不知。首先我们看一个较为简单的,叫做计数排序,这个排序可以很好的应用到整数排序,其主要的核心思想为寻找出数组中最大和最小元素,然后开辟max-min大小的数组,然后对原数组进行遍历,原数组某位置的元素值为多少,则在新开辟的辅助数组中的对应位置(辅助数组下标为原数组元素值)加1(可能有重复的数字),然后将其按照某一顺序重新写回原来的数组。来看下代码是怎么完成这个过程的:

public class CountSort {
    public static void countSort(int[] array, int size){
        
        int max_vaue = array[0];//序列中的最大值
        int min_value = array[0];//序列中的最小值

        //找到序列中的最大值和最小值
        for(int i = 0; i < size; i++){
            if(array[i] >= max_vaue) {
                max_vaue = array[i];
            }
            if(array[i] <= min_value){
                min_value = array[i];
            }
        }

        //计算辅助数组的长度并开辟相应的空间
        int range = max_vaue - min_value + 1;
        int[] helpCount = new int [range];

        //helpCount[array[i]-min_value]++; 将对应的数字放置
        //即求得array[i]相对于min_value值的距离是多少
        //到对应的位置
        for(int i = 0; i < size; i++){
            helpCount[array[i] - min_value] ++;
        }

        int index = 0;

        //遍历辅助空间
        for(int i = 0; i < range; i++){
            while (helpCount[i] -- > 0){//下标是几则说明出现了几次
                //将下标处的数字拉回原来数组
                array[index++] = i + min_value;
            }
        }
    }
}

其实一点本质的东西就是将原数组的元素的值和help数组的下标值联系起来,因为我们在进行比较排序的时候比较的也是元素的值,也即说明顺序包含在原数组的值当中,所以我们改变一下策略,使得原数组元素值对因为新数组的下标索引值。

好了,如果理解好了上面这个计数排序算法之后,再来一个桶排序吧,所谓的循序渐进嘛:

桶排序

    说道桶排序,其核心的思想有点和散列表有些向,有原来的一排桶中现在变为了多排桶,桶的第一排来接受经过计算后被分配到这一列桶,后续插入的元素则在这一咧往下排列,这么说抽象哈,可以参考下面的图和博客。

https://www.cnblogs.com/ECJTUACM-873284962/p/6935506.html

    是不是很像定址法散列表的样子?那该怎么设计呢,参考下面的代码:

public class BucketSort {

    public  void bucketSort(int[] array, int bucketSize){
        //用于存放排序后的结果
        int[] result = new int[array.length];

        //开辟bucket的大小为bucketSize
        Node[] bucket = new Node[bucketSize];

        //初始化每个节点
        for(int i = 0 ; i < bucket.length; i++){
            bucket[i] = new Node();
        }

        //核心部分,将各个元素按照刚才所示的图的形式进行分组放入
        //桶中
        for(int i = 0; i < array.length; i++){
            int bucketIndex = hash(array[i]);
            Node node = new Node(array[i]);
            Node p = bucket[bucketIndex];

            if(p.next == null){//无节点的情况
                p.next = node;
            }else{
                //插入到连表中的情况,链表的插入操作还是比较快速的
                while (p.next != null && p.next.val <= node.val){
                    p = p.next;
                }
                //更新链表节点
                node.next = p.next;
                p.next = node;
            }
        }

        int j = 0;
        
        //将桶中的结果放入到结果数组中
        for(int i = 0; i < bucket.length; i++){
            for (Node p = bucket[i].next; p != null; p = p.next){
                result[j++] = p.val;
            }
        }

        //将结果拷贝回原数组
        for(int i = 0; i < array.length; i++){
            array[i] = result[i];
        }
    }

    //此哈希非彼哈希,这个主要是为了能够将元素“散列”到各个桶之中
    public int hash(int val){
        return  val / 10;
    }

    //定义节点Node类型
     class Node{
        public Node(){
            this(0);
        }
        public Node(int val){
            this.val = val;
        }
        private int val;
        private Node next;
    }
}

基数排序

     基数排序是一种来自于老式的穿卡机上的算法,一个卡片有80列,每一列可以在12个位置上穿孔,排序器可以被机器机械的“程序化”,其利用这样的方式方便了老式机器对卡片进行检查。对于我们通用的10进制数字来说,每一列中只用到10个位置,一个d位数字占用d个列,因为卡片器一次只能查看一个数列。直觉上可以利用高位数字进行判别,但是其是不然,因为如果这样排序的话,10个位置的9个必须放置到一边,这个过程产生了较大的空间浪费,如果以低位进行,则会有效的解决这个问题。此时的基数排序则相当于对于数列先按照个位数进行排列,然后按照十位进行,然后不断的进行到最高位(若没有的话,用0代替)。对于此算法来说重要的问题就是排序算法一定要稳定。说了这么多不如一个图,也许你看下图就明白了:

算法核心较为简单,但是如何设计这个算法呢,有这么几个地方需要注意:首先要注意排序的顺序,是由低位到高位,其次还要注意算法的另一个问题,如何保存每次排序的结果,在一个就是,如何比较长度不同的数字,其次就是如何正确的将其放入到正确的桶的位置,可以参考一下代码:

public class RadixSort {

    /**
     * 获取array数组中的最大值
     * @param array   待排序数组
     * @return 返回数组中的最大值
     */
    private static int getMaxValue(int[] array){
        int max_value;

        max_value = array[0];

        for(int i = 1; i < array.length; i++){
            if(array[i] > max_value){
                max_value = array[i];
            }
        }
        return max_value;
    }

    public static void radixSortCore(int[] array, int exp){

        //开辟辅助数据,用于存放每次得到的排序结果
        int[] output = new int [array.length];
        //bucket用于存放每一位数字对应的在0-9的位置,其值
        //为每个数字的数量
        int[] bucket = new int[10];
        //按照每次指定了exp(此为对于某一位进行取余然后寻找位置)
        for(int i = 0; i < array.length; i++){
            bucket[(array[i] / exp) % 10]++;
        }

        //此过程可以更改bucket,使得更改后的bucket数组中的值
        // 为array数组中数据,在经过排序之后在output数组中的下标
        //需要注意的是,需要结合下面两个for循环过程
        for(int i = 1; i < 10; i++){
            bucket[i] += bucket[i - 1];
        }

        //例如array = 12,34,63,79,86,45;则bucket中的索引值从0-9所对
        //对应的内容为;0,0,1,1,1,1,1,0,0,1,经过上面的for循环后
        //可以得到0,0,1,2,3,4,5,5,5,6 此时,
        // [bucket[(array[array[i] / exp]) % 10] - 1,可以分析为:若按照个位计算,
        //(array[i] / exp) % 10 为bucket数组的索引,若i = 5 时,可以得到
        //(array[i] / exp) % 10 为5,则bucket [(array[i] / exp) % 10]为4
        //则output的索引值(array[i] / exp) % 10] - 1为3,即output的第四个位置
        //而排序之后的结果为:12,63,34,45,86,79。可以发现符合结果
        for (int i = array.length - 1; i >= 0; i--){
            output[bucket [(array[i] / exp) % 10] - 1 ] = array[i];
            //当有重复数字的时候有效,即当此位置的数字个数-1
            bucket[(array[i] / exp)% 10 ] --;
        }

        /**
         * 移动到原来的数组
         */
        for (int i = 0; i < array.length; i++){
            array[i] = output[i];
        }
        output = null;
        bucket = null;
    }

    public static void radix_sort(int[] array){
        int max = getMaxValue(array);

        for(int exp = 1; max / exp > 0; exp *= 10){
            radixSortCore(array,exp);
        }
    }
}

猜你喜欢

转载自blog.csdn.net/qq_18870127/article/details/84367527