计数排序与位图排序

  计数排序(Counting sort)是一种稳定的线性时间排序算法。计数排序使用一个额外的数组C,其中第i个元素是待排序数组A中值等于i的元素的个数。然后根据数组C来将A中的元素排到正确的位置。计数排序不是比较排序,排序的速度快于任何比较排序算法。由于用来计数的数组C的长度取决于待排序数组中数据的范围(等于待排序数组的最大值与最小值的差加上1),这使得计数排序对于数据范围很大的数组,需要大量时间和内存。计数排序更适合于小范围集合的排序。比如100万学生参加高考,我们想对这100万学生的数学成绩(假设分数为0到100)做个排序。计数排序的步骤如下:

  1. 确定待排序数组中数值的范围,以此确定C数组的大小;
  2. 计算数组中每个值为A[i]的元素出现的次数,存入数组C[A[i]]中;
  3. 用C数组重新填充原数组。

  计数排序的实现代码如下,需要注意的是代码并未对数组内的元素进行比较,而是:

 1 void CountingSort(int *nArray, int nSize)
 2 {
 3     int *nCounting = new int[nSize];        //nCounting 是一个伴随数组,记录原数组中每个数出现的次数
 4     memset(nCounting, 0x0, sizeof(int)*nSize);
 5
 6     int i;
 7     for(i=0; i!=nSize; i++)
 8         nCounting[nArray[i]]++;     //计算每个数出现过的次数,例如 nCounting[5] = 3;表示原数组存在3个5
 9
10     int z=0;
11     for(i=0; i<=nSize; i++)
12     {
13         while(nCounting[i]-- > 0)    //nCounting[i]>0 表示原数组中存在数字i
14             nArray[z++] = i;
15     }
16
17     delete[] nCounting;
18
19     return;
20 }

  代码很好理解,但是仍然需要注意几点:

  1. 计数排序是典型的以空间换时间,数组内元素的范围决定了伴随数组C的大小,从而决定程序运行的时间和内存大小。
  2. 计算C[i]时,使用了A[i]作为下标,那么必须保证A数组中所有元素非负,如果存在负值,那么可以对数组元素加上最小负值的绝对值,使得数组元素全部为正数。


  当计数排序中原数组A中数值范围较大时,伴随数组C也越大,内存的消耗越大,要进一步限制内存的大小,可以使用位图法(Bit-map)进行排序。假设要对数值范围为0-7内的5个数(4,7,2,5,3)进行排序,那么我们就可以采用Bit-map的方法来达到排序的目的。要表示8个数,我们就只需要8个Bit(1Bytes),首先我们开辟1Byte的空间,将这些空间的所有Bit位都置为0,如下图

       

然后遍历这5个元素,首先第一个元素是4,那么就把4对应的位置为1,当然了这里的操作涉及到Big-ending和Little-ending的情况,这里默认为Big-ending),因为是从零开始的,所以要把第五位置为一(如下图):

接着再处理第二个元素7,将第八位置为1,,接着再处理第三个元素,一直到最后处理完所有的元素,将相应的位置为1,这时候的内存的Bit位的状态如下:

最后我们现在遍历一遍Bit区域,将该位是一的位的编号输出(2,3,4,5,7),这样就达到了排序的目的。以上文字摘自http://www.cnblogs.com/Trony/archive/2012/09/01/2667064.html

实现如下:

 1 void SetBit(char *pData, int nNum)
 2 {
 3     //找出需要设置的字节位置
 4     for(int i=0; i<nNum/BYTESIZE; i++)
 5         pData++;
 6     //对字节中nNum对应的bit位进行设置
 7     *(pData) |= 0x1<<(nNum%BYTESIZE);
 8 }
 9
