一种O(n)时间复杂度的计数排序算法和Top N热词算法
排序算法是研究非常广泛且超级经典的算法,主流排序算法的时间复杂度基本都在O(nlogn)。
今天就介绍一种以hash表为基础的,时间复杂度能够达到O(n)的排序算法——计数排序;
同时基于它的思想,完成时间复杂度同样为O(n)的求Top N热词的小功能应用。
算法思想
01 n个数据需要排序,就把数据映射到 [0, n-1] ,然后用 int 数组 id2num [n] 完成 “编号 to 出现次数的映射 ”;
简单来说,对于正整数数据,先找到最大值maxData,那么可以直接创建 int data2num [ maxData+1 ]的数组,这样 data2num[i] 就是数据 i 的出现次数。
映射和统计出现次数都只需要遍历一遍,时间复杂度均为 O(n)。
02 因为已经完成了data2num的映射,索引是数据,且索引int值的大小顺序与数据的大小顺序一致,所以直接输出即可完成排序;
例如原始数据为[1,3,9, 5,7,9],则data2num数组中索引为[1,3,5,7]的值都是1,data2num[9] = 2,索引为剩下的[0,2,4,6,8]的值都是0;
递增排序结果:
我们直接顺序遍历data2num数组,对每个值data2num[i],输出data2num[i]个 i 即可。样例的输出就是:[1,3,5,7,9,9]
递减排序结果:
我们直接逆序遍历data2num数组,做同样的操作。样例的输出就是:[9,9,7,5,3,1]
03 显而易见,这种算法适合处理重复值很多的序列,那么它的最佳应用就是求Top N热词。
因为Top N热词就是出现次数最多的前N个词,那么我们只需要建立num2string的链表数组即可;
这样,num2string[i] 就表示存储了所有出现 i 次的热词string的链表,逆序输出 N 个词就完成了这个任务。
C++代码
#include<iostream>
#include<vector>
#include<unordered_map> using namespace std; // 计数排序,通过记录所有元素出现的次数来实现O(n)的排序算法
// 以最简单的正整数数据为例:
vector<int> JiShuSort(vector<int>&a, bool asc=true) {
int n = a.size(), maxData = 0; //找到最大元素(每个元素都是正整数)
for (int i = 0; i < n; i++) {
maxData = max(maxData, a[i]);
} // 数据都是正整数,最多有从1到maxData共maxData种数据,所以空间只用开辟这么大
vector<int>data2num(maxData + 1, 0);
// 记录所有数据出现的次数,进行从maxData种数据到出现次数num的映射 data to num
for (int i = 0; i < n; i++) {
data2num[a[i]]++;
} // 获得排序结果
vector<int> ans;
if (asc) { //递增顺序就从前向后遍历
for (int i = 1; i <= maxData; i++) {
for (int j = 0; j < data2num[i]; j++) {
ans.push_back(i);
}
}
}
else { //递减顺序就从后向前遍历
for (int i = maxData; i > 0; i--) {
for (int j = 0; j < data2num[i]; j++) {
ans.push_back(i);
}
}
}
return ans;
} // 打印n个字符串中出现次数最多的前N个字符串,TOP N 热词算法
// n个字符串共有cnt种不同的字符串
// 时间复杂度O(3*n + 2*cnt + N) = O(n)
void PrintTopN(vector<string>& a, int N) {
cout << "-----------the Top " << N << " hot words is belowe--------------\n";
unordered_map<string, int> string2id; //hash表对新key值的默认value值为0
int n = a.size(), cnt = 0;
// 对cnt个不同的字符串进行hash映射,映射到[1,cnt]
for (int i = 0; i < n; i++) {
if (string2id[a[i]] == 0)string2id[a[i]] = ++cnt;
}
// 进行id到string的反向映射
vector<string>id2string(cnt + 1);
for (int i = 0; i < n; i++) {
id2string[string2id[a[i]]] = a[i];
} // 记录cnt个字符串出现的次数,第i个字符串出现num[i-1]次
vector<int>id2num(cnt + 1);
for (int i = 0; i < n; i++) {
id2num[string2id[a[i]]]++;
}
// 找到最大出现次数
int maxNum = 0;
for (int i = 1; i <= cnt; i++)
maxNum = max(maxNum, id2num[i]); // 记录每个出现次数对应的字符串id
vector<vector<int>>num2id(maxNum + 1, vector<int>());
for (int i = 1; i <= cnt; i++) {
num2id[id2num[i]].push_back(i);
} // 逆序打印top N
for (int i = maxNum; i > 0; i--) {
for (int x : num2id[i]) {
if (N-- == 0)return;
cout << id2string[x] << '\n';
}
}
} int main() {
string a[11] = { "one","two","three","four","five","six","steven","eight","nine","ten","eleven" };
vector<string> strData;
vector<int> intData;
// 让strData中是1个“one”,2个“two”,..., 11个“eleven”
// 让intData中是1个1,2个2,...,11个11
for (int i = 0; i < 11; i++) {
for (int j = 0; j <= i; j++) {
strData.push_back(a[i]);
intData.push_back(i + 1);
}
}
// 获得intData的递减排序结果
vector<int> ans = JiShuSort(intData, false);
cout << "---------------下面是intData递减排序后的数据---------------\n";
for (int x:ans) {
cout << x << '\n';
}
// 获得intData的递增排序结果,asc默认是true
ans = JiShuSort(intData);
cout << "---------------下面是intData递增排序后的数据---------------\n";
for (int x : ans) {
cout << x << '\n';
}
//打印strData中的top 10 热词
PrintTopN(strData, 10);
return 0;
}
一种O(n)时间复杂度的计数排序算法和Top N热词算法的更多相关文章
- js中各个排序算法和sort函数的比较
js中要实现数据排序,其实只需要用sort函数就能很好的满足了,但是我今天想知道他和其他排序算法的区别,比如耗时呀等.测了一组数据如下: // ---------- 一些排序算法 Sort = {} ...
- 惊!世界上竟然有O(N)时间复杂度的排序算法!计数排序!
啥?你以为排序算法的时间复杂度最快也只能O(N*log(N))了? O(N)时间复杂度的排序算法听说过没有?计数排序!!它是世界上最快最简单的算法!!! 计数排序算法操作起来只有三步,看完秒懂! 根据 ...
- 计数排序算法——时间复杂度O(n+k)
计数排序 计数排序是一个非基于比较的排序算法,该算法于1954年由 Harold H. Seward 提出.它的优势在于在对一定范围内的整数排序时,它的复杂度为Ο(n+k)(其中k是整数的范围),快于 ...
- 计数排序和桶排序(Java实现)
目录 比较和非比较的区别 计数排序 计数排序适用数据范围 过程分析 桶排序 网络流传桶排序算法勘误 桶排序适用数据范围 过程分析 比较和非比较的区别 常见的快速排序.归并排序.堆排序.冒泡排序等属于比 ...
- 计数排序 + 线段树优化 --- Codeforces 558E : A Simple Task
E. A Simple Task Problem's Link: http://codeforces.com/problemset/problem/558/E Mean: 给定一个字符串,有q次操作, ...
- 归并排序 & 计数排序 & 基数排序 & 冒泡排序 & 选择排序 ----> 内部排序性能比较
2.3 归并排序 接口定义: int merge(void* data, int esize, int lpos, int dpos, int rpos, int (*compare)(const v ...
- 计数排序详解以及java实现
前言 我们知道,通过比较两个数大小来进行排序的算法(比如插入排序,合并排序,以及上文提到的快速排序等)的时间复杂度至少是Θ(nlgn),这是因为比较排序对应的决策树的高度至少是Θ(nlgn),所以排序 ...
- 计数排序(C语言版本)
让我们来谈谈数的排序思维: 计数排序假定待排序的全部元素都是介于0到K之间的整数.计数排序使用一个额外的数组countArray.当中第i个元素是待排序数组array中值等于i的元素的个数.然后依据数 ...
- 排序算法的C语言实现(下 线性时间排序:计数排序与基数排序)
计数排序 计数排序是一种高效的线性排序. 它通过计算一个集合中元素出现的次数来确定集合如何排序.不同于插入排序.快速排序等基于元素比较的排序,计数排序是不需要进行元素比较的,而且它的运行效率要比效率为 ...
随机推荐
- cookies、sessionStorage和localStorage的区别
cookies.sessionStorage和localStorage的区别 对比 特性 Cookie LocalStorage SessionStorage 数据的生命周期 ...
- Python的安装与开发环境的选用
2021快要结束了,这一年我依旧深耕于python的广阔土壤,将重点放在机器人和传感器的角度.也收获了一大批正在学习和期望学习python的朋友. 正在学习的暂且不言,这篇主要是写给期望学习的朋友,同 ...
- 从零开始,开发一个 Web Office 套件(14):复制、粘贴、剪切、全选
这是一个系列博客,最终目的是要做一个基于 HTML Canvas 的.类似于微软 Office 的 Web Office 套件(包括:文档.表格.幻灯片--等等). 博客园:<从零开始, 开发一 ...
- java代码常用知识点
1.Assert java断言assert是jdk1.4引入的.assert这个关键字我们称之为"断言".当这个关键字后边的条件为假的时候,程序自动崩溃并抛出AssertionEr ...
- vue使用svg,animate事件绑定无效问题及解决方法
由于使用svg制作圆形进度条,但是进度展示的太生硬,没有过渡圆滑的效果,所以使用 animate(在svg元素里可以查到) 元素标签,但 这样使用了,还是没有效果,我前端使用的 vue ,所以通过 @ ...
- 解决MySQL报错ERROR 2002 (HY000)
今天在为新的业务线搭架数据库后,在启动的时候报错 root@qsbilldatahis-db01:/usr/local/mysql/bin# ./mysql ERROR 2002 (HY000): C ...
- synchronized 和 ReentrantLock 的区别?
synchronized 是和 if.else.for.while 一样的关键字,ReentrantLock 是类, 这是二者的本质区别.既然 ReentrantLock 是类,那么它就提供了比 sy ...
- Java 中,Serializable 与 Externalizable 的区别?
Serializable 接口是一个序列化 Java 类的接口,以便于它们可以在网络上传输 或者可以将它们的状态保存在磁盘上,是 JVM 内嵌的默认序列化方式,成本高. 脆弱而且不安全.Externa ...
- Mybatis useGeneratedKeys无法返回主键解决
1.项目环境--SpringBoot下的SSM+Maven 2.问题出现位置--Dao层和Mapper文件 错误代码如下图: dao层: mapper文件: 错误代码分析: 使用useGenerate ...
- mybatis-01-基本流程
mybatis执行流程 1. 加载配置文件并初始化(SqlSession) 配置文件来源于两个地方,一个是配置文件(主配置文件conf.xml,mapper文件*.xml), 一个是java代码中 ...