排序算法之归分配排序   

罗朝辉 (http://www.cnblogs.com/kesalin/)
本文遵循“署名-非商业用途-保持一致”创作公用协议

排序是数据处理中经常使用的一种重要运算,在计算机及其应用系统中,花费在排序上的时间在系统运行时间中占有很大比重,其重要性勿需多言。下文将介绍常用的如下排序方法,对它们进行简单的分析和比较,并提供 C 语言实现。

所谓排序,就是要将一堆记录,使之按关键字递增(或递减)次序排列起来。根据排序所采用的策略,可以分为如下五种:

1、插入排序(直接插入排序、希尔排序);

2、交换排序(冒泡排序、快速排序);

3、选择排序(直接选择排序、堆排序);

4、归并排序;

5、桶排序(桶排序,基数排序);

-----------------------------------------------------------------------------------------------

前面讲了插入排序,交换排序,选择排序,归并排序,下面接着来讲桶排序,基数排序。

桶排序和基数排序均属于分配排序。分配排序的基本思想:排序过程无须比较关键字,而是通过用额外的空间来"分配"和"收集"来实现排序,它们的时间复杂度可达到线性阶:O(n)。简言之就是:用空间换时间,所以性能与基于比较的排序才有数量级的提高!

桶排序(Bucket Sort),也称箱排序

基本思想:设置若干个箱子,依次扫描待排序的记录 array[0],array[1],…,array[n - 1],把关键字等于 k 的记录全都装入到第 k 个箱子里(分配),然后按序号依次将各非空的箱子里的记录收集起来,从而完成排序。

桶排序所需要的额外空间取决于关键字的个数,若 array[0..n - 1] 中关键字的取值范围是 0 到 m - 1 的整数,则必须设置 m 个箱子。因此箱排序要求关键字的类型是有限类型,否则可能要无限个箱子。一般情况下每个箱子中存放多少个关键字相同的记录是无法预料的,故箱子的类型应设计成链表为宜。

在实现上,桶排序一般采用另外的变种。即:把 [0, m) 划分为 m 个大小相同的子区间,每一子区间是一个桶。然后将 n 个记录分配到各个桶中。因为关键字序列是均匀分布在[0,m)上的,所以一般不会有很多个记录落入同一个桶中。由于同一桶中的记录其关键字不尽相同,所以必须采用关键字比较的排序方法(通常用插入排序)对各个桶进行排序,然后依次将各非空桶中的记录收集起来即可。

C 代码实现:

struct bucket_node {
    int key;
    bucket_node* next;
};

// 取得数组中最大数的位数
int get_max_digital_count(int* array, int length)
{
    assert(array && length > 0);

    int i = 0;
    int max = array[0];
    int maxDigitalCount = 1;

    for (i = 1; i < length; i++) {
        if (max < array[i]) {
            max = array[i];
        }
    }

    while ((max / 10) > 0) {
        max %= 10;
        ++maxDigitalCount;
    }

    return maxDigitalCount;
}

// 取得数 num 中从低到高第 n 位上的数字
int get_ditital_at(int num, int n)
{
    while (--n > 0) {
        num /= 10;
    }

    return (num % 10);
}

// 箱/桶排序
//
void bucket_sort(int* array, int length)
{
    assert(array && length >= 0);

    if (length <= 1) {
        return;
    }

    int i, index;
    bucket_node* temp = NULL;
    bucket_node bucket[10] = {0, };    // 根据数字个数 0 ~ 9 建立 10 个桶

    int count = get_max_digital_count(array, length);

    // 建立数据节点
    bucket_node* data = (bucket_node*)malloc(length * sizeof(bucket_node));
    if (!data) {
        printf("Error: out of memory!/n");
        return;
    }

    for (i = 0; i < length; i++) {
        data[i].key = array[i];
        data[i].next = NULL;
    }

    // 分配
    for (i = 0; i < length; i++) {
        index = get_ditital_at(data[i].key, count);
        if (bucket[index].next == NULL) {
            bucket[index].next = &data[i];
        }
        else {
            temp = &bucket[index];
            while (temp->next != NULL && temp->next->key < data[i].key) {
                temp = temp->next;
            }

            data[i].next = temp->next;
            temp->next = &data[i];
        }
    }

    // 收集
    index = 0;
    for (i = 0; i < 10; i++) {
        temp = bucket[i].next;
        while (temp != NULL) {
            array[index++] = temp->key;
            temp = temp->next;
        }
    }


    free(data);
}

时间复杂度分析:
桶排序的平均时间复杂度是线性的,即 O(n)。但最坏情况仍有可能是 O(n ^ 2)。

空间复杂度分析:
桶排序只适用于关键字取值范围较小的情况,否则所需箱子的数目 m 太多导致浪费存储空间和计算时间。

基数排序(Radix Sort)
基本思想:基数排序是对桶排序的改进和推广。如果说桶排序是一维的基于桶的排序,那么基数排序就是多维的基于桶的排序。我这么说,可能还不是太清楚。比方说:用桶排序对 [0, 30] 之间的数进行排序,那么需要 31 个桶,分配一次,收集一次,完成排序;那么基数排序则只需要 0 - 9 总共 10 个桶(即关键字为数字 0 - 9),依次进行个位和十位的分配和收集从而完成排序。

C 代码实现:

// 基数排序
//
void radix_sort(int* array, int length)
{
    assert(array && length >= 0);

    if (length <= 1) {
        return;
    }

    const int buffer_size = length * sizeof(int);

    int i, k, count, index;
    int bucket[10] = {0, };    // 根据数字个数 0 ~ 9 建立 10 个桶

    int* temp = (int*)malloc(buffer_size);
    if (!temp) {
        printf("Error: out of memory!/n");
        return;
    }

    count = get_max_digital_count(array, length);

    for (k = 1; k <= count; ++k) {
        memset(bucket, 0, 10 * sizeof(int));

        // 统计各桶中元素的个数
        for (i = 0; i < length; ++i) {
            index = get_ditital_at(array[i], k);
            ++bucket[index];
        }

        // 为每个记录创建索引下标
        for (i = 1; i < 10; ++i) {
            bucket[i] += bucket[i - 1];
        }

        // 按索引下标顺序排列
        for (i = length - 1; i >= 0; --i) {
            index = get_ditital_at(array[i], k);
            assert(bucket[index] - 1 >= 0);
            temp[--bucket[index]] = array[i];
        }

        // 一趟桶排序完毕,拷贝结果
        memcpy(array, temp, buffer_size);

#ifdef DEBUG_SORT
        debug_print(" 第 %d 趟排序:", k);
        for (i = 0; i < length; ++i) {
            debug_print("%d ", array[i]);
        }

        debug_print("/n");
#endif
    }

    free(temp);
}

时间复杂度分析:
基数排序的时间负责度为 O(n)。

空间复杂度:
基数排序所需的辅助存储空间为 O(n + r * d),其中 r 为记录中关键字分量的最大个数,d 为关键字的个数。比如说:待排序为 0 - 999,那么分量的最大个数为 3,关键字的个数为 10(0 - 9)。

补充:
基数排序是稳定的。

若排序文件不是以数组 array 形式给出,而是以单链表形式给出(此时称为链式的基数排序),则可通过修改出队和入队函数使表示箱子的链队列无须分配结点空间,而使用原链表的结点空间。人队出队操作亦无需移动记录而仅需修改指针。虽然这样一来节省了一定的时间和空间,但算法要复杂得多,且时空复杂度就其数量级而言并未得到改观。

=======================================================================================
测试:
在前文《排序算法之插入排序》测试代码的基础上添加两行代码即可:
 {"桶/箱排序",    bucket_sort},
 {"基数排序",     radix_sort},

测试结果:

=== 桶/箱排序 ===
 original: 65 32 49 10 8 72 27 42 18 58 91
   sorted: 8 10 18 27 32 42 49 58 65 72 91

original: 10 9 8 7 6 5 4 3 2 1 0
   sorted: 0 1 2 3 4 5 6 7 8 9 10

=== 基数排序 ===
 original: 65 32 49 10 8 72 27 42 18 58 91
 第 1 趟排序:10 91 32 72 42 65 27 8 18 58 49
 第 2 趟排序:8 10 18 27 32 42 49 58 65 72 91
   sorted: 8 10 18 27 32 42 49 58 65 72 91

original: 10 9 8 7 6 5 4 3 2 1 0
 第 1 趟排序:10 0 1 2 3 4 5 6 7 8 9
 第 2 趟排序:0 1 2 3 4 5 6 7 8 9 10
   sorted: 0 1 2 3 4 5 6 7 8 9 10

转载于:https://www.cnblogs.com/kesalin/archive/2011/03/18/bucket_sort.html

【排序】排序算法之分配排序相关推荐

  1. 【数据结构、算法】八大排序算法概述(算法复杂度、稳定性)

    前言   排序是计算机程序设计中一个非常重要的操作,它将一个数据元素(或记录)的任意序列重新排列成一个按关键字有序的序列.在有序的序列中查找元素的效率很高,(例如,折半查找法的平均查找长度为log2( ...

  2. 输入十个数进行排序_十大经典排序算法(Javascript描述)

    "本文13906字,阅读大约需要35分钟." 这世界上总存在着那么一些看似相似但有完全不同的东西,比如雷锋和雷峰塔,小平和小平头,玛丽和马里奥,Java和Javascript... ...

  3. sqlserver 根据数组排序_看动画学算法之:排序-count排序

    简介 今天我们介绍一种不需要作比较就能排序的算法:count排序. count排序是一种空间换时间的算法,我们借助一个外部的count数组来统计各个元素出现的次数,从而最终完成排序. count排序的 ...

  4. 算法与数据结构 - 排序详解

    目录 前言 引言 业务场景 代码模拟 1. 冒泡排序 1.1 什么是冒泡排序 1.2 图解冒泡 1.3 代码编写 1.4 总结分析 时间复杂度 是否为原地排序 2. 选择排序 2.1 什么是选择排序 ...

  5. python实现排序_python实现以及所有排序大总结【转】

    原址 一.概述 排序有内部排序和外部排序,内部排序是数据记录在内存中进行排序,而外部排序是因排序的数据很大,一次不能容纳全部的排序记录,在排序过程中需要访问外存. 我们这里说说八大排序就是内部排序. ...

  6. 数据结构——排序:插入排序、选择排序、交换排序、归并排序、基数排序

    排序 内部排序:数据量不大,在内存中可以完成排序. 外部排序:借助外存.把数据文件分成若干块,涉及内外存数据的转换.存储器的管理等. 稳定排序:能保证排序前两个相等的数其在序列的前后位置顺序和排序后它 ...

  7. 数据结构与算法:选择排序

    数据结构与算法:选择排序 雪柯 大工生物信息 提笔为写给奋进之人 已关注 8 人赞同了该文章 引用自算法图解,作者[美] Aditya Bhargava 译袁国忠 特别备注:本书非原创,但部分内容自己 ...

  8. 【每日算法】桶排序算法

    1)算法简介 桶排序 (Bucket sort)或所谓的箱排序,是一个排序算法,工作的原理是将数组分到有限数量的桶子里.每个桶子再个别排序(有可能再使用别的排序算法或是以递归方式继续使用桶排序进行排序 ...

  9. java排序算法总结_排序算法总结及Java实现

    1. 整体介绍 分类 排序大的分类可以分为两种,内排序和外排序.在排序过程中,全部记录存放在内存,则称为内排序,如果排序过程中需要使用外存,则称为外排序.主要需要理解的都是内排序算法: 内排序可以分为 ...

  10. 排序算法_桶排序(箱排序)

    一.算法描述 假设有一组长度为N的待排关键字序列K[1....n]. 首先将这个序列划分成M个的子区间(桶). 然后基于某种映射函数 ,将待排序列的关键字k映射到第i个桶中(即桶数组B的下标 i) , ...

最新文章

  1. python字符串出栈方法_python字符串常用方法
  2. 在创建的Silverlight Control中使用图片
  3. CentOS 7安装Zabbix 3.4
  4. quick-cocos2d-x api构建文档
  5. python 中的input
  6. 读《大学之路》有感①
  7. Zuul 2 –样本过滤器
  8. 完善区块链产业链 加速经济数字化转型
  9. GARFIELD@05-01-2005
  10. python itertools模块_Python标准模块--itertools
  11. 【干货】PyTorch Tricks 集锦
  12. Linux学习笔记(一):Win10上用VMware虚拟机安装Linux-CentOS
  13. Unity3DShader基础(CG)
  14. ie8兼容性视图灰色修复_兼容性视图_win7兼容性设置选项为灰色
  15. SVCHOST启动服务实战
  16. SaaS、PaaS、IaaS是什么意思
  17. python环境安装 (win7)
  18. peoplesoft笔记
  19. 网易云信技术创新,助力网易云音乐社交玩法升级
  20. dr.oracle素颜霜好用吗,Dr.Glamour素颜霜BB霜哪个好用?这几个误千万不要踩

热门文章

  1. Please select Android SDK的解决
  2. 实现模拟内存动态分区分配算法_你还在为了JVM而烦恼么?(内存结构和垃圾回收算法)...
  3. php preg_split 正则截取字符串
  4. 服务器上怎么查询ubuntu版本信息失败,Ubuntu安装包信息如何查看
  5. php中怎么创建对象,php创建对象有哪几种方法
  6. 关于报表在移动端展现你需要知道哪些?
  7. java算法-递归算法思想
  8. gcov 使用注意事项
  9. Python引力波火了 你该了解的开源框架
  10. 微信公众号小程序开发