10 void BitMapSort(int Arr[], int nSize)
11 {
12     //一个byte可以表示8个bit,伴随数组的大小为(nSize/BYTESIZE)+1
13     char *pData = new char[nSize/BYTESIZE+1];
14     memset(pData, 0x0, sizeof(char)*(nSize/BYTESIZE+1));
15
16     //根据原数组中每个元素的值设置伴随数组中对应的bit位
17     for(int i=0; i!=nSize; i++)
18         SetBit(pData, Arr[i]);
19
20     int z=0;
21     for(int i=0; i!=nSize/BYTESIZE+1; i++)
22     {
23         //对每个byte中的8个bit进行校验
24         for(int j=0; j!=BYTESIZE; j++)
25         {
26             if(*pData & (0x1<<j))
27                 Arr[z++] = i*BYTESIZE+j;
28         }
29         pData++;
30     }
31
32     delete[] pData;
33 }
34
35 int _tmain(int argc, _TCHAR* argv[])
36 {
37     int a[] = {3,1,2,5,9};
38
39     BitMapSort(a, sizeof(a)/sizeof(*a));
40     for(int i=0; i!=sizeof(a)/sizeof(*a); i++)
41         cout<<a[i]<<"    ";
42     cout<<endl;
43     return 0;
44 }

  位图排序同样是一种线性排序,与计数排序相比,动态分配的伴随数组占用的空间小了很多,但是存在一个缺点,位图排序要求所排序的数组中没有重复元素,因为一个bit位只能用0,1来表示一个整数的存在与否,不能表示此整数数的出现次数,位图排序用到了很多位操作,代码实现也比计数排序更为复杂。

  位图法除了可以用作排序,也可以用来做匹配和查找。这里举一个字符串包含(匹配)的例子:

 1 bool bMatchString(const char *a, const char *b)
 2 {
 3     int nDictionary = 0;
 4     int nLenA = strlen(a), nLenB = strlen(b);
 5
 6     int i;
 7     for(i=0; i!=nLenA; i++)
 8         nDictionary |= 0x1<<(a[i]-‘A‘);    //对应的bit位进行set
 9
10     for(i=0; i!=nLenB; i++)
11         if(!(nDictionary & 1<<(b[i]-‘A‘)))
12             break;
13
14     if(i != nLenB)
15         return false;
16     else
17         return true;
18 }
19
20 int _tmain(int argc, _TCHAR* argv[])
21 {
22     char A[] = "YOUKNOWILVEYOUSO";
23     char B[] = "LOVE";
24
25     if(bMatchString(A, B))
26         cout<<"A has B"<<endl;
27
28     return 0;
29 }

  实际上对于字符串匹配(包含)程序,定义int hash[26]的伴随数组即可(将元素范围看做26个字母),不会有太多内存消耗,这里只是为了说明位图法同样适用。

时间: 2024-10-29 10:46:16

计数排序与位图排序的相关文章

【每日算法】计数&amp;基数&amp;桶&amp;位图排序-简介

在前面的文章中,我们介绍的都是基于比较的排序. 对于比较排序,对含n个元素的序列进行排序,在最坏情况下都要用O(n logn)次比较(归并排序和堆排序是渐近最优的). 本文将继续介绍以线性时间运行的排序算法,他们使用的是非比较排序,因此下界O(n logn)对它们不适用. 计数排序 想象下面这种情况: 一个班有k个人,需要排成一条纵队,地面上已经用粉笔按从小到大的顺序标明了1到k个号码,要求按身高从低到高排列,也就是说,最高的站在标号为k的位置,最矮的站在标号为1的位置. 那么对于每个人,如何知

序列(两)密钥索引、桶排序、位图、失败者树(照片详细解释--失败者树)

序列(两) 以上排序算法都有一个性质:在排序的终于结果中,各元素的次序依赖于它们之间的比較.我们把这类排序算法称为比較排序. 不论什么比較排序的时间复杂度的下界是nlgn. 下面排序算法是用运算而不是比較来确定排序顺序的.因此下界nlgn对它们是不适用的. 键索引计数法(计数排序) 计数排序如果n个输入元素中的每个都是在0到k区间的一个整数,当中k为某个整数. 思想:对每个输入元素x,确定小于x的元素个数.利用这一信息,就能够直接把x放到它在输出数组中的位置了. 比如: 学生被分为若干组,标号为

位图排序

