排序算法系列——基数排序

基数排序不同于其他的七种排序算法,它是基于一种分配法,而非比较。基数排序属于“分配式排序”(distribution sort),基数排序法又称“桶子法”(bucket sort)或bin sort,顾名思义,它是透过键值的部份资讯,将要排序的元素分配至某些“桶”中,藉以达到排序的作用。它的灵感来自于队列(Queue),它最独特的地方在于利用了数字的有穷性(阿拉伯数字只有0到9的10个)。

基本思想

我们考虑对整数进行基数排序的过程,对于整数,我们可以将其分成个位、十位、百位、。。。基数排序就是先对元素的个位进行排序,然后再对十位进行排序,。。。最终按最高位进行排序完成整个排序。逻辑可以理解为,比如正常情况下我们排序两个整数,我们首先对比的是十位然后是个位,现在我们颠倒过来先排序个位,再排序十位,如果十位相同上一步我们已经按个位排好序,所以还是有序的。其实基数排序也有另一种方式MSD,即从高位开始。

实现要点

首先我们需要一个能够放下所有一位数的桶(bucket),还好阿拉伯数字只有10个,所以我们只需要10个bucket就可以搞定,但是在将所有元素放入bucket时肯定会出现多个元素放入一个bucket的情况,这时候就需要使用链表来解决了(也有使用二维数组的方式,但是空间需要n^2,当排序元素很多时肯定有点吃不消),同时为了方便往bucket中遍历元素以及添加元素,我们让bucket包含两个指针,一个指向bucket中第一个元素(head),另一个指向最后一个元素(tail),而bucket中每个元素都是一个Node,Node中包含一个排序序列中的值(val)以及一个指向下一个元素的指针(next)。

有了桶,下一步就是需要将所有数值从个位开始依次放入桶,然后再按顺序取出放回原数组了,这里有个地方需要注意下,就是如何循环到数组中所有元素的最高位就终止循环,这里有两个解决方法:

(1)首先遍历一遍数组,找到最大值,确定最高位

(2)一直循环直到所有元素的指定位数都是0为止

第一种方法需要O(n)次的比较,而第二次需要计算每个值在指定位数的值,同时还需要将其放入桶中。我的实现需要第一种方式。

到这里基本就可以实现出一个基数排序了,下面就看看如何实现。

Java实现

package com.vicky.sort;

import java.util.Random;

/**
 * 基数排序
 *
 * @author Vicky
 *
 */
public class RadixSort {
    private static final int RADIX = 10;

    public static void sort(Integer[] data) {
        long start = System.nanoTime();
        if (null == data) {
            throw new NullPointerException("data");
        }
        if (data.length == 1) {
            return;
        }
        // 遍历一遍,找到最大值,确定最高位数
        int max = Integer.MIN_VALUE;
        int i = 0;
        for (; i < data.length; i++) {
            if (data[i] > max) {
                max = data[i];
            }
        }
        // 计算最高位数
        int maxDigit = String.valueOf(max).length();
        Bucket[] temp = new Bucket[RADIX];// 用于保存各个位数的bucket
        for (int num = 0; num < maxDigit; num++) {
            // 将每个元素指定位数的值放入对应的bucket中
            for (i = 0; i < data.length; i++) {
                int val = data[i] / pow(RADIX, num);
                Node node = new Node(data[i], null);
                Bucket tmp = temp[val % RADIX];
                if (null == tmp) {
                    temp[val % RADIX] = new Bucket(node, node);
                } else {
                    tmp.getTail().setNext(node);
                    tmp.setTail(node);
                }
            }
            // 将temp中保存的元素按顺序更新到原数组中
            int index = 0;
            for (i = 0; i < temp.length; i++) {
                if (null == temp[i]) {
                    continue;
                }
                Node node = temp[i].getHead();
                data[index++] = node.getValue();
                while (null != node.getNext()) {
                    data[index++] = node.getNext().getValue();
                    node = node.getNext();
                }
                // 顺便重置temp
                temp[i] = null;
            }
        }

        System.out.println("use time:" + (System.nanoTime() - start) / 1000000);
    }

    /**
     * 计算指定基数的指定幂的值
     *
     * @param digit
     *            基数,如10
     * @param num
     *            幂次值,从0开始,10^0=1
     * @return
     */
    private static int pow(int digit, int num) {
        int val = 1;
        for (int i = 0; i < num; i++) {
            val *= digit;
        }
        return val;
    }

    /**
     * <p>
     * 桶,用于存放数组
     * </p>
     *
     * @author Vicky
     * @date 2015-8-21
     */
    private static class Bucket {
        private Node head;
        private Node tail;

        public Bucket(Node head, Node tail) {
            super();
            this.head = head;
            this.tail = tail;
        }

        public Node getHead() {
            return head;
        }

        public Node getTail() {
            return tail;
        }

        public void setTail(Node tail) {
            this.tail = tail;
        }
    }

    /**
     * <p>
     * 节点,用于解决桶中存放多个元素问题
     * </p>
     *
     * @author Vicky
     * @date 2015-8-21
     */
    private static class Node {
        private int value;
        private Node next;

