• 算法与数据结构——排序(十)基数排序


         我们现在来学习几个线性相关的排序算法,首先是基数排序。

         我们平时在打扑克的时候,如果我们要对我们手里的牌进行排序,那么一般我们先会对花色排个序,然后在每个花色里面,再来对数字进行排序。当给一系列数字给我们的时候,我们有排序的时候,也会先按照百位数(假如最高是百位数)排序,如果对百位数相同的再用十位数来排序,再对十位数相同的用个位数来排序,最终整个序列就是有序的了。

         基数排序分为MSD和LSD两种方法,MSD就是上面描述的那样,是从左到右的排序(从百位数到十位数到个位数),LSD则是为从右到左的排序(从个位数到十位数到百位数)。

         下面以LSD为列,假如待排的序列为{345,563,136,873,74,456,132,9,583}.

         那么第一步我们把建立十个桶,把个位数相同的放到一个桶内(第i个桶内装的是个位数为i的数)。

    image

          第二步,从上到下把每个桶的数遍历一遍,成为一个新的数组,新数组是{132,563,873,583,74,345,136,456,9},然后再次建立十个桶,把十位数相同的放到一个桶内(第i个桶内装的是十位数为i的数)。

    image

          第三步,从上到下把每个桶的数遍历一遍,成为一个新的数组,新数组是{9,132,136,345,456,563,873,74,583},然后再次建立十个桶,把百位数相同的放到一个桶内(第i个桶内装的是百位数为i的数)。

    image

          第四步,从上到下把每个桶的数遍历一遍,成为一个新的数组,新数组是{9,74,132,136,345,456,563,583,873},此时序列已经是有序的了。

    从上面的过程我们可以看到,基数排序是先把数分配到桶内,然后重新组成一个数组,然后再次分配这样的一个过程,所以有人也有基数排序叫做“分配—收集”排序。上面演示的LSD排序的过程,MSD的过程恰好与这个相反,它是先把百位数相同的放入到一个桶内,放完后,它并不马上进行收集,而是在每个子桶内,再建立子桶,按照十位数相同的进行分配,一直这样递归,直到按照个位数相同的进行分配了,这时候,从最小的桶里面开始收集数据,最后整个序列就是有序的了。LSD的基数排序适用于位数小的数列,如果位数多的话,使用MSD的效率会比较好。

          我们平时在排序的时候,一般是从高位往低位排,而LSD是从低位往高位排,最后为什么也能得到正确的结果呢?其实这可以用数学里面的归纳法来证明。假如我们先按照个位数来进行排序,个位数排序完成后,个位数是有序的,这个时候再按照十位数来进行排序,这时候分两种情况,一种情况是十位数是递增的,那么这个时候不管个位数的大小,按照十位数的大小的顺序就是整个序列的顺序;如果十位数是相等的,那么就取决于个位数的情况,而个位数也是有序的,那么就能证明整个序列是有序的,这样依此类推,最后的结果一定就是有序的。

         下面是具体的代码:

    public void Sort(List<int> sortList, int nCount)
    {
     
        for (int i = 0; i < nCount; i++)
        {
            int[] tempList = new int[10];//记录余数的数组,temp[i]记录的是余数为i的数的个数
            int[] tempResult = new int[sortList.Count];//记录排序后的结果
     
            //1.分配到桶内
            for (int j = 0; j < sortList.Count; j++)
            {
                //求每个数的个位数,十位数以及百位数.....
                int m =Convert.ToInt32(sortList[j] % Math.Pow(10, i + 1) / Math.Pow(10, i)+0.5)-1;
                tempList[m] += 1;//记录个(十,百,...)位数为m的数的个数
            }
     
     
            //2.收集
            for (int j = 0; j < sortList.Count; j++)
            {
                int m = Convert.ToInt32(sortList[j] % Math.Pow(10, i + 1) / Math.Pow(10, i) + 0.5) - 1;
                int sumCount = 0;
                //求出个(十,百,...)位数比m小的数的个数,这个数也就是即将插入数的下标
                for (int k = 0; k < m; k++)
                {
                    sumCount += tempList[k];
                }
     
                //如果说这个下标已经有值了,那么就往后移
               while(tempResult[sumCount] != 0)
                {
                    sumCount++;
                }
     
                //把当前值插入到数组中下标为sumCount的元素中
               tempResult[sumCount] = sortList[j];
            }
     
     
            //另一种收集的方法
            //for (int m = 1; m < 10; m++)
            //{
            //    tempList[m] += tempList[m - 1];//记录个(十,百,...)位数等于或小于m的数的个数,与上面的记录的有区别
            //}
     
            //for (int n = sortList.Length - 1; n >= 0; n--)
            //{
            //    int m = Convert.ToInt32(sortList[j] % Math.Pow(10, i + 1) / Math.Pow(10, i) + 0.5) - 1;
            //    tempResult[tempList[m] - 1] = sortList[n];
            //    tempList[tmpSplitDigit] -= 1;
            //}
     
            for (int m = 0; m < sortList.Count; m++)
            {
                sortList[m] = tempResult[m];
            }
     
        }
    }

         我们来看一下效率的问题,假如待排的序列有n个数,这些数有d个关键码(取关键码最多的那个数的关键码,关键码就可以理解为最高位是多少位,上面的例子中关键码是3),每个关键码的取值范围为m(m为10,一般每个位上的数字取值范围都是0到9),那么基数排序的时候复杂度是O(d(n+m)),具体来说就是一次分配需要时间为O(n),一次收集时间为O(m),共进行d次分配与收集。

  • 相关阅读:
    论 IntStream 和 for 循环的速度
    单链线性表的基本操作--创建,插入,删除,查看,打印
    Android中的异步处理方式
    Kotlin 集合变换与序列
    Kotlin Lazy延迟初始化
    协程及Kotlin协程
    Java 注解
    Android 事件传递机制进阶
    Java 异常
    Java 多线程及线程间通信(Synchronized和Volatile关键字)
  • 原文地址:https://www.cnblogs.com/xiaoxiangfeizi/p/2783760.html
Copyright © 2020-2023  润新知