【数据挖掘】FP-growth算法

目录

一、引言

二、算法核心定位与核心优势

[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 的核心设计思想

  1. 压缩存储 :用 FP 树将事务数据库中所有频繁项 的关联信息压缩成一棵带链接的前缀树,树中每个节点记录项名、出现次数,同时用项头表维护同一项的所有节点链接,保证能快速追溯;
  2. 自底向上挖掘 :利用 "频繁项集的所有非空子集都是频繁的 " 这一性质(和 Apriori 相同),从项头表中支持度最低的频繁项 开始,自底向上生成条件模式基 ,构建条件 FP 树,递归挖掘包含当前项的所有频繁项集;
  3. 单路径优化:若某棵条件 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 树的结构要求:

  1. 包含一个根节点 (记为NULL,无项名、计数为 0);
  2. 每个非根节点包含 4 个属性:项名(item_name)计数(count)(该节点代表的项在当前路径上出现的次数)、父节点(parent)(指向父节点,根节点无父节点)、子节点(children)(字典,键为项名,值为子节点对象);
  3. 树的路径对应事务中的频繁项序列(按支持度降序排列),路径上的节点组合代表一个项集;
  4. 同一项的所有节点通过同项链接(next_node) 相连,方便后续快速追溯。
(2)项头表(Header Table)

项头表是按支持度降序排列的频繁 1 - 项集列表,是挖掘 FP 树的 "入口",每个条目包含两个属性:

  1. 频繁项名:如啤酒、尿布、牛奶;
  2. 支持度:该频繁 1 - 项集的支持度;
  3. 链接指针 :指向 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. 剔除非频繁项:删除事务中属于非频繁 1 - 项集的项(无意义,不参与 FP 树构建);
  2. 按排序列表 L 排序:将事务中剩余的频繁项,按照步骤 2 得到的排序列表 L 的顺序重新排序(保证 FP 树的前缀一致性,便于压缩);
  3. 空事务过滤:若预处理后的事务无任何项,直接丢弃。

:某条事务为 {啤酒,可乐,尿布},预处理后剔除 {可乐},按 L 排序后为 [啤酒,尿布];某条事务为 {可乐,鸡蛋},预处理后无项,丢弃。

步骤 4:构建 FP 树和项头表

初始化一棵空的 FP 树(仅含根节点NULL),初始化空的项头表;遍历步骤 3 预处理后的每一条事务序列 ,将其插入 FP 树,插入过程中更新节点计数、子节点、父节点和同项链接,同时更新项头表:

  1. 插入规则 :从根节点开始,依次遍历事务序列中的每个项,若当前节点的子节点中包含该项,则子节点计数 + 1 ;若不包含,则新建一个节点(设置项名、计数 = 1、父节点为当前节点),并将新节点加入当前节点的子节点字典;
  2. 同项链接维护 :若项头表中已存在该项,则将新节点的next_node指向项头表中该项的最后一个同项节点,同时更新项头表的链接指针为新节点;若项头表中无该项,则在项头表中添加该条目(项名、支持度、链接指针指向新节点);
  3. 项头表更新:项头表的支持度为该频繁项在所有事务中的总支持数(步骤 1 的统计结果),仅维护链接指针即可。

核心:插入所有事务后,得到一棵完整的 FP 树,以及带同项链接的项头表,原始事务数据库的频繁项关联信息已全部压缩到其中。

阶段 2:从 FP 树中挖掘所有频繁项集(自底向上,递归挖掘)

这一阶段是 FP-growth 的核心,目标是从阶段 1 构建的 FP 树 + 项头表中,挖掘出所有 支持度≥min_support 的频繁项集(1 - 项集、2 - 项集、3 - 项集......),核心思路是自底向上处理项头表,递归生成条件模式基→构建条件 FP 树→挖掘频繁项集,共分 3 步,利用 "频繁项集的子集必频繁" 的性质,无冗余挖掘。

步骤 1:初始化频繁项集集合

将项头表中的所有频繁 1 - 项集加入频繁项集集合(这些是基础频繁项集)。

步骤 2:自底向上遍历项头表的每个频繁项

从项头表中支持度最低的频繁项开始,自底向上处理每个频繁项x(原因:支持度越低的项,对应的高阶频繁项集越少,递归次数越少,效率越高),对每个x执行以下操作:

  1. 生成x的条件模式基 :通过项头表中x的链接指针,遍历 FP 树中所有x的节点;对每个x节点,向上追溯到根节点 ,得到该节点的前缀路径(剔除x本身),前缀路径的计数等于x节点的计数;所有x节点的前缀路径 + 计数,即为x的条件模式基;
  2. 构建x的条件 FP 树 :以x的条件模式基为新的 "事务数据库",重复阶段 1 的步骤 3-4(预处理、插入树),构建x的条件 FP 树和条件项头表;若条件模式基为空,则跳过后续步骤(无包含x的高阶频繁项集);
  3. 递归挖掘条件 FP 树的频繁项集 :对x的条件 FP 树,递归执行阶段 2 的所有步骤,挖掘出该条件 FP 树中的所有频繁项集,记为F;
  4. 组合生成包含x的频繁项集 :将x与F中的每个频繁项集进行组合(如F中有 {啤酒},则组合为 {啤酒,x}),将组合后的项集加入全局频繁项集集合(这些是包含x的高阶频繁项集)。
步骤 3:终止递归,输出所有频繁项集

当遍历完项头表的所有频繁项,且所有递归调用结束后,全局频繁项集集合中包含了所有支持度≥min_support 的频繁项集,挖掘完成。

关键优化:单路径条件 FP 树的枚举法

若某一条件 FP 树只有一条路径 (从根节点到叶子节点只有一条唯一路径),则无需递归挖掘,直接枚举该路径上所有项的非空组合,这些组合都是频繁项集(因为路径上的所有项的组合的支持度等于路径中最小的节点计数),大幅减少递归次数,提升效率。

:条件 FP 树的路径为 [啤酒→尿布→牛奶],则直接枚举 {啤酒}、{尿布}、{牛奶}、{啤酒,尿布}、{啤酒,牛奶}、{尿布,牛奶}、{啤酒,尿布,牛奶},均为该条件 FP 树的频繁项集。

五、经典实例演示(购物篮场景,一步一步走)

用一个小型购物篮事务数据库演示 FP-growth 的完整步骤,让抽象的理论变具体,所有步骤可手动验证,理解后就能推及海量数据。

前置条件

  1. 事务数据库D :共 8 条购物篮事务,每条事务是一次购物的商品集合,记为 T1-T8:

    事务 ID 商品集合(项)
    T1 牛奶、面包、啤酒
    T2 牛奶、尿布
    T3 牛奶、面包、尿布
    T4 啤酒、尿布
    T5 牛奶、面包、啤酒、尿布
    T6 牛奶、面包
    T7 啤酒、面包
    T8 牛奶、啤酒
  2. 最小支持数:设为 3(即至少出现在 3 条事务中的项集为频繁项集,对应支持度 = 3/8=0.375);

  3. 目标:挖掘所有频繁项集。

阶段 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 树项头表如下(文字简化描述):

  1. 项头表 (按支持数降序,带链接指针):

    频繁项 支持数 链接指针指向 FP 树的第一个节点
    牛奶 6 根节点→牛奶(计数 6)
    面包 5 根节点→牛奶→面包(计数 4)
    啤酒 5 根节点→牛奶→面包→啤酒(计数 2)
    尿布 4 根节点→牛奶→尿布(计数 1)
  2. FP 树核心结构 (关键路径 + 节点计数):

    • 根→牛奶(6):分支 1→面包(4)→啤酒(2)→尿布(1);分支 2→面包(4)→尿布(1);分支 3→面包(4);分支 4→尿布(1);分支 5→啤酒(1);
    • 根→面包(1)→啤酒(1);
    • 根→啤酒(1)→尿布(1);(所有同项节点通过next_node链接,如所有 "尿布" 节点互相链接)。

阶段 2:挖掘所有频繁项集(自底向上递归)

从项头表最底部的尿布开始,自底向上处理牛奶、面包、啤酒、尿布(实际顺序:尿布→啤酒→面包→牛奶),逐步挖掘:

步骤 1:处理尿布(支持数 4)
  1. 生成条件模式基:遍历 FP 树中所有尿布节点,向上追溯前缀路径,得到条件模式基为 {[牛奶,面包,啤酒]:1, [牛奶,面包]:1, [牛奶]:1, [啤酒]:1}(计数分别为 1,总支持数 4);
  2. 构建条件 FP 树:以条件模式基为新数据库,最小支持数 3,筛选后无频繁项(所有前缀路径的计数均 < 3),条件 FP 树为空;
  3. 无递归挖掘,仅将 {尿布} 加入频繁项集集合。
步骤 2:处理啤酒(支持数 5)
  1. 生成条件模式基:前缀路径为 {[牛奶,面包]:2, [牛奶]:1, [面包]:1}(计数分别为 2、1、1,总支持数 5);
  2. 构建条件 FP 树:筛选出频繁项 [牛奶 (3), 面包 (3)],构建条件 FP 树为根→牛奶(3)→面包(2);
  3. 递归挖掘条件 FP 树,得到频繁项集 {牛奶}、{面包}、{牛奶,面包};
  4. 组合生成包含啤酒的频繁项集:{啤酒,牛奶}、{啤酒,面包}、{啤酒,牛奶,面包},加入集合;
  5. 基础频繁项集 {啤酒} 加入集合。
步骤 3:处理面包(支持数 5)
  1. 生成条件模式基:前缀路径为 {[牛奶]:4}(计数 4,总支持数 5);
  2. 构建条件 FP 树:频繁项 {牛奶 (4)},条件 FP 树为根→牛奶(4);
  3. 递归挖掘得到频繁项集 {牛奶};
  4. 组合生成包含面包的频繁项集:{面包,牛奶},加入集合;
  5. 基础频繁项集 {面包} 加入集合。
步骤 4:处理牛奶(支持数 6)
  1. 生成条件模式基:无(牛奶是项头表第一个项,无前缀路径);
  2. 仅将基础频繁项集 {牛奶} 加入集合。

最终挖掘结果(所有频繁项集)

支持数≥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 算法的优缺点

优点

  1. 效率极高:仅扫描事务数据库两次,无候选项集生成,避免了 Apriori 的多次扫描和候选集剪枝开销,海量数据下优势明显;
  2. 空间复杂度低:FP 树压缩存储频繁项的关联信息,相比 Apriori 存储大量候选项集,内存占用大幅减少;
  3. 挖掘完整:能挖掘出所有支持度≥最小支持度的频繁项集,无遗漏;
  4. 单路径优化:对单路径条件 FP 树直接枚举,减少递归次数,进一步提升效率;
  5. 适配离散型数据:完美适配购物篮、文本词频、用户行为等离散型数据的频繁模式挖掘。

缺点

  1. 内存依赖 :FP 树需要一次性加载到内存,若事务数据库极大(超过内存容量),FP 树构建会失败,效率急剧下降(可通过分块 FP-growth 优化);
  2. 动态数据不友好 :若事务数据库更新(新增 / 删除事务),需要重新构建整个 FP 树,无法增量更新,适合静态数据挖掘;
  3. 实现复杂度较高:相比 Apriori 的双层循环,FP-growth 需要维护 FP 树的节点链接、递归挖掘,代码实现更复杂;
  4. 不适合低支持度场景:若最小支持度过低,频繁项数量会急剧增加,FP 树会变得庞大,挖掘效率下降(和 Apriori 一致)。

十、FP-growth 算法的应用场景

FP-growth 是目前工业界最常用的频繁项集挖掘算法 ,广泛应用于需要挖掘 "项间关联模式" 的场景,核心还是关联规则挖掘,和你之前的购物篮分析高度相关,主要应用场景包括:

  1. 零售 / 电商购物篮分析:挖掘商品间的频繁组合(如 {啤酒,尿布}、{面包,牛奶}),用于商品推荐、货架摆放、捆绑销售;
  2. 网页访问模式挖掘:挖掘用户频繁访问的网页组合(如 {首页→商品列表→详情页}),用于网站

十一、总结

FP-growth是一种高效的频繁项集挖掘算法,相比Apriori算法具有显著优势。它通过构建FP树压缩存储频繁项关联信息,仅需扫描数据库两次,无需生成候选项集。算法分为两个阶段:首先构建FP树和项头表,然后自底向上递归挖掘频繁项集。FP-growth在购物篮分析等场景中应用广泛,能有效挖掘商品关联模式。虽然存在内存依赖等缺点,但其高效性和完整性使其成为关联规则挖掘的核心算法之一。本文详细介绍了算法原理、实现步骤和Python代码,为实际应用提供了完整参考。

相关推荐
Juicedata4 小时前
JuiceFS 企业版 5.3 特性详解:单文件系统支持超 5,000 亿文件,首次引入 RDMA
大数据·人工智能·机器学习·性能优化·开源
Piar1231sdafa4 小时前
蓝莓目标检测——改进YOLO11-C2TSSA-DYT-Mona模型实现
人工智能·目标检测·计算机视觉
愚公搬代码4 小时前
【愚公系列】《AI短视频创作一本通》002-AI引爆短视频创作革命(短视频创作者必备的能力)
人工智能
数据猿视觉5 小时前
新品上市|奢音S5耳夹耳机:3.5g无感佩戴,178.8元全场景适配
人工智能
2301_790300965 小时前
Python单元测试(unittest)实战指南
jvm·数据库·python
蚁巡信息巡查系统5 小时前
网站信息发布再巡查机制怎么建立?
大数据·人工智能·数据挖掘·内容运营
AI浩5 小时前
C-RADIOv4(技术报告)
人工智能·目标检测
Purple Coder5 小时前
AI赋予超导材料预测论文初稿
人工智能
Data_Journal5 小时前
Scrapy vs. Crawlee —— 哪个更好?!
运维·人工智能·爬虫·媒体·社媒营销
只是懒得想了5 小时前
C++实现密码破解工具:从MD5暴力破解到现代哈希安全实践
c++·算法·安全·哈希算法