        public Node(int value, Node next) {
            super();
            this.value = value;
            this.next = next;
        }

        public int getValue() {
            return value;
        }

        public Node getNext() {
            return next;
        }

        public void setNext(Node next) {
            this.next = next;
        }
    }

    public static void main(String[] args) {
        Random ran = new Random();
        Integer[] data = new Integer[10000000];
        Integer[] data2 = new Integer[data.length];
        Integer[] data3 = new Integer[data.length];
        for (int i = 0; i < data.length; i++) {
            data[i] = ran.nextInt(100000);
            data2[i] = data[i];
            data3[i] = data[i];
        }
        MergeSort.sort(data);
        QuickSort.sort(data2);
        RadixSort.sort(data3);
        // SortUtils.printArray(data);
        // SortUtils.printArray(data2);
        // SortUtils.printArray(data3);
    }
}

效率分析

(1)时间复杂度

O(kn)

基数排序的时间复杂度是 O(k·n),其中n是排序元素个数,k是数字位数。注意这不是说这个时间复杂度一定优于O(n·log(n)),因为k的大小一般会受到 n 的影响。 以排序n个不同整数来举例,假定这些整数以B为底,这样每位数都有B个不同的数字,k就一定不小于logB(n)。由于有B个不同的数字,所以就需要B个不同的桶,在每一轮比较的时候都需要平均n·log2(B) 次比较来把整数放到合适的桶中去,所以就有:

k 大于或等于 logB(n)

每一轮(平均)需要 n·log2(B) 次比较

所以,基数排序的平均时间T就是:

T ≥logB(n)·n·log2(B) = log2(n)·logB(2)·n·log2(B)= log2(n)·n·logB(2)·log2(B) = n·log2(n)

所以和比较排序相似,基数排序需要的比较次数:T ≥ n·log2(n)。故其时间复杂度为Ω(n·log2(n)) = Ω(n·log n) 。

(2)空间复杂度

O(radix+n)

radix个bucket,以及n个Node。

(3)稳定性

稳定

假设一组元素:12(a),23,12(b),34,15,首先我们按个位分桶得到bucket(1)里面是12(a),12(b),取出后还是12(a),12(b)所以顺序并没有变化。

基数排序虽然看着感觉效率很高,其实经过测试效率并不如快速排序和归并排序,但是比其他的O(n^2)的排序算法效率肯定是高的。因为基数排序需要操作链接,对于链表的操作肯定不如数组效率高。不过基数排序可以适用于非数值排序,比如字符串。对于一组字符串进行排序,其他的排序算法就无法适用了,但是可以运用基数排序,按字符串个每位字符进行划分入桶,再进行排序即可,不过得先确定有多少个不同字符,要是包含全部字符,那就没法玩了。下面引用一篇文章介绍的基数排序的适用范围吧。

基数排序从低位到高位进行,使得最后一次计数排序完成后,数组有序。其原理在于对于待排序的数据,整体权重未知的情况下,先按权重小的因子排序,然后按权重大的因子排序。例如比较时间,先按日排序,再按月排序,最后按年排序,仅需排序三次。但是如果先排序高位就没这么简单了。基数排序源于老式穿孔机,排序器每次只能看到一个列,很多教科书上的基数排序都是对数值排序,数值的大小是已知的,与老式穿孔机不同。将数值按位拆分再排序,是无聊并自找麻烦的事。算法的目的是找到最佳解决问题的方案,而不是把简单的事搞的更复杂。基数排序更适合用于对时间、字符串等这些整体权值未知的数据进行排序。这时候基数排序的思想才能体现出来,例如字符串,如果从高位(第一位)往后排就很麻烦。而反过来,先对影响力较小,排序排重因子较小的低位(最后一位)进行排序就非常简单了。这时候基数排序的思想就能体现出来。又或者所有的数值都是以字符串形式存储,就象穿孔机一样,每次只能对一列进行排序。这时候基数排序也适用,例如:对{“193”;”229”;”233”;”215”}进行排序。

参考文章

java基数排序

Java排序算法总结(八):基数排序

版权声明:本文为博主原创文章,未经博主允许不得转载。

时间: 2024-08-03 04:37:49

排序算法系列——基数排序的相关文章

(转载)排序算法系列

排序算法系列 目录 概述 概念 排序是计算机内经常进行的一种操作,其目的是将一组"无序"的记录序列调整为"有序"的记录序列. 排序分为内部排序和外部排序. 若整个排序过程不需要访问外存便能完成,则称此类排序问题为内部排序. 反之,若参加排序的记录数量很大,整个序列的排序过程不可能在内存中完成,则称此类排序问题为外部排序. 排序分类 如果按照策略来分类,大致可分为:交换排序.插入排序.选择排序.归并排序和基数排序.如 图-排序策略分类图 所示. 图-排序策略分类图 算

排序算法系列——堆排序

