目录
[1. 核心定位](#1. 核心定位)
[2. 与 Apriori 的核心对比(突出 FP-growth 优势)](#2. 与 Apriori 的核心对比(突出 FP-growth 优势))
[3. FP-growth 的核心设计思想](#3. FP-growth 的核心设计思想)
[1. 基础概念(和 Apriori 一致,回顾)](#1. 基础概念(和 Apriori 一致,回顾))
[2. FP-growth 专属核心概念(重点)](#2. FP-growth 专属核心概念(重点))
[(1)FP 树(Frequent Pattern Tree,频繁模式树)](#(1)FP 树(Frequent Pattern Tree,频繁模式树))
[(2)项头表(Header Table)](#(2)项头表(Header Table))
[(3)条件模式基(Conditional Pattern Base)](#(3)条件模式基(Conditional Pattern Base))
[(4)条件 FP 树(Conditional FP Tree)](#(4)条件 FP 树(Conditional FP Tree))
[四、FP-growth 完整算法步骤](#四、FP-growth 完整算法步骤)
[阶段 1:构建 FP 树(扫描数据库 2 次,核心是 "压缩")](#阶段 1:构建 FP 树(扫描数据库 2 次,核心是 “压缩”))
[步骤 1:第一次扫描事务数据库,统计 1 - 项集的支持度](#步骤 1:第一次扫描事务数据库,统计 1 - 项集的支持度)
[步骤 2:筛选频繁 1 - 项集,按支持度降序排序](#步骤 2:筛选频繁 1 - 项集,按支持度降序排序)
[步骤 3:第二次扫描事务数据库,预处理每条事务](#步骤 3:第二次扫描事务数据库,预处理每条事务)
[步骤 4:构建 FP 树和项头表](#步骤 4:构建 FP 树和项头表)
[阶段 2:从 FP 树中挖掘所有频繁项集(自底向上,递归挖掘)](#阶段 2:从 FP 树中挖掘所有频繁项集(自底向上,递归挖掘))
[步骤 1:初始化频繁项集集合](#步骤 1:初始化频繁项集集合)
[步骤 2:自底向上遍历项头表的每个频繁项](#步骤 2:自底向上遍历项头表的每个频繁项)
[步骤 3:终止递归,输出所有频繁项集](#步骤 3:终止递归,输出所有频繁项集)
[关键优化:单路径条件 FP 树的枚举法](#关键优化:单路径条件 FP 树的枚举法)
[阶段 1:构建 FP 树(扫描 2 次数据库)](#阶段 1:构建 FP 树(扫描 2 次数据库))
[步骤 1:第一次扫描,统计 1 - 项集支持数](#步骤 1:第一次扫描,统计 1 - 项集支持数)
[步骤 2:筛选频繁 1 - 项集,降序排序](#步骤 2:筛选频繁 1 - 项集,降序排序)
[步骤 3:第二次扫描,预处理每条事务](#步骤 3:第二次扫描,预处理每条事务)
[步骤 4:构建 FP 树和项头表](#步骤 4:构建 FP 树和项头表)
[阶段 2:挖掘所有频繁项集(自底向上递归)](#阶段 2:挖掘所有频繁项集(自底向上递归))
[步骤 1:处理尿布(支持数 4)](#步骤 1:处理尿布(支持数 4))
[步骤 2:处理啤酒(支持数 5)](#步骤 2:处理啤酒(支持数 5))
[步骤 3:处理面包(支持数 5)](#步骤 3:处理面包(支持数 5))
[步骤 4:处理牛奶(支持数 6)](#步骤 4:处理牛奶(支持数 6))
[六、FP-growth 算法的 Python 代码实现思路](#六、FP-growth 算法的 Python 代码实现思路)
[1. 定义 FP 树节点类](#1. 定义 FP 树节点类)
[2. 构建 FP 树和项头表函数](#2. 构建 FP 树和项头表函数)
[3. 递归挖掘频繁项集函数](#3. 递归挖掘频繁项集函数)
[4. 测试代码(结合上述实例)](#4. 测试代码(结合上述实例))
[七、FP-growth 算法的Python代码完整实现](#七、FP-growth 算法的Python代码完整实现)
[九、FP-growth 算法的优缺点](#九、FP-growth 算法的优缺点)
[十、FP-growth 算法的应用场景](#十、FP-growth 算法的应用场景)
一、引言
FP-growth(Frequent Pattern Growth,频繁模式增长)是由韩家炜(Jiawei Han)等人在 2000 年提出的频繁项集挖掘经典算法,是关联规则挖掘的核心算法之一(关联规则挖掘分两步:先挖频繁项集,再由频繁项集生成关联规则)。
该算法针对 Apriori 算法的核心缺陷做了颠覆性优化 ------无需生成大量候选项集、仅扫描事务数据库两次 ,通过构建FP 树(频繁模式树) 压缩存储事务中频繁项的关联信息,再基于 FP 树自底向上递归挖掘所有频繁项集,在海量数据场景下效率远高于 Apriori。
本文会从算法核心定位与优势 、核心概念 、完整算法步骤 、经典实例演示 、代码实现思路 、优缺点 、应用场景七个维度讲解,全程结合购物篮分析场景,兼顾理论和工程落地,同时与 Apriori 算法进行对比。
二、算法核心定位与核心优势
1. 核心定位
FP-growth 是无候选集、基于树结构 的频繁项集挖掘算法,核心目标和 Apriori 一致:在给定的事务数据库中,挖掘出支持度≥最小支持度的所有频繁项集(单个项、多个项的组合),为后续生成关联规则(如 "啤酒→尿布")提供基础。
2. 与 Apriori 的核心对比(突出 FP-growth 优势)
Apriori 算法是频繁项集挖掘的入门算法,但存在两大致命缺陷,也是 FP-growth 要解决的问题:
| 特性 | Apriori 算法 | FP-growth 算法 |
|---|---|---|
| 数据库扫描次数 | 多次扫描(挖掘 k - 项集需扫描 k 次) | 仅扫描两次(全程) |
| 候选项集生成 | 生成大量候选项集(如由 2 - 项集生成 3 - 项集),需频繁剪枝(剔除非频繁项) | 不生成任何候选项集,直接从 FP 树中挖掘 |
| 效率 | 低(小数据可用,大数据下因多次扫描、候选集过多急剧变慢) | 高(FP 树压缩存储数据,递归挖掘无冗余操作,适合海量事务) |
| 空间复杂度 | 高(需存储大量候选项集和其支持度) | 低(FP 树仅存储频繁项的关联信息,压缩比高) |
| 实现难度 | 简单(逻辑直观,双层循环即可实现) | 稍复杂(需维护 FP 树的节点链接、递归挖掘) |
3. FP-growth 的核心设计思想
- 压缩存储 :用 FP 树将事务数据库中所有频繁项 的关联信息压缩成一棵带链接的前缀树,树中每个节点记录项名、出现次数,同时用项头表维护同一项的所有节点链接,保证能快速追溯;
- 自底向上挖掘 :利用 "频繁项集的所有非空子集都是频繁的 " 这一性质(和 Apriori 相同),从项头表中支持度最低的频繁项 开始,自底向上生成条件模式基 ,构建条件 FP 树,递归挖掘包含当前项的所有频繁项集;
- 单路径优化:若某棵条件 FP 树只有一条路径,直接枚举路径上所有项的组合即可,无需递归,大幅提升挖掘效率。
三、算法核心概念
FP-growth 的所有步骤都依赖以下核心概念,先把这些概念理解透,后续步骤会迎刃而解,所有概念均以购物篮事务数据库为背景讲解:
1. 基础概念(和 Apriori 一致,回顾)
- 事务数据库 :所有购物篮的集合,每个元素是一个事务(一次购物的商品集合),记为D;
- 项:事务中的单个元素(如啤酒、尿布、牛奶),记为item;
- 项集 :单个或多个项的无序组合(如 {啤酒}、{啤酒,尿布}),k 个项的组合称为k - 项集;
- 支持度(support) :某项目集在事务数据库中出现的频率,公式:
,例:{啤酒,尿布} 在 100 条事务中出现 20 次,支持度 = 20/100=0.2; - 最小支持度(min_support) :人工设定的阈值(如 0.2),支持度≥该阈值的项集为频繁项集 ,否则为非频繁项集;
- 频繁 1 - 项集:仅包含单个项的频繁项集(如 {啤酒}、{尿布}),是构建 FP 树的基础。
2. FP-growth 专属核心概念(重点)
(1)FP 树(Frequent Pattern Tree,频繁模式树)
FP 树是一棵带节点链接的前缀树 ,是 FP-growth 算法的核心数据结构,作用是压缩存储事务数据库中所有频繁项的关联信息,保留所有频繁项的出现次数和项间关联关系,同时剔除所有非频繁项(无意义数据)。
FP 树的结构要求:
- 包含一个根节点 (记为
NULL,无项名、计数为 0); - 每个非根节点包含 4 个属性:
项名(item_name)、计数(count)(该节点代表的项在当前路径上出现的次数)、父节点(parent)(指向父节点,根节点无父节点)、子节点(children)(字典,键为项名,值为子节点对象); - 树的路径对应事务中的频繁项序列(按支持度降序排列),路径上的节点组合代表一个项集;
- 同一项的所有节点通过同项链接(next_node) 相连,方便后续快速追溯。
(2)项头表(Header Table)
项头表是按支持度降序排列的频繁 1 - 项集列表,是挖掘 FP 树的 "入口",每个条目包含两个属性:
- 频繁项名:如啤酒、尿布、牛奶;
- 支持度:该频繁 1 - 项集的支持度;
- 链接指针 :指向 FP 树中该频繁项的第一个节点 ,通过该指针可遍历 FP 树中该频繁项的所有节点(借助节点的
next_node)。
(3)条件模式基(Conditional Pattern Base)
针对项头表中的某一个频繁项x,条件模式基 是指:从 FP 树中找到x的所有节点,向上追溯到根节点(不包含根节点和x本身) ,得到的所有前缀路径集合,且每条前缀路径的计数等于x对应节点的计数。
简单说:条件模式基是 "包含x的所有事务的前缀(剔除x后)",是构建条件 FP 树的基础。
(4)条件 FP 树(Conditional FP Tree)
以某个频繁项x的条件模式基 为新的 "事务数据库",重新构建的 FP 树就是x的条件 FP 树,作用是挖掘所有包含x的频繁项集。
条件 FP 树的构建规则和原始 FP 树完全一致,若条件模式基为空,则无对应的条件 FP 树,也说明没有包含x的高阶频繁项集(仅x自身是频繁 1 - 项集)。
四、FP-growth 完整算法步骤
FP-growth 算法分为两大核心阶段 ,全程仅扫描事务数据库两次,无候选项集生成,所有操作围绕 "FP 树" 展开,阶段 1 是构建 FP 树,阶段 2 是从 FP 树中挖掘所有频繁项集,步骤清晰且无冗余。
前置条件
给定事务数据库D 、最小支持度 min_support(或最小支持数,即包含项集的最少事务数,更易计算),目标是挖掘所有频繁项集。
阶段 1:构建 FP 树(扫描数据库 2 次,核心是 "压缩")
这一阶段的目标是将原始事务数据库压缩成FP 树 + 项头表,剔除所有非频繁项,保留频繁项的关联信息,是 FP-growth 的基础,共分 4 步:
步骤 1:第一次扫描事务数据库,统计 1 - 项集的支持度
遍历数据库中的每一条事务,统计每个单个项(1 - 项集)出现的次数(支持数),得到所有 1 - 项集的支持度统计结果。
例:扫描 100 条购物篮事务,统计得到 {啤酒}:30 次,{尿布}:25 次,{牛奶}:20 次,{可乐}:5 次,{鸡蛋}:3 次。
步骤 2:筛选频繁 1 - 项集,按支持度降序排序
根据最小支持度(或最小支持数),剔除非频繁 1 - 项集 (支持度 < min_support),保留频繁 1 - 项集;将保留的频繁 1 - 项集按支持度从高到低 排序,得到排序列表 L(后续构建 FP 树的项顺序依据)。
例:设最小支持数 = 10(即至少出现在 10 条事务中),则 {可乐}、{鸡蛋} 为非频繁项,剔除;排序列表 L=[啤酒 (30), 尿布 (25), 牛奶 (20)]。
步骤 3:第二次扫描事务数据库,预处理每条事务
对数据库中的每一条事务执行以下操作:
- 剔除非频繁项:删除事务中属于非频繁 1 - 项集的项(无意义,不参与 FP 树构建);
- 按排序列表 L 排序:将事务中剩余的频繁项,按照步骤 2 得到的排序列表 L 的顺序重新排序(保证 FP 树的前缀一致性,便于压缩);
- 空事务过滤:若预处理后的事务无任何项,直接丢弃。
例:某条事务为 {啤酒,可乐,尿布},预处理后剔除 {可乐},按 L 排序后为 [啤酒,尿布];某条事务为 {可乐,鸡蛋},预处理后无项,丢弃。
步骤 4:构建 FP 树和项头表
初始化一棵空的 FP 树(仅含根节点NULL),初始化空的项头表;遍历步骤 3 预处理后的每一条事务序列 ,将其插入 FP 树,插入过程中更新节点计数、子节点、父节点和同项链接,同时更新项头表:
- 插入规则 :从根节点开始,依次遍历事务序列中的每个项,若当前节点的子节点中包含该项,则子节点计数 + 1 ;若不包含,则新建一个节点(设置项名、计数 = 1、父节点为当前节点),并将新节点加入当前节点的子节点字典;
- 同项链接维护 :若项头表中已存在该项,则将新节点的
next_node指向项头表中该项的最后一个同项节点,同时更新项头表的链接指针为新节点;若项头表中无该项,则在项头表中添加该条目(项名、支持度、链接指针指向新节点); - 项头表更新:项头表的支持度为该频繁项在所有事务中的总支持数(步骤 1 的统计结果),仅维护链接指针即可。
核心:插入所有事务后,得到一棵完整的 FP 树,以及带同项链接的项头表,原始事务数据库的频繁项关联信息已全部压缩到其中。
阶段 2:从 FP 树中挖掘所有频繁项集(自底向上,递归挖掘)
这一阶段是 FP-growth 的核心,目标是从阶段 1 构建的 FP 树 + 项头表中,挖掘出所有 支持度≥min_support 的频繁项集(1 - 项集、2 - 项集、3 - 项集......),核心思路是自底向上处理项头表,递归生成条件模式基→构建条件 FP 树→挖掘频繁项集,共分 3 步,利用 "频繁项集的子集必频繁" 的性质,无冗余挖掘。
步骤 1:初始化频繁项集集合
将项头表中的所有频繁 1 - 项集加入频繁项集集合(这些是基础频繁项集)。
步骤 2:自底向上遍历项头表的每个频繁项
从项头表中支持度最低的频繁项开始,自底向上处理每个频繁项x(原因:支持度越低的项,对应的高阶频繁项集越少,递归次数越少,效率越高),对每个x执行以下操作:
- 生成x的条件模式基 :通过项头表中x的链接指针,遍历 FP 树中所有x的节点;对每个x节点,向上追溯到根节点 ,得到该节点的前缀路径(剔除x本身),前缀路径的计数等于x节点的计数;所有x节点的前缀路径 + 计数,即为x的条件模式基;
- 构建x的条件 FP 树 :以x的条件模式基为新的 "事务数据库",重复阶段 1 的步骤 3-4(预处理、插入树),构建x的条件 FP 树和条件项头表;若条件模式基为空,则跳过后续步骤(无包含x的高阶频繁项集);
- 递归挖掘条件 FP 树的频繁项集 :对x的条件 FP 树,递归执行阶段 2 的所有步骤,挖掘出该条件 FP 树中的所有频繁项集,记为F;
- 组合生成包含x的频繁项集 :将x与F中的每个频繁项集进行组合(如F中有 {啤酒},则组合为 {啤酒,x}),将组合后的项集加入全局频繁项集集合(这些是包含x的高阶频繁项集)。
步骤 3:终止递归,输出所有频繁项集
当遍历完项头表的所有频繁项,且所有递归调用结束后,全局频繁项集集合中包含了所有支持度≥min_support 的频繁项集,挖掘完成。
关键优化:单路径条件 FP 树的枚举法
若某一条件 FP 树只有一条路径 (从根节点到叶子节点只有一条唯一路径),则无需递归挖掘,直接枚举该路径上所有项的非空组合,这些组合都是频繁项集(因为路径上的所有项的组合的支持度等于路径中最小的节点计数),大幅减少递归次数,提升效率。
例:条件 FP 树的路径为 [啤酒→尿布→牛奶],则直接枚举 {啤酒}、{尿布}、{牛奶}、{啤酒,尿布}、{啤酒,牛奶}、{尿布,牛奶}、{啤酒,尿布,牛奶},均为该条件 FP 树的频繁项集。
五、经典实例演示(购物篮场景,一步一步走)
用一个小型购物篮事务数据库演示 FP-growth 的完整步骤,让抽象的理论变具体,所有步骤可手动验证,理解后就能推及海量数据。
前置条件
-
事务数据库D :共 8 条购物篮事务,每条事务是一次购物的商品集合,记为 T1-T8:
事务 ID 商品集合(项) T1 牛奶、面包、啤酒 T2 牛奶、尿布 T3 牛奶、面包、尿布 T4 啤酒、尿布 T5 牛奶、面包、啤酒、尿布 T6 牛奶、面包 T7 啤酒、面包 T8 牛奶、啤酒 -
最小支持数:设为 3(即至少出现在 3 条事务中的项集为频繁项集,对应支持度 = 3/8=0.375);
-
目标:挖掘所有频繁项集。
阶段 1:构建 FP 树(扫描 2 次数据库)
步骤 1:第一次扫描,统计 1 - 项集支持数
遍历 8 条事务,统计每个商品的出现次数:
- 牛奶:T1、T2、T3、T5、T6、T8 → 6 次
- 面包:T1、T3、T5、T6、T7 → 5 次
- 啤酒:T1、T4、T5、T7、T8 → 5 次
- 尿布:T2、T3、T4、T5 → 4 次
步骤 2:筛选频繁 1 - 项集,降序排序
最小支持数 = 3,所有项的支持数均≥3,均为频繁 1 - 项集;按支持数降序排序,得到排序列表 L:牛奶面包啤酒尿布
步骤 3:第二次扫描,预处理每条事务
对每条事务剔除非频繁项(本例无),按 L 的顺序重新排序,预处理结果:
| 事务 ID | 原始商品集合 | 预处理后序列(按 L 排序) |
|---|---|---|
| T1 | 牛奶、面包、啤酒 | 牛奶、面包、啤酒 |
| T2 | 牛奶、尿布 | 牛奶、尿布 |
| T3 | 牛奶、面包、尿布 | 牛奶、面包、尿布 |
| T4 | 啤酒、尿布 | 啤酒、尿布(无牛奶 / 面包) |
| T5 | 牛奶、面包、啤酒、尿布 | 牛奶、面包、啤酒、尿布 |
| T6 | 牛奶、面包 | 牛奶、面包 |
| T7 | 啤酒、面包 | 面包、啤酒(无牛奶 / 尿布) |
| T8 | 牛奶、啤酒 | 牛奶、啤酒 |
步骤 4:构建 FP 树和项头表
从根节点NULL开始,依次插入预处理后的 8 条序列,最终得到的FP 树 和项头表如下(文字简化描述):
-
项头表 (按支持数降序,带链接指针):
频繁项 支持数 链接指针指向 FP 树的第一个节点 牛奶 6 根节点→牛奶(计数 6) 面包 5 根节点→牛奶→面包(计数 4) 啤酒 5 根节点→牛奶→面包→啤酒(计数 2) 尿布 4 根节点→牛奶→尿布(计数 1) -
FP 树核心结构 (关键路径 + 节点计数):
- 根→牛奶(6):分支 1→面包(4)→啤酒(2)→尿布(1);分支 2→面包(4)→尿布(1);分支 3→面包(4);分支 4→尿布(1);分支 5→啤酒(1);
- 根→面包(1)→啤酒(1);
- 根→啤酒(1)→尿布(1);(所有同项节点通过
next_node链接,如所有 "尿布" 节点互相链接)。
阶段 2:挖掘所有频繁项集(自底向上递归)
从项头表最底部的尿布开始,自底向上处理牛奶、面包、啤酒、尿布(实际顺序:尿布→啤酒→面包→牛奶),逐步挖掘:
步骤 1:处理尿布(支持数 4)
- 生成条件模式基:遍历 FP 树中所有尿布节点,向上追溯前缀路径,得到条件模式基为 {[牛奶,面包,啤酒]:1, [牛奶,面包]:1, [牛奶]:1, [啤酒]:1}(计数分别为 1,总支持数 4);
- 构建条件 FP 树:以条件模式基为新数据库,最小支持数 3,筛选后无频繁项(所有前缀路径的计数均 < 3),条件 FP 树为空;
- 无递归挖掘,仅将 {尿布} 加入频繁项集集合。
步骤 2:处理啤酒(支持数 5)
- 生成条件模式基:前缀路径为 {[牛奶,面包]:2, [牛奶]:1, [面包]:1}(计数分别为 2、1、1,总支持数 5);
- 构建条件 FP 树:筛选出频繁项 [牛奶 (3), 面包 (3)],构建条件 FP 树为根→牛奶(3)→面包(2);
- 递归挖掘条件 FP 树,得到频繁项集 {牛奶}、{面包}、{牛奶,面包};
- 组合生成包含啤酒的频繁项集:{啤酒,牛奶}、{啤酒,面包}、{啤酒,牛奶,面包},加入集合;
- 基础频繁项集 {啤酒} 加入集合。
步骤 3:处理面包(支持数 5)
- 生成条件模式基:前缀路径为 {[牛奶]:4}(计数 4,总支持数 5);
- 构建条件 FP 树:频繁项 {牛奶 (4)},条件 FP 树为根→牛奶(4);
- 递归挖掘得到频繁项集 {牛奶};
- 组合生成包含面包的频繁项集:{面包,牛奶},加入集合;
- 基础频繁项集 {面包} 加入集合。
步骤 4:处理牛奶(支持数 6)
- 生成条件模式基:无(牛奶是项头表第一个项,无前缀路径);
- 仅将基础频繁项集 {牛奶} 加入集合。
最终挖掘结果(所有频繁项集)
支持数≥3 的所有频繁项集如下(包含 1 - 项集、2 - 项集、3 - 项集):
bash
{牛奶}, {面包}, {啤酒}, {尿布},
{牛奶,面包}, {牛奶,啤酒}, {面包,啤酒},
{牛奶,面包,啤酒}
后续可基于这些频繁项集,设置最小置信度,生成关联规则(如 {啤酒,面包}→{牛奶},需计算置信度和提升度)。
六、FP-growth 算法的 Python 代码实现思路
结合上述步骤,FP-growth 的 Python 代码实现主要分为3 个核心部分 :定义 FP 树节点类 、FP 树 + 项头表构建函数 、频繁项集挖掘函数(递归),代码结构清晰,以下是落地思路(可直接基于此实现,兼顾可读性和效率):
1. 定义 FP 树节点类
用类封装 FP 树的节点属性,包含项名、计数、父节点、子节点、同项链接,是构建 FP 树的基础:
python
class FPNode:
def __init__(self, item_name, count, parent):
self.item_name = item_name # 项名
self.count = count # 节点计数
self.parent = parent # 父节点
self.children = {} # 子节点:{item_name: FPNode对象}
self.next_node = None # 同项链接:指向下一个同项节点
2. 构建 FP 树和项头表函数
实现阶段 1 的所有步骤 ,输入事务数据库和最小支持数,输出构建好的 FP 树根节点 和项头表(字典形式,{item_name: [支持数,第一个节点]}):
python
def build_fp_tree(transactions, min_support):
# 步骤1:第一次扫描,统计1-项集支持数
item_count = {}
for trans in transactions:
for item in trans:
item_count[item] = item_count.get(item, 0) + 1
# 步骤2:筛选频繁1-项集,按支持数降序排序
frequent_items = {k: v for k, v in item_count.items() if v >= min_support}
if not frequent_items:
return None, {} # 无频繁项,返回空
# 按支持数降序排序
frequent_items_sorted = sorted(frequent_items.items(), key=lambda x: x[1], reverse=True)
frequent_items_list = [item for item, _ in frequent_items_sorted]
# 构建项头表初始版
header_table = {item: [count, None] for item, count in frequent_items_sorted}
# 步骤3:第二次扫描,预处理每条事务
processed_trans = []
for trans in transactions:
# 剔除非频繁项,按frequent_items_list排序
trans_filtered = [item for item in trans if item in frequent_items]
trans_sorted = sorted(trans_filtered, key=lambda x: frequent_items_list.index(x))
if trans_sorted:
processed_trans.append(trans_sorted)
# 步骤4:构建FP树
root = FPNode("NULL", 0, None)
for trans in processed_trans:
current_node = root
for item in trans:
# 若子节点存在,计数+1
if item in current_node.children:
current_node.children[item].count += 1
# 若子节点不存在,新建节点
else:
new_node = FPNode(item, 1, current_node)
current_node.children[item] = new_node
# 更新同项链接和项头表
if header_table[item][1] is None:
header_table[item][1] = new_node
else:
# 找到同项链接的最后一个节点
temp_node = header_table[item][1]
while temp_node.next_node:
temp_node = temp_node.next_node
temp_node.next_node = new_node
# 移动到子节点
current_node = current_node.children[item]
return root, header_table
3. 递归挖掘频繁项集函数
实现阶段 2 的所有步骤 ,输入 FP 树根节点、项头表、最小支持数、当前项集,输出所有频繁项集(字典形式,{项集:支持数}):
python
def mine_fp_tree(root, header_table, min_support, current_set, frequent_itemsets):
# 自底向上遍历项头表
sorted_items = sorted(header_table.items(), key=lambda x: x[1][0]) # 按支持数升序
for item, (count, first_node) in sorted_items:
# 组合生成当前项集
new_set = current_set.copy()
new_set.add(item)
frequent_itemsets[frozenset(new_set)] = count # frozenset可哈希,作为字典键
# 生成条件模式基
pattern_bases = []
temp_node = first_node
while temp_node:
# 向上追溯前缀路径
prefix_path = []
parent_node = temp_node.parent
while parent_node.item_name != "NULL":
prefix_path.append(parent_node.item_name)
parent_node = parent_node.parent
if prefix_path:
pattern_bases.extend([prefix_path] * temp_node.count)
temp_node = temp_node.next_node
# 构建条件FP树
cond_root, cond_header = build_fp_tree(pattern_bases, min_support)
# 递归挖掘条件FP树
if cond_header:
mine_fp_tree(cond_root, cond_header, min_support, new_set, frequent_itemsets)
return frequent_itemsets
# 封装主函数
def fp_growth(transactions, min_support):
root, header_table = build_fp_tree(transactions, min_support)
if not root:
return {}
frequent_itemsets = {}
mine_fp_tree(root, header_table, min_support, set(), frequent_itemsets)
return frequent_itemsets
4. 测试代码(结合上述实例)
python
if __name__ == "__main__":
# 上述实例的事务数据库
transactions = [
["牛奶", "面包", "啤酒"],
["牛奶", "尿布"],
["牛奶", "面包", "尿布"],
["啤酒", "尿布"],
["牛奶", "面包", "啤酒", "尿布"],
["牛奶", "面包"],
["啤酒", "面包"],
["牛奶", "啤酒"]
]
min_support = 3 # 最小支持数
frequent_itemsets = fp_growth(transactions, min_support)
# 打印结果
for itemset, count in frequent_itemsets.items():
print(f"项集:{set(itemset)},支持数:{count}")
七、FP-growth 算法的Python代码完整实现
python
class FPNode:
def __init__(self, item_name, count, parent):
self.item_name = item_name # 项名
self.count = count # 节点计数
self.parent = parent # 父节点
self.children = {} # 子节点:{item_name: FPNode对象}
self.next_node = None # 同项链接:指向下一个同项节点
def build_fp_tree(transactions, min_support):
# 步骤1:第一次扫描,统计1-项集支持数
item_count = {}
for trans in transactions:
for item in trans:
item_count[item] = item_count.get(item, 0) + 1
# 步骤2:筛选频繁1-项集,按支持数降序排序
frequent_items = {k: v for k, v in item_count.items() if v >= min_support}
if not frequent_items:
return None, {} # 无频繁项,返回空
# 按支持数降序排序
frequent_items_sorted = sorted(frequent_items.items(), key=lambda x: x[1], reverse=True)
frequent_items_list = [item for item, _ in frequent_items_sorted]
# 构建项头表初始版
header_table = {item: [count, None] for item, count in frequent_items_sorted}
# 步骤3:第二次扫描,预处理每条事务
processed_trans = []
for trans in transactions:
# 剔除非频繁项,按frequent_items_list排序
trans_filtered = [item for item in trans if item in frequent_items]
trans_sorted = sorted(trans_filtered, key=lambda x: frequent_items_list.index(x))
if trans_sorted:
processed_trans.append(trans_sorted)
# 步骤4:构建FP树
root = FPNode("NULL", 0, None)
for trans in processed_trans:
current_node = root
for item in trans:
# 若子节点存在,计数+1
if item in current_node.children:
current_node.children[item].count += 1
# 若子节点不存在,新建节点
else:
new_node = FPNode(item, 1, current_node)
current_node.children[item] = new_node
# 更新同项链接和项头表
if header_table[item][1] is None:
header_table[item][1] = new_node
else:
# 找到同项链接的最后一个节点
temp_node = header_table[item][1]
while temp_node.next_node:
temp_node = temp_node.next_node
temp_node.next_node = new_node
# 移动到子节点
current_node = current_node.children[item]
return root, header_table
def mine_fp_tree(root, header_table, min_support, current_set, frequent_itemsets):
# 自底向上遍历项头表
sorted_items = sorted(header_table.items(), key=lambda x: x[1][0]) # 按支持数升序
for item, (count, first_node) in sorted_items:
# 组合生成当前项集
new_set = current_set.copy()
new_set.add(item)
frequent_itemsets[frozenset(new_set)] = count # frozenset可哈希,作为字典键
# 生成条件模式基
pattern_bases = []
temp_node = first_node
while temp_node:
# 向上追溯前缀路径
prefix_path = []
parent_node = temp_node.parent
while parent_node.item_name != "NULL":
prefix_path.append(parent_node.item_name)
parent_node = parent_node.parent
if prefix_path:
pattern_bases.extend([prefix_path] * temp_node.count)
temp_node = temp_node.next_node
# 构建条件FP树
cond_root, cond_header = build_fp_tree(pattern_bases, min_support)
# 递归挖掘条件FP树
if cond_header:
mine_fp_tree(cond_root, cond_header, min_support, new_set, frequent_itemsets)
return frequent_itemsets
# 封装主函数
def fp_growth(transactions, min_support):
root, header_table = build_fp_tree(transactions, min_support)
if not root:
return {}
frequent_itemsets = {}
mine_fp_tree(root, header_table, min_support, set(), frequent_itemsets)
return frequent_itemsets
if __name__ == "__main__":
# 上述实例的事务数据库
transactions = [
["牛奶", "面包", "啤酒"],
["牛奶", "尿布"],
["牛奶", "面包", "尿布"],
["啤酒", "尿布"],
["牛奶", "面包", "啤酒", "尿布"],
["牛奶", "面包"],
["啤酒", "面包"],
["牛奶", "啤酒"]
]
min_support = 3 # 最小支持数
frequent_itemsets = fp_growth(transactions, min_support)
# 打印结果
for itemset, count in frequent_itemsets.items():
print(f"项集:{set(itemset)},支持数:{count}")
八、程序运行结果展示
项集:{'尿布'},支持数:4
项集:{'牛奶', '尿布'},支持数:3
项集:{'面包'},支持数:5
项集:{'牛奶', '面包'},支持数:4
项集:{'啤酒'},支持数:5
项集:{'啤酒', '面包'},支持数:3
项集:{'牛奶', '啤酒'},支持数:3
项集:{'牛奶'},支持数:6
九、FP-growth 算法的优缺点
优点
- 效率极高:仅扫描事务数据库两次,无候选项集生成,避免了 Apriori 的多次扫描和候选集剪枝开销,海量数据下优势明显;
- 空间复杂度低:FP 树压缩存储频繁项的关联信息,相比 Apriori 存储大量候选项集,内存占用大幅减少;
- 挖掘完整:能挖掘出所有支持度≥最小支持度的频繁项集,无遗漏;
- 单路径优化:对单路径条件 FP 树直接枚举,减少递归次数,进一步提升效率;
- 适配离散型数据:完美适配购物篮、文本词频、用户行为等离散型数据的频繁模式挖掘。
缺点
- 内存依赖 :FP 树需要一次性加载到内存,若事务数据库极大(超过内存容量),FP 树构建会失败,效率急剧下降(可通过分块 FP-growth 优化);
- 动态数据不友好 :若事务数据库更新(新增 / 删除事务),需要重新构建整个 FP 树,无法增量更新,适合静态数据挖掘;
- 实现复杂度较高:相比 Apriori 的双层循环,FP-growth 需要维护 FP 树的节点链接、递归挖掘,代码实现更复杂;
- 不适合低支持度场景:若最小支持度过低,频繁项数量会急剧增加,FP 树会变得庞大,挖掘效率下降(和 Apriori 一致)。
十、FP-growth 算法的应用场景
FP-growth 是目前工业界最常用的频繁项集挖掘算法 ,广泛应用于需要挖掘 "项间关联模式" 的场景,核心还是关联规则挖掘,和你之前的购物篮分析高度相关,主要应用场景包括:
- 零售 / 电商购物篮分析:挖掘商品间的频繁组合(如 {啤酒,尿布}、{面包,牛奶}),用于商品推荐、货架摆放、捆绑销售;
- 网页访问模式挖掘:挖掘用户频繁访问的网页组合(如 {首页→商品列表→详情页}),用于网站
十一、总结
FP-growth是一种高效的频繁项集挖掘算法,相比Apriori算法具有显著优势。它通过构建FP树压缩存储频繁项关联信息,仅需扫描数据库两次,无需生成候选项集。算法分为两个阶段:首先构建FP树和项头表,然后自底向上递归挖掘频繁项集。FP-growth在购物篮分析等场景中应用广泛,能有效挖掘商品关联模式。虽然存在内存依赖等缺点,但其高效性和完整性使其成为关联规则挖掘的核心算法之一。本文详细介绍了算法原理、实现步骤和Python代码,为实际应用提供了完整参考。