Misra-Gries 算法

Misra-Gries算法是一种在内存受限条件下,用于挖掘数据流中频繁项的近似算法。它通过一个大小为k的数组记录数据,低估了频繁项的实际数量,但确保输出的项均为频繁项。算法的空间复杂度为O(k(logm+logn)),其中k是计数器数量,m和n分别代表元素的最大频数和数据流的元素总数。尽管存在低估误差,但在Zipf法则下,该算法在大多数实际数据集上表现良好。

Misra-Gries 算法

参考
https://www.cnblogs.com/super-zhang-828/p/7353217.html

前言

Misra-Gries算法是频繁项挖掘中一个著名的算法。频繁项就是那些在数据流中出现频率最高的数据项。频繁项挖掘,这个看似简单的任务却是很多复杂算法的基础,同时也有着广泛的应用。

对于频繁项挖掘而言,一个简单的想法是,为所有的数据项分配计数器,当一个数据项到达,我们即增加相应计数器的值。但当数据流的规模较大时,出于内存的限制,我们往往不可能为每个数据项分配计数器。而Misra-Gries算法则是以一种清奇的思路解决了这个问题,实现了在内存受限的情况下,以较小的错误率统计数据流中的频繁项。

算法作者

Misra-Gries算法在1982年由华威大学的Misra和Gries提出

Misra-Gries算法

即使ϕ的值很大,解决这个问题的算法也至少要花费O(n)的空间。在这种情况下,一个错误率为ϵ的近似算法被提出。这就是我们的Misra-Gries算法。它的具体步骤如下:

首先建立一个大小为k的数组T。

对于数据流中依次到达的项i进行如下处理:如果项i在数组T中,则其对应的计数器ci++;如果项i不在数组T中,且数组T中的元素个数小于k−1,则将项i加入数组T,并为其分配计数器ci=1;其他情况,将数组T中所有元素的计数器减1,此时如果数组T中存在元素的计数器值为0,则从数组T移除这个元素。

当完成对数据流的扫描后,数据T中保存的k′(k′≤k−1)个元素即是数据流中的频繁项。
伪代码如下
在这里插入图片描述

Misra-gries算法分析

空间复杂度

如果用平衡二叉树存储key-value数组,其中key,也就是元素的值可以从[ n ] [n][n]中取,因此长度为⌈ log ⁡ n ⌉ \lceil \log n\rceil⌈logn⌉。value,也就是频数,最大不超过m mm,因此长度为⌈ log ⁡ n ⌉ \lceil \log n\rceil⌈logn⌉。最多存储k − 1 k-1k−1个这样的key-value对。因此空间复杂度为O ( k ( log ⁡ m + log ⁡ n ) ) O(k(\log m + \log n))O(k(logm+logn))。
————————————————
版权声明:本文为优快云博主「十三言」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.youkuaiyun.com/karroyan/article/details/110818901

其他讨论

公认无误的逻辑是,

① 运算逻辑是:不满就放入,如果满了就消掉一行,每个记录中的项都减一;

② Misra-Gries算法输出的数据项并不一定是频繁项,但是频繁项一定在输出结果之中。

③ 频繁项(频数>=n/k)要出现在最终的结果里

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.youkuaiyun.com/qq_40208392/article/details/112794548
———————————————— 版权声明:本文为优快云博主「Yuhui Wang」的原创文章,遵循CC 4.0
BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.youkuaiyun.com/qq_40208392/article/details/112794548

上面这位博主的想法我觉得有问题,这个近似算法的输出的数据项不应该是输出所有的频繁项,而应该是输出的项都是频繁项,而不能输出所有频繁项。
因为

你说说看,这个近似算法是高估了频繁元素的数量还是低估了?

小可:内存中的频繁元素的计数器不断地由于内存被占满而被削减,显然是低估了。

Mr.
王:没错,我们不能仅仅知道结果是低估了准确值,最好还要能根据算法分析出究竟低估了多少。想要知道低估了多少,我们首先要考虑的就是一个计数器被减小了几次。这就需要我们考虑到在整个算法的执行过程中,执行过多少个减少计数器的步骤。假如把整个结构的权重(也就是计数器的和)记作m’,整个数据流的权重(全部元素的数量)记作m。每当计数器需要降低时,由于内存中有k个计数器,我们也就减少了k个计数器,但是这时新到来的元素x并未计入内存中的计数器,它的到来只是标志着该削减计数器了,所以我们少加了k+1个计数器。因此,最多有个减少步骤。也就是说,估计和真实值最多相差。如果数据流的元素总量远大于值,我们可以得到一个好的频繁元素的估计。

可以看出,错误的界限是与k成反比的。你说说看,这说明什么?

小可:k 是内存中计数器的个数,也就是内存的大小,这说明内存越大,结果就越准确。

Mr. 王:嗯,这也是符合客观规律的。而且我们可以利用数据概要来计算错误的界限,只需要记录m、m’ 和k就可以了。

不过不难看出,如果数据集合中每个元素的数量都相差不多的话,这个算法求出的结果会具有很大的随机性,好在我们一般需要处理的数据都满足Zipf法则。

Zipf法则:典型的频率分布是高度偏斜的,只有少数频繁元素,最多10%的元素占元素总个数的90%。这个定律说明,只有少数的元素是大量重复出现的,而绝大多数元素的出现是不频繁的。

根据Zipf法则我们知道,频繁元素的种类只有少数,而其数量往往是非常大的,在算法执行的过程中,不断地削减内存中的计数器对于频繁元素最终被保留在内存里不会有太大程度的影响。