在<编程珠玑>上看到的,开篇第一个问题,有很多数,小于一个MAXNUM,没有重复的,怎么排序最快. 答案是位图排序. 如果某一位不为0,那么这一位存代表一个数,位数(在序列中的位置)代表这个数. 比方说这些数都存在数组a,然后利用一个数组b,b初始状态各位都为0,然后读取a,如果a[1]=2,那么b[2]为1,a[3]=6,那么b[6]=1. 实现如下: 1 #include <iostream> 2 #include <fstream> 3 #include<s

大数据【四】MapReduce(单词计数;二次排序;计数器;join;分布式缓存)

   前言: 根据前面的几篇博客学习,现在可以进行MapReduce学习了.本篇博客首先阐述了MapReduce的概念及使用原理,其次直接从五个实验中实践学习(单词计数,二次排序,计数器,join,分布式缓存). 一 概述 定义 MapReduce是一种计算模型,简单的说就是将大批量的工作(数据)分解(MAP)执行,然后再将结果合并成最终结果(REDUCE).这样做的好处是可以在任务被分解后,可以通过大量机器进行并行计算,减少整个操作的时间. 适用范围:数据量大,但是数据种类小可以放入内存. 基

[数据结构]利用位图排序

[cpp] view plaincopy #include <stdio.h> #include <stdlib.h> #define INT_BY_BIT 32 #define MASK 0x1F #define SHIFT 5 #define N 1000000 int a[N/INT_BY_BIT+1]; void set_bit(int x) {a[x>>SHIFT] |= 1 << (x & MASK);} void clear_bit(i

计数排序、桶排序python实现

计数排序在输入n个0到k之间的整数时,时间复杂度最好情况下为O(n+k),最坏情况下为O(n+k),平均情况为O(n+k),空间复杂度为O(n+k),计数排序是稳定的排序. 桶排序在输入N个数据有M个桶时,如果每个桶的数据接近N/M个且桶内使用基于比较的排序,则桶排序的时间复杂度为O(N+M*N/M*log(N/M)).如果N=M时,每个桶只有一个数据,时间复杂度降低为O(N). 桶排序的时间复杂度为O(N+M),桶排序是稳定的排序 1.计数排序 计数排序介绍及C语言实现在:计数排序(链接) d

位图排序(位图技术应用)

1.  问题描述 给定不大于整数 n 的 k 个互不相等的整数 ( k <n ) , 对这些整数进行排序.本文讨论的内容具体可参见<编程珠玑>(第二版)的第一章. 2.  问题分析 关于排序,已经有多种排序方法了:插入排序,归并排序,快速排序,希尔排序等.每种排序都有不同的用武之地.为什么需要位图排序呢?所有的内部排序(上述所提及)都必须一次性将所有排序元素载入内存.假如有1000,000个整数,每个整数4字节,则意味着,至少需要4000,000B 约为 4MB 的内存空间, 如果仅仅只

【算法思想】位图排序算法

问题的提出 一个最多包含n个正整数的文件,每个数都小于n,其中n=10^7.假设最多只有1M的内存空间可用,在考虑空间和时间的优化的情况下,请问如何对其进行排序? 常规思想 我们假设这些整数都是用整型存储(一般整型的大小为4个字节),那么1M字节可以存储250 000个数据.由于输入文件最大可能有10^7个数据,因此可以通过遍历输入文件40次来完成排序.第一次将在[0,249 999]范围内的整数读入到内存中,第二次将在[250 000,499 999]范围内的整数读入到内存中,依此类推.每读入

九种经典排序算法详解(冒泡排序,插入排序,选择排序,快速排序,归并排序,堆排序,计数排序,桶排序,基数排序)

综述 最近复习了各种排序算法,记录了一下学习总结和心得,希望对大家能有所帮助.本文介绍了冒泡排序.插入排序.选择排序.快速排序.归并排序.堆排序.计数排序.桶排序.基数排序9种经典的排序算法.针对每种排序算法分析了算法的主要思路,每个算法都附上了伪代码和C++实现. 算法分类 原地排序(in-place):没有使用辅助数据结构来存储中间结果的排序**算法. 非原地排序(not-in-place / out-of-place):使用了辅助数据结构来存储中间结果的排序算法 稳定排序:数列值(key)