元婴期
15. 堆是什么?最大堆和最小堆的应用场景是什么?
堆 是一种特殊的二叉树,满足以下性质:
最大堆:每个节点的值都大于或等于其子节点的值,根节点是整个堆的最大值。
如下结构所示:
9 / \ 5 6 / \ / 3 4 2
最小堆:每个节点的值都小于或等于其子节点的值,根节点是整个堆的最小值。
如下结构所示:
1 / \ 3 2 / \ / 6 5 9
应用场景:
最大堆:用于实现优先级队列(如任务调度器),可以快速获取优先级最高的元素。
最小堆:用于求解最小值问题,或者在需要按顺序输出最小元素时(如最短路径算法的实现)。
16. 如何实现堆排序?解释其时间复杂度。
堆排序 是一种基于堆的数据结构进行排序的算法,利用最大堆或最小堆的性质实现升序或降序排序。
实现步骤:
将数组构建为最大堆(升序)或最小堆(降序)。
取出堆顶元素(最大或最小),将其放到已排序区域,并将剩余的元素重新调整为堆。
重复上述操作,直到所有元素排序完成。
时间复杂度:
构建堆 的时间复杂度为
O(n)
。每次调整堆的时间复杂度为
O(log n)
,重复n
次,整体时间复杂度为O(n log n)
。
17. 什么是跳表(Skip List)?跳表的时间复杂度是多少?
跳表 是一种支持快速查找、插入和删除的链表结构,它在有序链表的基础上增加多层索引,通过跳跃式的搜索提高效率。
假设我们有一个有序的链表存储以下数据:1, 3, 5, 7, 9, 13, 17, 21, 25
。我们可以构建如下的跳表:
Level 2: 1 -------- 9 -------- 17 -------- 25
Level 1: 1 ---- 5 ---- 9 ---- 13 ---- 17 ---- 21 ---- 25
Level 0: 1 -- 3 -- 5 -- 7 -- 9 -- 13 -- 17 -- 21 -- 25
Level 0 是完整的链表,包含所有元素。
Level 1 是
Level 0
的缩减版本,跳跃性连接部分节点,减少查找步骤。Level 2 是
Level 1
的缩减版本,跳跃性连接更少的节点。
假设我们要在这个跳表中查找数字 13
,过程如下:
从最高层 Level 2 开始,比较
13
和当前节点的值:当前节点是
1
,向右跳到9
。13 > 9
,继续跳到17
。发现13 < 17
,回到上一层。
进入 Level 1,继续比较:
当前节点是
9
,向右跳到13
。找到了
13
。
通过跳表结构,我们跳过了部分节点,减少了比较的次数,从而加速查找。
时间复杂度:
查找、插入、删除 的平均时间复杂度是
O(log n)
,因为每一层跳表大约减少一半的元素,类似于二分查找。最坏情况下,时间复杂度是
O(n)
。
跳表常用于实现有序数据结构,如 Redis
中的有序集合。
18. Trie 树是什么?它的应用场景有哪些?
Trie 树 是一种用于高效存储和查找字符串的数据结构,每个节点表示一个字符,路径表示一个字符串。
假设我们要存储以下单词集合:
cat
cap
bat
bake
Trie
树的结构如下:
(root)
/ \
c b
/ \ / \
a a a a
/ \ \
t* p* t*
/
k
/
e*
根节点(root):
Trie
树从一个空的根节点开始。每个单词的字符路径:
cat
:路径是c -> a -> t
。cap
:路径是c -> a -> p
。注意,这与cat
共享前缀c -> a
。bat
:路径是b -> a -> t
。bake
:路径是b -> a -> k -> e
。注意,b -> a
共享在bat
中。
终止节点:每个单词的最后一个字符节点代表一个完整单词。例如,
t
在cat
中,p
在cap
中,e
在bake
中。
应用场景:
前缀匹配:快速查找具有共同前缀的字符串集。
自动补全:输入法、搜索引擎中的自动补全功能。
字典树:快速检索单词,如拼写检查器。
在 Trie
树中存储单词 apple
和 app
时,app
是 apple
的前缀,两个单词共享相同的前缀节点。
19. 什么是并查集?如何用并查集实现集合的合并与查找?
并查集(Union-Find) 是一种用于处理动态连通性问题的数据结构,适用于快速合并集合和查询元素是否属于同一集合。
基本操作:
查找(Find):找到某个元素所在集合的代表元素(根节点)。
合并(Union):将两个集合合并为一个集合。
实现:
路径压缩:在查找过程中,将沿途节点直接指向根节点,优化后续查询。
按秩合并:每次合并时,将节点数较少的树合并到节点数较多的树中,减少树的高度。
并查集用于解决图中的连通性问题,如判断图中是否存在环,或解决网络中节点是否属于同一连通区域。
20. 什么是哈夫曼编码?它的原理和应用是什么?
哈夫曼编码(Huffman Coding) 是一种贪心算法,用于构建最优前缀编码,常用于数据压缩。
原理:
根据字符出现的频率构建哈夫曼树,频率越高的字符对应的编码越短。
使用二进制编码表示字符,保证没有一个编码是另一个编码的前缀。
哈夫曼编码示例:
假设我们要对字符串 BCAADDDCCACACAC
进行哈夫曼编码。
第一步:统计字符频率
字符: A B C D
频率: 5 1 6 3
第二步:构建哈夫曼树
将每个字符看作一个叶子节点,频率作为权重。
合并频率最小的两个节点。
初始节点:
A: 5 B: 1 C: 6 D: 3
- 合并
B
和D
,新节点频率为1 + 3 = 4
,结果如下:
*
/ \
B D
新节点权重 4:B 和 D
- 再合并
A
和新节点(B+D)
,结果如下:
*
/ \
A *
/ \
B D
- 最后合并上面的结果和
C
:
*
/ \
* C
/ \
A *
/ \
B D
第三步:生成编码
根据哈夫曼树,从根节点出发生成二进制编码。每往左走一步记作 0
,每往右走一步记作 1
。
生成的编码如下:
A
的编码:00
B
的编码:110
C
的编码:1
D
的编码:111
第四步:编码文本
将字符串 BCAADDDCCACACAC
转换成哈夫曼编码:
B -> 110
C -> 1
A -> 00
A -> 00
D -> 111
D -> 111
D -> 111
C -> 1
C -> 1
A -> 00
C -> 1
A -> 00
C -> 1
A -> 00
C -> 1
压缩后的哈夫曼编码是:
110 1 00 00 111 111 111 1 1 00 1 00 1 00 1
应用场景:
数据压缩:如
ZIP
文件压缩、JPEG
图像压缩。通信:用于减少数据传输时的带宽占用。
21. 布隆过滤器(Bloom Filter)是什么?它有哪些应用场景?
布隆过滤器(Bloom Filter) 是一种概率型数据结构,用于高效判断某个元素是否存在于集合中,但允许一定的误判率(即存在误判为真,但不存在误判为假)。
原理:
布隆过滤器使用多个哈希函数将元素映射到一个位数组上。当查询一个元素时,只需检查所有哈希函数映射的位置是否都为
1
。如果有任何位置为
0
,则该元素肯定不在集合中;如果所有位置都为1
,元素可能存在(存在误判)。
假设我们有一个位数组初始为全 0,并使用 3 个哈希函数。
插入数据 apple:
apple
被3
个哈希函数分别映射到位数组的第2
、5
和8
位。我们将这 3 个位置的值设置为
1
。此时位数组状态为:
[0, 0, 1, 0, 0, 1, 0, 0, 1, 0]
查询数据 apple:
再次使用 3 个哈希函数对
apple
进行哈希映射,依然会映射到位数组的第2
、5
和8
位。检查这
3
个位置的值是否为1
。由于它们都是1
,我们判断apple
可能存在。
查询数据 banana:
使用
3
个哈希函数对banana
进行哈希映射,假设映射到位数组的第1
、3
和7
位。检查位数组中这
3
个位置。由于第1
位为0
,我们可以确定"banana"
一定不存在。
应用场景:
网页黑名单检测:判断一个
URL
是否在黑名单中。缓存穿透:在分布式缓存系统中,布隆过滤器可以避免查询数据库的无效请求。
垃圾邮件过滤:判断某个邮件是否为垃圾邮件。