date: 2018-11-25 08:31:30
updated: 2018-11-25 08:31:30
栈&队列&并查集&哈希表(julyedu网课整理)
栈和队列
1.定义
- 存放数据的线性表
- 操作:入栈/队列、出栈/队列、判断满/空
- 空间复杂度:O(n)
- 单次操作时间复杂度:O(1)
- 区别
- 先进后出(FILO, First In Last Out)
- 先进先出(FIFO, First In First Out)
2.实现
- 数组和链表皆可(线性表)
- 指针(辅助变量)
- 栈顶/底指针
- 队头/尾指针
- 关键:出入元素的同时移动指针
- 队列的头/尾指针都要改变
- 栈只有栈顶指针改变,栈底指针不变
3.栈的应用:括号匹配检测
- 括号、引号等符号是成对出现的,必须相互匹配
- 设计一个算法,自动检测输入的字符串中的括号是否匹配
- 比如:
- {}[([][])] 匹配
- [(]) 不匹配
- (()] 不匹配
思路:
从左到右扫描字符串,当入栈一个 '[' 时就期待一个 ']' ,当且仅当入栈一个 '[' 后紧跟着一个 '] '时, '[' 出栈。
leetcode #394
4.栈的应用:模拟系统栈
int F(int n) {
if (n <= 1)
return 1;
return n * F(n – 1);
}
递归,时间空间复杂度O(n)
生产上应谨慎使用递归,多个项目运行时可能造成栈溢出
原因:每一次递归都要记录上一次的地址,以及下一次的结果,不断递归下去,就会不断增加栈的压力
模拟系统栈:
do {
if (!back) { // back是边界判断
if (n <= 1) {
back = true;
ret = 1;
continue;
}
n进栈;
--n;
}else{
ret *= 出栈;
}
} while (栈不为空);
并查集
1.定义
- 存放数据的集合关系,如{1,2}{3,4}{5}
- 支持操作
- 建立新集合
- 查找某个元素属于哪个集合
- 合并两个集合
- 均摊时间复杂度近似O(1)
2.应用
- 假设n个节点,初始时点与点之间没有连接
- 给出一系列的连接操作
- 一次连接操作不产生环,则接受,否则被抛弃
哈希表(散列表)
1.定义
- 根据关键码值(Key,Value)而直接进行访问的数据结构
- 操作:根据(Key, Value)进行
- 插入,查找,删除(可以没有)
- 空间复杂度:O(n)
- 单次操作时间复杂度:O(1)
- 本质:Key的索引
2.实现
就是把Key通过一个固定的算法函数既所谓的哈希函数转换成一个整型数字,然后就将该数字对数组长度进行取余,取余结果就当作数组的下标,将value存储在以该数字为下标的数组空间里。
而当使用哈希表进行查询的时候,就是再次使用哈希函数将key转换为对应的数组下标,并定位到该空间获取value,如此一来,就可以充分利用到数组的定位性能进行数据定位
3.例题:Top K
搜索引擎会通过日志文件把用户每次检索使用的所有检索串都记录下来,每个查询串的长度为1-255字节。
假设目前有一千万个记录(这些查询串的重复度比较高,虽然总数是1千万,但如果除去重复后,不超过3百万个。一个查询串的重复度越高,说明查询它的用户越多,也就是越热门。),请你统计最热门的10个查询串,要求使用的内存不能超过1G。
一共分为两个步骤,第一个是需要先统计出每个Query的次数,第二个是根据统计结果,找出Top K
3.1 统计
3.1.1 归并排序
归并排序和快速排序的期望时间复杂度O(nlogn)
遍历的时间复杂度O(n)
∴总体的时间复杂度是O(n)+O(nlogn) = O(nlogn)
3.1.2 HashTable
HashTable的查询速度非常的快,几乎是O(1)的时间复杂度。
维护一个Key为Query字串,Value为该Query出现次数的HashTable,每次读取一个Query,如果该字串不在Table中,那么加入该字串,并且将Value值设为1;如果该字串在Table中,那么将该字串的计数加一即可。
最终我们在O(N)的时间复杂度内完成了对该海量数据的处理。
3.2 找出Top K
3.2.1 直接排序
三百万数据,每条记录是255Byte,大约0.712G,所以1G内存可以存下
3.2.2 部分排序
题目要求是求出Top 10,因此我们没有必要对所有的Query都进行排序,我们只需要维护一个10个大小的数组,初始化放入10个Query,按照每个Query的统计次数由大到小排序,然后遍历这300万条记录,每读一条记录就和数组最后一个Query对比,如果小于这个Query,那么继续遍历,否则,将数组中最后一条数据淘汰,加入当前的Query。最后当所有的数据都遍历完毕之后,那么这个数组中的10个Query便是我们要找的Top10了。
不难分析出,这样,算法的最坏时间复杂度是O(N*K), 其中K是指top多少。
####### 3.2.3 最小堆
求最大的K个数--最小堆--堆顶放的是K个数里最小的那一个
求最小的K个数--最大堆--堆顶放的是K个数里最大的那一个
具体过程是,堆顶存放的是整个堆中最小的数,现在遍历N个数,把最先遍历到的k个数存放到最小堆中,并假设它们就是我们要找的最大的k个数,X1>X2...Xmin(堆顶),而后遍历后续的N-K个数,一一与堆顶元素进行比较,如果遍历到的Xi大于堆顶元素Xmin,则把Xi放入堆中,而后更新整个堆,更新的时间复杂度为logK,如果Xi<Xmin,则不更新堆,整个过程的复杂度为O(K) + O((N-K)*logK)=O(N * logK)
总结:
第一步用HashTable统计每个Query出现的次数,时间复杂度为O(n);
第二步用采用最小堆找出Top K,时间复杂度O(N*logK)
最终时间复杂度为O(N) + O(N' * logK),N为一千万,N'为三百万