[非基于比较的排序]
在计算机科学中,排序是一门基础的算法技术,许多算法都要以此作为基础,不同的排序算法有着不同的时间开销和空间开销。排序算法有非常多种,如我们最常用的快速排序和堆排序等算法,这些算法需要对序列中的数据进行比较,因为被称为基于比较的排序。
而非基于比较的排序,如计数排序,桶排序,和在此基础上的基数排序,则可以突破O(NlogN)时间下限。但要注意的是,非基于比较的排序算法的使用都是有条件限制的,例如元素的大小限制,相反,基于比较的排序则没有这种限制(在一定范围内)。但并非因为有条件限制就会使非基于比较的排序算法变得无用,对于特定场合有着特殊的性质数据,非基于比较的排序算法则能够非常巧妙地解决。
[计数排序]
首先从计数排序(Counting Sort)开始介绍起,假设我们有一个待排序的整数序列A,其中元素的最小值不小于0,最大值不超过K。建立一个长度为K的线性表C,用来记录不大于每个值的元素的个数。
算法思路如下:
1. 扫描序列A,以A中的每个元素的值为索引,把出现的个数填入C中。此时C[i]可以表示A中值为i的元素的个数。
2. 对于C从头开始累加,使C[i]=C[i]+C[i-1]。这样,C[i]就表示A中值不大于i的元素的个数。
3. 从尾部往头扫描,则A[i]应该放在B[count[A[i]]-1]处,(减1因为C[i]也包括了自己),然后count[A[i]]–;
#include<iostream>
#include<vector>
using namespace std;
void countingSort(vector<int> &A, int maxKey){
vector<int> count(maxKey + 1, 0);
for (int i = 0; i < A.size(); i++){
count[A[i]]++;
}
for (int i = 1; i < count.size(); i++){
count[i] += count[i - 1];
}
vector<int> B(A.size());
/*下面的操作是为了让排序是稳定的,否则不需要*/
for (int i = A.size() - 1; i >= 0; i--){
B[count[A[i]] - 1] = A[i];
count[A[i]]--;
}
A = B;
}
int main(){
vector<int> A = { 3, 5, 8, 9, 1, 2 };
countingSort(A, 9);
for (int n : A){
cout << n << " ";
}
cout << endl;
return 0;
}
可以发现计数是一种稳定排序算法;
计数排序的时间复杂度为O(N+K),空间复杂度为O(N+K)。当K不是很大时,这是一个很有效的线性排序算法。
[桶排序]
同计数排序一样,桶排序也对待排序序列作了假设,桶排序假设序列由一个随机过程产生,该过程将元素均匀而独立地分布在区间[0,1)上。基本思想是:把区间[0,1)划分成M个相同大小的子区间,称为桶。将n个记录分布到各个桶中去。如果有多于一个记录分到同一个桶中,需要进行桶内排序。最后依次把各个桶中的记录列出来记得到有序序列。
当然,也不需要一定要在[0,1)之间。如果元素的最小值不小于0,最大值不超过K。假设我们有M个桶,第i个桶Bucket[i]存储iK/M至(i+1)K/M之间的数。
注意:只要数据和桶之间有一个映射函数即可,不一定非要上面的形式。
参考:http://hxraid.iteye.com/blog/647759
#include<iostream>
#include<vector>
using namespace std;
struct node{
int val;
node* next;
node(int val) :val(val), next(NULL){}
};
void bucketSort(vector<int> &A, int maxKey,int numOfBucket){
vector<node*> B(numOfBucket);
for (int i = 0; i < numOfBucket; i++){
B[i] = new node(0);
}
for (int i = 0; i < A.size(); i++){
node* u = new node(A[i]);
int key = A[i] / numOfBucket;
if (B[key]->next == NULL){
B[key]->next = u;
}
else{
//插入排序
node* p = B[key];
//要用p->next
while (p->next!=NULL && p->next->val < u->val){
p = p->next;
}
node* tmp = p->next;
p->next = u;
u->next = tmp;
}
}
int i = 0;
for (int j = 0; j < numOfBucket; j++){
node *p = B[j]->next;
while (p){
A[i++] = p->val;
p = p->next;
}
}
}
int main(){
vector<int> A = { 3, 5, 8, 9, 1, 2 };
bucketSort(A, 9, 4);
for (int n : A){
cout << n << " ";
}
cout << endl;
return 0;
}
当记录在桶中分布均匀时,即每个桶只有一个元素,此时时间复杂度o(n)。空间复杂度也是O(n)。
算法导论证明只要就算不是均匀分布,只要桶数目的平方根元素个数成比例,时间复杂度就是O(n)。
桶排序在海量数据中的应用
1.
一年的全国高考考生人数为500 万,分数使用标准分,最低100 ,最高900 ,没有小数,你把这500 万元素的数组排个序。
分析:对500W数据排序,如果基于比较的先进排序,平均比较次数为O(5000000*log5000000)≈1.112亿。但是我们发现,这些数据都有特殊的条件: 100=<score<=900
。那么我们就可以考虑桶排序这样一个“投机取巧”的办法、让其在毫秒级别就完成500万排序。
方法:创建801(900-100)个桶。将每个考生的分数丢进f(score)=score-100的桶中。这个过程从头到尾遍历一遍数据只需要500W次。然后根据桶号大小依次将桶中数值输出,即可以得到一个有序的序列。而且可以很容易的得到100分有?人,501分有?人。
2.
题目:在一个文件中有 10G 个整数,乱序排列,要求找出中位数。内存限制为 2G。只写出思路即可(内存限制为 2G的意思就是,可以使用2G的空间来运行程序,而不考虑这台机器上的其他软件的占用内存)。
思想:将整形的每1byte作为一个关键字,也就是说一个整形可以拆成4个keys,而且最高位的keys越大,整数越大。如果高位keys相同,则比较次高位的keys。整个比较过程类似于字符串的字典序。
假设是unsigned int.
第一步:把10G整数每2G/4读入一次内存,然后一次遍历这些个数据。每个数据用位运算”>>”取出最高8位(31-24)。这8bits(0-255)最多表示256个桶,那么可以根据8bit的值来确定丢入第几个桶。最后把每个桶写入一个磁盘文件中,同时在内存中统计每个桶内数据的数量。
第二步:根据内存中256个桶内的数量,计算中位数在第几个桶中。
第三步:继续以内存中的整数的次高8bit进行桶排序(23-16)。过程和第一步相同,也是256个桶。
第四步:一直下去,直到最低字节(7-0bit)的桶排序结束。我相信这个时候完全可以在内存中使用一次快排就可以了。
[基数排序]
上述的基数排序和桶排序都只是在研究一个关键字的排序,现在我们来讨论有多个关键字的排序问题。
假设我们有一些二元组(a,b),要对它们进行以a为首要关键字,b的次要关键字的排序。我们可以先把它们先按照首要关键字排序,分成首要关键字相同的若干堆。然后,在按照次要关键值分别对每一堆进行单独排序。最后再把这些堆串连到一起,使首要关键字较小的一堆排在上面。按这种方式的基数排序称为MSD(Most Significant Dight)排序。
第二种方式是从最低有效关键字开始排序,称为LSD(Least Significant Dight)排序。首先对所有的数据按照次要关键字排序,然后对所有的数据按照首要关键字排序。要注意的是,使用的排序算法必须是稳定的,否则就会取消前一次排序的结果。由于不需要分堆对每堆单独排序,LSD方法往往比MSD简单而开销小。下文介绍的方法全部是基于LSD的。
常用于整数排序:
将所有待比较数值(注意,必须是正整数)统一为同样的数位长度,数位较短的数前面补零.
然后, 从最低位开始, 依次进行一次稳定排序(计数排序算法, 因为每个位可能的取值范围是固定的从0到9).
这样从最低位排序一直到最高位排序完成以后, 数列就变成一个有序序列.
#include<iostream>
#include<vector>
#include<cmath>
using namespace std;
void countingSort(vector<int> &A, int maxKey, int d){
vector<int> count(maxKey + 1, 0);
vector<int> dVal(A.size());
for (int i = 0; i < A.size(); i++){
dVal[i] = (A[i] / int(pow(10, d - 1))) % 10;
}
for (int i = 0; i < A.size(); i++){
count[dVal[i]]++;
}
for (int i = 1; i < count.size(); i++){
count[i] += count[i - 1];
}
vector<int> B(A.size());
/*下面的操作是为了让排序是稳定的,否则不需要*/
for (int i = A.size() - 1; i >= 0; i--){
B[count[dVal[i]] - 1] = A[i];
count[dVal[i]]--;
}
A = B;
}
void radixSort(vector<int> &A, int maxD){
for (int d = 1; d < maxD; d++){
countingSort(A, 9, d);
}
}
int main(){
vector<int> A = { 114, 118, 152, 114, 111, 132 };
radixSort(A, 3);
for (int n : A){
cout << n << " ";
}
cout << endl;
return 0;
}