数据结构
FP-Node设计
需要有的属性
- name
- count
- parent
- children:用 map 存,方便查找后代有什么
- nodeLink:指向下一个同名节点,用于横向遍历
// 先定义数据结构
// 树节点结构
struct FPNode
{
string name; // 物品名称
int count; // 计数
FPNode *parent; // 父节点
map<string, FPNode *> children; // 子节点
FPNode *nodeLink; // 指向下一个同名节点 (用于横向链表)
FPNode(string n, FPNode *p = nullptr) : name(n), count(1), parent(p), nodeLink(nullptr) {}
~FPNode()
{
for (auto &pair : children)
delete pair.second;
}
};
// 头指针表
struct HeaderItem
{
int count; // 全局支持度
FPNode *headNode; // 链表头
FPNode *tailNode; // 链表尾 (方便快速插入)
HeaderItem() : count(0), headNode(nullptr), tailNode(nullptr) {}
};FP-Tree
// 定义 FPTree
class FPTree
{
public:
FPNode *root;
map<string, HeaderItem> headerTable;
int minSupport; // 最小支持度计数
FPTree(int minSup) : minSupport(minSup)
{
root = new FPNode("Root", nullptr);
}
~FPTree() { delete root; }
// 构建 FPtree
void build(const vector<vector<string>> &dataset)
{
// 第一遍扫描: 统计频率
map<string, int> frequency;
for (const auto &trans : dataset)
{
for (const string &item : trans)
{
frequency[item]++;
}
}
// 过滤并初始化头指针表
for (auto &pair : frequency)
{
if (pair.second >= minSupport)
{
headerTable[pair.first].count = pair.second;
}
}
// 第二遍扫描: 排序并插入树
for (const auto &trans : dataset)
{
vector<string> filteredItems;
for (const string &item : trans)
{
if (headerTable.count(item))
{
filteredItems.push_back(item);
}
}
// 按照频率降序排序 (如果频率相同,按字典序)
sort(filteredItems.begin(), filteredItems.end(),
[&](const string &a, const string &b)
{
if (headerTable[a].count != headerTable[b].count)
return headerTable[a].count > headerTable[b].count;
return a < b;
});
if (!filteredItems.empty())
{
insertTransaction(filteredItems, root);
}
}
}
// 工具函数,插入一条记录
void insertTransaction(const vector<string> &items, FPNode *node)
{
if (items.empty())
return;
string firstItem = items[0];
FPNode *nextNode = nullptr;
// 检查孩子是否存在
if (node->children.find(firstItem) != node->children.end())
{
nextNode = node->children[firstItem];
nextNode->count++;
}
else
{
// 创建新节点
nextNode = new FPNode(firstItem, node);
node->children[firstItem] = nextNode;
// 更新头指针表的链表 (side-links)
HeaderItem &header = headerTable[firstItem];
if (header.headNode == nullptr)
{
header.headNode = nextNode;
}
else
{
header.tailNode->nodeLink = nextNode;
}
header.tailNode = nextNode;
}
// 递归处理剩下的物品
vector<string> remainingItems(items.begin() + 1, items.end());
insertTransaction(remainingItems, nextNode);
}挖掘频繁模式
// 挖掘频繁模式
// prefix: 当前的前缀 (比如已经挖出了 {啤酒})
// results: 存储最终结果
void mine(vector<string> prefix, vector<pair<vector<string>, int>> &results)
{
// 1. 获取头指针表中所有项,并按频率升序排列 (从底向上挖)
vector<pair<string, int>> sortedHeaders;
for (auto &pair : headerTable)
{
sortedHeaders.push_back(make_pair(pair.first, pair.second.count));
}
sort(sortedHeaders.begin(), sortedHeaders.end(),
[](const pair<string, int> &a, const pair<string, int> &b)
{
return a.second < b.second;
});
// 2. 遍历每一个项 (作为后缀)
for (const auto &pair : sortedHeaders)
{
string item = pair.first;
int support = pair.second;
// 生成新的频繁模式: prefix + item
vector<string> newPattern = prefix;
newPattern.push_back(item);
results.push_back({newPattern, support});
// 3. 构建条件模式基 (Conditional Pattern Base)
vector<vector<string>> conditionalPatternBase;
FPNode *curr = headerTable[item].headNode;
while (curr != nullptr)
{
vector<string> path;
FPNode *parent = curr->parent;
// 向上回溯直到 Root
while (parent != nullptr && parent->name != "Root")
{
path.push_back(parent->name);
parent = parent->parent;
}
// 路径逆序才是从上到下,但在构建条件树时顺序不敏感,只要计数对就行
// 关键: 这条路径出现的次数 = curr->count
for (int i = 0; i < curr->count; i++)
{
// 为了简化,这里笨办法重复添加路径。优化做法是带权重的FPTree。
// 但为了代码易读,模拟把路径重复加入。
// 注意: 标准FP-Growth这里是带计数的,为简化代码逻辑,
// 下面构建条件树时直接把路径 reverse 后传进去重新建树。
vector<string> pathCopy = path;
reverse(pathCopy.begin(), pathCopy.end());
conditionalPatternBase.push_back(pathCopy);
}
curr = curr->nodeLink;
}
// 4. 构建条件 FP-Tree (递归)
if (!conditionalPatternBase.empty())
{
FPTree conditionalTree(minSupport);
conditionalTree.build(conditionalPatternBase);
if (!conditionalTree.headerTable.empty())
{
conditionalTree.mine(newPattern, results);
}
}
}
}测试
// 测试
int main()
{
// 1. 准备数据 (课件的数据)
vector<vector<string>> dataset = {
{"牛奶", "鸡蛋", "面包", "薯片"},
{"鸡蛋", "爆米花", "薯片", "啤酒"},
{"鸡蛋", "面包", "薯片"},
{"牛奶", "鸡蛋", "面包", "爆米花", "薯片", "啤酒"},
{"牛奶", "面包", "啤酒"},
{"鸡蛋", "面包", "啤酒"},
{"牛奶", "面包", "薯片"},
{"牛奶", "鸡蛋", "面包", "黄油", "薯片"},
{"牛奶", "鸡蛋", "黄油", "薯片"}};
int minSupport = 2; // 设定最小支持度 (例如 2 次)
cout << "========== 正在构建 FP-Tree ==========" << endl;
cout << "最小支持度计数: " << minSupport << endl;
FPTree tree(minSupport);
tree.build(dataset);
cout << "构建完成,开始挖掘频繁模式..." << endl;
vector<pair<vector<string>, int>> frequentPatterns;
vector<string> emptyPrefix;
tree.mine(emptyPrefix, frequentPatterns);
// 输出结果
cout << "========== 挖掘结果 (频繁项集) ==========" << endl;
for (const auto &p : frequentPatterns)
{
cout << "{ ";
for (const string &s : p.first)
cout << s << " ";
cout << "} : " << p.second << endl;
}
return 0;
}