1、计数排序:
条件:基本上仅用于数字排序,需要知道数据的上下限,而且最好从0开始这样实现起来比较方便些,需要[上限 - 下限]的空间复杂度,时间复杂度可以按教科书的认知是O(N),实际操作起来其实要随数据范围的变大而变大(O(N * 2) + O(范围长度)),如数据范围特别大从0-2^32,这样的话计数排序的时间复杂度也相当不小。
结论:适合范围不太大的数据的排序,最好从0到正整数这样的范围
思路:比如排序1000个数,范围为0-99之间,即上限99下限0,计数排序的思路是:
1、既然范围已知,创建一个100个元素大小的数组,然后遍历一次全部1000个数据,记录每个数据的出现次数
2、然后对0-99范围内每个数字,累计全部1000个数据中,小于等于这个数字的数据个数,更新到上一步创建的数组中,如:
假设全部1000个数据中,0出现12次,1出现15次,2出现0次,3出现5次,4出现17次,5出现10次,则:
数组[0] = 12,数组[1] = 12 + 15 = 27,数组[2] = 27 + 0 = 27,数组[3] = 27 + 5 = 32,数组[4] = 32 + 5 = 37,数组[5] = 37 + 10 = 47
这步遍历一次这个数组,时间复杂度为O(数据范围长度),目的是排范围中每个数字的排序座次,比如从小到大排序,则全部数据中的5将出现在38-47位
3、然后就按第2步更新好的数组,再逆向遍历一次全部1000数据,按每个数字的出现座次落位到返回结果数组中
代码:
#include <iostream>
#include <functional>
#include <random>
#include <vector>
#include <algorithm>
int cutf (int max, int cur) {
return cur/max;
}
using CutFunc = std::function<int (int, int)>;
class BucketSort {
int *rawdata;
int size;
int max;
int bucket_num;
std::vector<int> *buckets;
std::function<int (int, int)> &cutfunc;
public:
BucketSort (int *_data, int _size, int _bucket_num, int _max, CutFunc &_cutfunc):size(_size), bucket_num(_bucket_num), max(_max), rawdata(_data), cutfunc(_cutfunc) {
buckets = new std::vector<int>[bucket_num];
}
~BucketSort () {
delete []buckets;
}
void bucketsort () {
for (int i = 0; i < size; i++) {
int idx = cutfunc(max, rawdata[i]);
buckets[idx].push_back(rawdata[i]);
}
for (int i = 0; i < bucket_num; i++) {
std::sort(buckets[i].begin(), buckets[i].end());
}
for (int i = 0; i < bucket_num; i++) {
for (int j = 0; j < buckets[i].size(); j++) {
std::cout << buckets[i][j] << "\t";
}
}
std::cout << std::endl;
}
int Get_Median () {
int half = size/2, sum = 0;
for (int i = 0; i < bucket_num; i++) {
int cur = buckets[i].size();
if (sum + cur < half) {
sum += cur;
} else if (sum + cur == half) {
return buckets[i][buckets[i].size() - 1];
} else {
return buckets[i][sum + cur - half];
}
}
}
};
int main () {
std::random_device rd;
int data[100];
int max = 10000;
for (int i = 0; i < 100; i++) {
int cur = rd() % max;
std::cout << cur << ",\t";
data[i] = cur;
}
std::cout << std::endl;
int bucket_num = 100;
CutFunc cutfunc = &cutf;
BucketSort bs(data, sizeof(data)/sizeof(data[0]), bucket_num, max, cutfunc);
bs.bucketsort();
std::cout << bs.Get_Median() << std::endl;
return 0;
}
2、桶排序:
桶排序可以认为是计数排序的加强版,计数排序很机械的强牵制于数据出现范围的大小,对范围敏感,范围太大导致低效;
桶排序和计数排序相似的是,它也需要关注数据出现范围,但不再是挨个关注每个数字出现的次数,而是按数字的范围,用自定义的数据划分方式,将原始数据划分在一系列的桶内,比如原始数据的范围是0-9999,那么可以自定义100个桶,划分方式就是0-99在桶1,100-199在桶2.....直到9900-9999在桶100,这样首先保证了桶之间有序,然后依次对排序每个桶内的数据,最后就可以按桶id的顺序遍历每个桶内全部数据,自然就是有序的了。
计数排序相当于每个数字是一个桶,一个桶只会有"一种多个"数字,所以计数排序可以认为是桶排序的一个特例
桶排序体现了一种数据分布的思想,合理的划分方式,让数据分在多个桶内,实现了数据分片,每个桶内的排序工作量就大大减轻了,很容易发现,它适合大数据分布式运行,可以作为解决一些大数据问题的方法之一,适用场景的典型特征就是原始数据有明显适合的划分方式,如15亿中国人按年龄排序、海量数据中第k大/中位数之类,代码中包括中位数求法。
代码:
#include <iostream>
#include <functional>
#include <random>
#include <vector>
#include <algorithm>
int cutf (int max, int cur) {
return cur/max;
}
using CutFunc = std::function<int (int, int)>;
class BucketSort {
int *rawdata;
int size;
int max;
int bucket_num;
std::vector<int> *buckets;
std::function<int (int, int)> &cutfunc;
public:
BucketSort (int *_data, int _size, int _bucket_num, int _max, CutFunc &_cutfunc):size(_size), bucket_num(_bucket_num), max(_max), rawdata(_data), cutfunc(_cutfunc) {
buckets = new std::vector<int>[bucket_num];
}
~BucketSort () {
delete []buckets;
}
void bucketsort () {
for (int i = 0; i < size; i++) {
int idx = cutfunc(max, rawdata[i]);
buckets[idx].push_back(rawdata[i]);
}
for (int i = 0; i < bucket_num; i++) {
std::sort(buckets[i].begin(), buckets[i].end());
}
for (int i = 0; i < bucket_num; i++) {
for (int j = 0; j < buckets[i].size(); j++) {
std::cout << buckets[i][j] << "\t";
}
}
std::cout << std::endl;
}
int Get_Median () {
int half = size/2, sum = 0;
for (int i = 0; i < bucket_num; i++) {
int cur = buckets[i].size();
if (sum + cur < half) {
sum += cur;
} else if (sum + cur == half) {
return buckets[i][buckets[i].size() - 1];
} else {
return buckets[i][sum + cur - half];
}
}
}
};
int main () {
std::random_device rd;
int data[100];
int max = 10000;
for (int i = 0; i < 100; i++) {
int cur = rd() % max;
std::cout << cur << ",\t";
data[i] = cur;
}
std::cout << std::endl;
int bucket_num = 100;
CutFunc cutfunc = &cutf;
BucketSort bs(data, sizeof(data)/sizeof(data[0]), bucket_num, max, cutfunc);
bs.bucketsort();
std::cout << bs.Get_Median() << std::endl;
return 0;
}
基数排序:
思路:基数排序本质上有比桶排序和计数排序都更多的东西,类似于桶排序的是,也用自定义的划分方式,而基数排序更是不断划分数据,所谓的”基数“就是一直根据某种方式划分数据的思想。比如,在海量数据中寻找中位数,基数排序有更为开放的思路:
海量数据寻找中位数,假设是int32的非负整数即0到2^32 - 1(负数也可以搞,稍加修改即可(统一加32768),基数排序不是计数排序):
桶排序:根据自定义的区间创建若干桶,然后真的把海量数据每个数据放入对应区间的桶中且同时纷纷排序,然后寻找中位的桶及其里边对应位置处数据
基数排序:1、2^16 = 65536,上限是2^32即65536 * 65536,那就创建65536个桶,然后每个数除以65536,除数肯定位于0-65535中,每个桶只做累加;O(N)
2、统计每个桶内累加计数之和,像桶排序一样找到中位数位于哪个桶中,比如是32777号桶,同时要记下到32776号桶已经有多少个数据了,比如全部数据量是100 * 1000 * 1000为一亿,假设到32776号桶已经有9990万了,这样问题就转化为,确定会落在32777号桶里的按顺序的第10万名是谁;O(65536)
3、遍历全量数据,把会落在32777号桶的数据放入32777号桶,方式同1,然后排序并找到第10万名是谁,这个就是中位数了;O(N + klog(k))或O(N + k),k是32777号桶容量,用原地比较排序就是O(N + klog(k))要不就是O(N + k)
总计:相当于O(N * 2)
相比桶排序,免去了大量的桶空间占用,因为无需放入数据,只需要65536容量数组累加,而且免去了大量的每个桶排序,时间复杂度也更快一些。
下面代码是简单的对数字的基数排序,看起来更像是计数排序和桶排序的结合,核心思想是:按10进制数字从低位到高位为标准,不断的排序,即依次按个位、十位、百位、......位数不足的会是0,比如38,135,1111这3个数,首先按个位排序是1111、135、38,然后更新入桶里,再按十位排序是1111、135、38更新入桶,再按百位是38、1111、135更新入桶,最后是千位是38、135、1111,O(N)时间复杂度,O(N + 10)空间复杂度。
上面的按个位、十位这样的方式,对于基数排序的"基数"来说,每个数字10进制的依次的低位就是基数,还可以有其他自定义的各种”基数“去排序,随便举例如还可以按高位余数排序,如字符串的自定义排序规则、甚至复合结构比如典型一些面试题”全校学生的多个科目成绩表,按不同科目不同优先级排序“等等;也就是基数排序其实更多是一种”根据多维实现自定义排序“的思想吧,下面代码不重要,重在理解”基数“内涵。
代码:
#include <iostream>
#include <random>
#include <memory.h>
int get_bit_num (int max) {
int cnt = 0;
while (max) {
max /= 10;
if (max) {
++cnt;
}
}
return cnt;
}
void radixsort (int *data, int size, int max) {
int times = get_bit_num(max), base = 1;
int range[10] = {0};
int res[size];
for (int i = 0; i < times; i++) {
memset(range, 0, sizeof(range));
for (int i = 0; i < size; i++) {
int remander = (data[i]/base) % 10;
++range[remander];
}
for (int i = 1; i < 10; i++) {
range[i] += range[i - 1];
}
for (int i = size - 1; i >= 0; i--) {
res[range[(data[i]/base) % 10] - 1] = data[i];
--range[(data[i]/base) % 10];
}
for (int i = 0; i < size; i++) {
data[i] = res[i];
}
base *= 10;
}
for (int i = 0; i < size; i++) {
std::cout << data[i] << ",\t";
}
std::cout << std::endl;
}
int main () {
int max = 10000;
std::random_device rd;
int data[2000];
for (int i = 0; i < 2000; i++) {
int cur = rd() % max;
std::cout << cur << ",\t";
data[i] = cur;
}
std::cout << std::endl;
radixsort(data, sizeof(data)/sizeof(data[0]), max);
return 0;
}