记录学习点滴,菜鸟成长记 堆排序引入了另一种算法设计技巧:使用一种我们称之为“堆”的数据结构来进行数据管理. 堆排序算是真正意义上的利用数据结构来求解数组排序的方法. “插入排序”和“归并排序”可以看做是一种“计算机体力活”,体现的思想更多的是去模拟最简单的人类思维,比如插入排序过程中的比较,归并中子问题合并时的比较. “堆排序”可以看做是“计算机脑力活”,他利用了一种结构化的语言来表达,这种结构化带来一些性质,比如左右孩子.比[堆大小的一半向下取整]大的下标都是叶节点不需要维护其最大堆性质等.

排序算法系列——归并排序

记录学习点滴,菜鸟成长记 归并排序的英文叫做Merge-Sort,要想明白归并排序算法,还要从“递归”的概念谈起. 1.递归 一般来讲,人在做决策行事的时候是往往是从已知出发,比如,我又要举个不恰当的例子了→_→: 看到漂亮姑娘→喜欢人家→追→女朋友→老婆 但是人家施瓦辛格不是这么想的,人家从小就立志当总统: 要当总统←先当州长←竞选州长要有钱←那得找个有钱妹子←妹子都喜欢明星←身材好能当明星←健身 递归,就像一个人对自己的发展有清晰的规划和坚定的信心一样,他知道每一步会有怎么样的结果,他需要仅

排序算法系列——快速排序

记录学习点滴 快速排序算法是一种很有趣的算法,短小精悍,性能强劲,对于大部分情况都可以胜任,但对极端环境难以应付. 快速排序我理解为:这是一个“以自我为中心的”+“分治法”思想的算法. 分治法不必多说,化繁为简,那就是逐个击破. 那什么是“以自我为中心”?顾名思义,就是每次都一个“我”,每个人都要围绕“我”行事,比“我”小的都去左边站着,比“我”他大的都去右边站着,而且“我”不去关心每一边都有谁,反正你没“我”大或者小就行.一旦“我”落位了妥帖了,“我”就不动了.然后再在左右两边分别产生新“我”

经典排序算法之基数排序(C语言版)

排序算法之基数排序的C语言实现. #include "math.h" #include "stdio.h" /* * 基数排序 2016-04-18 23:43:49 * 基数排序的思想:1.求出数组中最大的元素. *   2.求出最大元素是几位数.设为i位. *   3.对所有的数进行i轮排序.首先排个位,然后在十位,然后百位...... *   4.每一轮的排位都需要分桶,桶是有顺序的,然后在把桶里的数按顺序放入原来的数组中. *   5.直到i轮排序结束后,数

排序算法系列——插入排序

记录学习点滴,菜鸟成长记 接触算法是研究生期间做项目时,需要编写一些诸如GA.QGA的时候,第一次使用“排序”还是用的Java自带的Comparator接口.后来买了<算法导论>来看,发现果然所有知识都是有专业壁垒的,简单的一个问题尽然蕴藏着如此多的思想,发现此简直欣喜无比,遂决定要好好研究研究.只有深入后才发现,原来算法的不仅仅是按照逻辑顺序写个程序那么简单,好的算法要考虑到方方面面,最简单的时间复杂度就够我学习很长时间了. 将自己学习排序算法的一些理解和感悟记录于此,方便自己温故而知新.

排序算法系列——八大排序算法对比分析

本系列最后一篇,综合分析下前面介绍的八种排序算法的效率,以及各自的适用情况. 下面先看看八种排序算法的时间复杂度表格: 图中八种排序被分成了两组,一组时间复杂度为O(n^2),另一组相对高效些. 下面先对第一组O(n^2)的四种排序算法进行对比,分别取数组长度为100,1000,10000,100000四个数量级,各个元素在0-10000000之间随机获取.下面看下结果的分析. 排序算法 长度=100 长度=1000 长度=10000 长度=100000 直接插入排序 535 2,198 135

排序算法系列:冒泡排序与双向冒泡排序

概述 排序算法应该算是一个比较热门的话题,在各个技术博客平台上也都有一些博文进行了一定程度的讲解.但还是希望能从自我完善的角度出发,可以更详细.全面.形象地表达这些算法的精髓.本文就先从最简单的冒泡排序开始说起,别说你已经彻底了解了冒泡排序算法(虽然一开始我也是这样以为的). 版权说明 本文链接:http://blog.csdn.net/lemon_tree12138/article/details/50474230 – Coding-Naga - 转载请注明出处 目录 概述 版权说明 目录 冒

经典排序算法系列7----堆与堆排序

堆排序与快速排序,归并排序一样都是时间复杂度为O(N*logN)的几种常见排序方法.学习堆排序前,先讲解下什么是数据结构中的二叉堆. 二叉堆的定义 二叉堆是完全二叉树或者是近似完全二叉树. 二叉堆满足二个特性: 1.父结点的键值总是大于或等于(小于或等于)任何一个子节点的键值. 2.每个结点的左子树和右子树都是一个二叉堆(都是最大堆或最小堆). 当父结点的键值总是大于或等于任何一个子节点的键值时为最大堆.当父结点的键值总是小于或等于任何一个子节点的键值时为最小堆.下图展示一个最小堆: 由于其它几