heapq内置模块的使用
heapq内置模块位于./Anaconda3/Lib/heapq.py,提供基于堆的优先排序算法
堆的逻辑结构就是完全二叉树,并且二叉树中父节点的值小于等于该节点的所有子节点的值。这种实现可以使用 heap[k] <= heap[2k+1] 并且 heap[k] <= heap[2k+2] (其中 k 为索引,从 0 开始计数)的形式体现,对于堆来说,最小元素即为根元素 heap[0]。
初始化#
可以通过 list 对 heap 进行初始化,或者通过 api 中的 heapify 将已知的 list 转化为 heap 对象。
heapq.py中提供的函数方法#
heapq.heappush(heap, item)
heapq.heappop(heap):返回 root 节点,即 heap 中最小的元素。
heapq.heapreplace(heap,item): python3中heappushpop的更高效版。
heapq.heappushpop(heap, item):向 heap 中加入 item 元素,并返回 heap 中最小元素。
heapq.heapify(x):Transform list into a heap, in-place, in O(len(x)) time
heapq.merge(*iterables, key=None, reverse=False)
heapq.nlargest(n, iterable, key=None):返回可枚举对象中的 n 个最大值,并返回一个结果集 list,key 为对该结果集的操作。
heapq.nsmallest(n, iterable, key=None):同上相反
heapq._heappop_max(heap): Maxheap version of a heappop
heapq._heapreplace_max(heap,item):Maxheap version of a heappop followed by a heappush.
heapq._heapify_max(x):Transform list into a maxheap, in-place, in O(len(x)) time
heapq._siftdown(heap,startpos,pos): Follow the path to the root, moving parents down until finding a place
heapq._siftup(heap,pos):Bubble up the smaller child until hitting a leaf
heapq._siftdown_max(heap,startpos,pos):Maxheap variant of _siftdown
heapq._siftup_max(heap,pos):Maxheap variant of _siftup
实例一
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 | import heapq def heapsort(iterable): h = [] for i in iterable: heapq.heappush(h, i) return [heapq.heappop(h) for i in range(len(h))] # method 1: sort to list s = [3, 5, 1, 2, 4, 6, 0, 1] print(heapsort(s)) ''' [0, 1, 1, 2, 3, 4, 5, 6] ''' # method 2: use key to find price_min portfolio = [{'name': 'IBM', 'shares': 100, 'price': 91.1}, {'name': 'AAPL', 'shares': 50, 'price': 543.22}, {'name': 'FB', 'shares': 200, 'price': 21.09}, {'name': 'HPQ', 'shares': 35, 'price': 31.75}, {'name': 'YHOO', 'shares': 45, 'price': 16.35}, {'name': 'ACME', 'shares': 75, 'price': 115.65}] cheap = heapq.nsmallest(1, portfolio, key=lambda s:s['price']) print(cheap) ''' [{'name': 'YHOO', 'shares': 45, 'price': 16.35}] ''' # method 3: use while to push min element def heapilize_list(x): n = len(x) # 获取存在子节点的节点 index 列表,并对每个节点单元进行最小堆处理 for i in reversed(range(n // 2)): raiseup_node(x, i) def put_down_node(heap, startpos, pos): current_item = heap[pos] # 判断单元中最小子节点与父节点的大小 while pos > startpos: parent_pos = (pos - 1) >> 1 parent_item = heap[parent_pos] if current_item < parent_item: heap[pos] = parent_item pos = parent_pos continue break heap[pos] = current_item def raiseup_node(heap, pos): heap_len = len(heap) start_pos = pos current_item = heap[pos] left_child_pos = pos * 2 + 1 while left_child_pos < heap_len: right_child_pos = left_child_pos + 1 # 将这个单元中的最小子节点元素与父节点元素进行位置调换 if right_child_pos < heap_len and not heap[left_child_pos] < heap[right_child_pos]: left_child_pos = right_child_pos heap[pos] = heap[left_child_pos] pos = left_child_pos left_child_pos = pos * 2 + 1 heap[pos] = current_item put_down_node(heap, start_pos, pos) p = [4, 6, 2, 10, 1] heapilize_list(p) print(p) ''' [1, 4, 2, 10, 6] ''' |
长被问到的面试题
2、搜索引擎会通过日志文件把用户每次检索使用的所有检索串都记录下来,每个查询串的长度为1-255字节。
假设目前有一千万个记录(这些查询串的重复度比较高,虽然总数是1千万,但如果除去重复后,不超过3百万个。一个查询串的重复度越高,说明查询它的用户越多,也就是越热门。),请你统计最热门的10个查询串,要求使用的内存不能超过1G。
典型的Top K算法,还是在这篇文章里头有所阐述,详情请参见:十一、从头到尾彻底解析Hash表算法。
文中,给出的最终算法是:
第一步、先对这批海量数据预处理,在O(N)的时间内用Hash表完成统计(之前写成了排序,特此订正。July、2011.04.27);
第二步、借助堆这个数据结构,找出Top K,时间复杂度为N‘logK。
即,借助堆结构,我们可以在log量级的时间内查找和调整/移动。因此,维护一个K(该题目中是10)大小的小根堆,然后遍历300万的Query,分别和根元素进行对比所以,我们最终的时间复杂度是:O(N) + N'*O(logK),(N为1000万,N’为300万)。ok,更多,详情,请参考原文。
或者:采用trie树,关键字域存该查询串出现的次数,没有出现为0。最后用10个元素的最小推来对出现频率进行排序。
我采用 trie 树 + 堆排序的实现方案
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 | #coding:utf-8 #py2.7 #trie树 class trie: def __init__(self): self.t={} def add(self, w): p = self.t for i in range(0, len(w)): p.setdefault(w[i], {}) p = p[w[i]] p.setdefault('', 0) p[''] += 1 def __iter__(self): return self.huisu(self.t, '') def huisu(self, dt, w): for k in dt.keys(): if k == '': yield w, dt[k] dt.pop(k) elif dt == {}: break else: nextstr = w + k for w, co in self.huisu(dt[k], nextstr): #调用递归中的发生器 yield w, co t = trie() t.add(u'我') t.add(u'我们') t.add(u'我家') t.add(u'我们家') t.add(u'我是') t.add(u'我们') t.add(u'我说的对') t.add(u'我想说') t.add(u'我想说') t.add(u'我想说') t.add(u'我方') print t.t print [dc for dc in t] |
代码中实现了两个重要的接口,一个是add 负责向trie树 添加 实例,另一个是对trie树的遍历的方法: __iter__。遍历方法采用回溯算法,其本质就是一个DFS算法。python 实现回溯算法用到了yield,注意递归 和yield 结合起来的方式。
下面是使用python 的堆排序返回了 TOP-K 的 搜索项:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | def topk(t, k): rs = {} ll = [] for w, c in t: #遍历trie树 if len(ll) == k: ll.append(c) rs.setdefault(c, []) rs[c].append(w) if len(ll) == k: heapq.heapify(ll) #收集前k项,并且进行堆排序 else: if c in ll: rs[c].append(w) continue pc = heapq.heappushpop(ll, c) #弹出频度最小的一项 if pc in rs.keys(): rs.pop(pc) #从结果集中剔除被弹出的搜索项 rs.setdefault(c, []) rs[c].append(w) return rs |
小窍门:heapq是一个最小堆, 如果要用最大堆的话,可以把所有 数值 都取相反数,就得到了最大堆。
trie 遍历的时间复杂度是O(n*le),查找一个词的时间复杂度是O(le),le是单词的平均长度,n是单词数量
一次K个元素 的堆排序的时间复杂度是 log(K),最多n次,所以堆排序总的时间复杂度为 O(n*log(K))
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 基于Microsoft.Extensions.AI核心库实现RAG应用
· 10年+ .NET Coder 心语 ── 封装的思维:从隐藏、稳定开始理解其本质意义
· 地球OL攻略 —— 某应届生求职总结
· 提示词工程——AI应用必不可少的技术
· Open-Sora 2.0 重磅开源!
· 周边上新:园子的第一款马克杯温暖上架