https://cloud.tencent.com/developer/article/1086891

作业

  1. 给定输入流< b, a, c, a, d, e, a, f, a, d>,计数器个数k = 3。请逐步写出Misra- Gries 算法执行的结果。
    我的答案(不保证对)
    i=b c_b=1 {b}
    i=a c_a=1 c_b=1 {b,a}
    i=c c_a=0 c_b=0 {}
    i=a c_a=1 {a}
    i=d c_d=1 c_a=1 {a,d}
    i=e c_a=0 c_d=0 {}
    i=a c_a=1 {a}
    i=f c_f=1 c_a=1 {f,a}
    i=a c_f=1 c_a=2 {f,a}
    i=d c_f=0 c_a=1 {a}
# 导入必要的库 import pandas as pd import numpy as np from collections import defaultdict import hashlib import matplotlib.pyplot as plt # ======================= # 读取数据 # ======================= # 请确保 athlete_events.csv 文件在当前目录下 df = pd.read_csv('athlete_events.csv') # 查看前几行数据 print("数据集前5行:") print(df.head()) # ======================= # 工具函数:字符串哈希映射到整数 # ======================= def hash_func(x, seed, w): """生成哈希值,返回 [0, w) 范围内的索引""" return int(hashlib.md5((str(seed) + str(x)).encode()).hexdigest(), 16) % w # ======================= # Misra-Gries 算法实现 # ======================= class MisraGries: def __init__(self, k): self.k = k self.freq_dict = {} def update(self, item): if item in self.freq_dict: self.freq_dict[item] += 1 elif len(self.freq_dict) < self.k - 1: self.freq_dict[item] = 1 else: # 减一并删除零值 keys_to_remove = [] for key in self.freq_dict: self.freq_dict[key] -= 1 if self.freq_dict[key] == 0: keys_to_remove.append(key) for key in keys_to_remove: del self.freq_dict[key] def estimate(self, item): return self.freq_dict.get(item, 0) # ======================= # Count-Min Sketch 实现 # ======================= class CountMinSketch: def __init__(self, k, w): self.k = k # 哈希函数数量 self.w = w # 每行宽度 self.table = np.zeros((k, w)) self.seeds = range(k) def update(self, item): for i in range(self.k): h = hash_func(item, self.seeds[i], self.w) self.table[i][h] += 1 def estimate(self, item): estimates = [] for i in range(self.k): h = hash_func(item, self.seeds[i], self.w) estimates.append(self.table[i][h]) return int(min(estimates)) # ======================= # 任务1:MG算法 - 游泳项目奖牌最多的国家 Top-3 # ======================= print("\n=== Misra-Gries 算法:游泳项目中获奖最多的国家 Top-3 ===") # 过滤游泳项目且有奖牌的记录 swim_medal = df[(df['Sport'] == 'Swimming') & df['Medal'].notna()] countries = swim_medal['Team'].tolist() # 测试不同k值 k_values = [10, 30, 60] mg_results = {} for k in k_values: mg = MisraGries(k) for country in countries: mg.update(country) # 排序取top sorted_items = sorted(mg.freq_dict.items(), key=lambda x: -x[1]) mg_results[k] = sorted_items[:3] print(f"k={k}: {sorted_items[:3]}") # ======================= # 任务2:男女参与最多的运动项目 # ======================= print("\n=== 男/女运动员参与最多的运动项目(真实统计) ===") male_sports = df[df['Sex'] == 'M']['Sport'] female_sports = df[df['Sex'] == 'F']['Sport'] top_male = male_sports.value_counts().head(1) top_female = female_sports.value_counts().head(1) print(f"男性最多参与项目: {top_male.index[0]} ({top_male.values[0]}次)") print(f"女性最多参与项目: {top_female.index[0]} ({top_female.values[0]}次)") # 使用 MG 算法验证(以 k=30 为例) mg_male = MisraGries(30) mg_female = MisraGries(30) for sport in male_sports: mg_male.update(sport) for sport in female_sports: mg_female.update(sport) mg_top_male = sorted(mg_male.freq_dict.items(), key=lambda x: -x[1])[0] mg_top_female = sorted(mg_female.freq_dict.items(), key=lambda x: -x[1])[0] print(f"MG算法估计 - 男性最多: {mg_top_male}") print(f"MG算法估计 - 女性最多: {mg_top_female}") # ======================= # 任务3:CM Sketch 算法对比 # ======================= print("\n=== Count-Min Sketch 算法:游泳项目国家 Top-3 估计 ===") cms = CountMinSketch(k=5, w=1000) # 5个哈希函数,宽度1000 for country in swim_medal['Team']: cms.update(country) # 获取所有候选国的估计频率 candidates = swim_medal['Team'].unique() estimates = [(c, cms.estimate(c)) for c in candidates] cm_top3 = sorted(estimates, key=lambda x: -x[1])[:3] print(f"CM Sketch Top-3: {cm_top3}") # ======================= # 复杂度与精度比较 # ======================= print("\n=== 算法比较 ===") print("Misra-Gries:") print(" 时间复杂度: O(n)") print(" 空间复杂度: O(k)") print(" 精度: 无偏上界,可能漏掉低频项") print("Count-Min Sketch:") print(" 时间复杂度: O(n*k)") print(" 空间复杂度: O(k*w)") print(" 精度: 可能高估(因哈希冲突)") 详细解释代码
最新发布
12-01
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值