当前位置: 首页 > news >正文

【C++游记】Map与Set的封装

 

 

枫の个人主页

你不能改变过去,但你可以改变未来

算法/C++/数据结构/C

Hello,这里是小枫。C语言与数据结构和算法初阶两个板块都更新完毕,我们继续来学习C++的内容呀。C++是接近底层有比较经典的语言,因此学习起来注定枯燥无味,西游记大家都看过吧~,我希望能带着大家一起跨过九九八十一难,降伏各类难题,学会C++,我会尽我所能,以通俗易懂、幽默风趣的方式带给大家形象生动的知识,也希望大家遇到困难不退缩,遇到难题不放弃,学习师徒四人的精神!!!故此得名【C++游记

 话不多说,让我们一起进入今天的学习吧~~~  

目录

封装红黑树实现 mymap 和 myset 超详细教程

一、SGI-STL3.0 源码及框架分析

1.1 核心结构框架

set 的结构框架

map 的结构框架

红黑树的结构框架

1.2 关键设计思想解析

二、模拟实现 map 和 set

2.1 红黑树的实现

2.1.1 红黑树节点定义

2.1.2 红黑树迭代器实现

2.1.3 红黑树核心类实现

2.2 封装实现 set

2.3 封装实现 map

2.4 测试代码及结果

三、总结

四、结语


封装红黑树实现 mymap 和 myset 超详细教程

在 STL 容器中,map 和 set 是常用的关联式容器,它们的底层实现都依赖于红黑树这一高效的数据结构。本文将从 SGI-STL3.0 版本源码框架分析入手,详细讲解如何基于红黑树封装实现自定义的 mymap 和 myset,并深入剖析其中的关键技术点,包括红黑树的实现、仿函数的应用、迭代器设计以及 map 的 [] 运算符重载等内容。

一、SGI-STL3.0 源码及框架分析

要实现自定义的 mymap 和 myset,首先需要深入理解 STL 中 map 和 set 的底层实现框架。在 SGI-STL3.0 版本中,map 和 set 的源代码分别位于 map/set/stl_map.h、stl_set.h、stl_tree.h 等头文件中。

1.1 核心结构框架

set 的结构框架
#ifndef __SGI_STL_INTERNAL_TREE_H
#include <stl_tree.h>
#endif
#include <stl_set.h>
#include <stl_multiset.h>template <class Key, class Compare = less<Key>, class Alloc = alloc>
class set {
public:// 类型定义typedef Key key_type;typedef Key value_type;
private:// 红黑树类型定义,作为set的底层存储typedef rb_tree<key_type, value_type, identity<value_type>, key_compare, Alloc> rep_type;rep_type t; // 用于表示set的红黑树
};
map 的结构框架
#ifndef __SGI_STL_INTERNAL_TREE_H
#include <stl_tree.h>
#endif
#include <stl_map.h>
#include <stl_multimap.h>template <class Key, class T, class Compare = less<Key>, class Alloc = alloc>
class map {
public:// 类型定义typedef Key key_type;typedef T mapped_type;typedef pair<const Key, T> value_type;
private:// 红黑树类型定义,作为map的底层存储typedef rb_tree<key_type, value_type, select1st<value_type>, key_compare, Alloc> rep_type;rep_type t; // 用于表示map的红黑树
};
红黑树的结构框架
// 红黑树节点基类
struct __rb_tree_node_base {typedef __rb_tree_color_type color_type;typedef __rb_tree_node_base* base_ptr;color_type color; // 节点颜色base_ptr parent; // 父节点指针base_ptr left; // 左子节点指针base_ptr right; // 右子节点指针
};// 红黑树节点类
template <class Value>
struct __rb_tree_node : public __rb_tree_node_base {typedef __rb_tree_node<Value>* link_type;Value value_field; // 节点存储的数据
};// 红黑树类
template <class Key, class Value, class KeyOfValue, class Compare, class Alloc = alloc>
class rb_tree {
protected:typedef void* void_pointer;typedef __rb_tree_node_base* base_ptr;typedef __rb_tree_node<Value> rb_tree_node;typedef rb_tree_node* link_type;typedef Key key_type;typedef Value value_type;public:// 插入唯一元素(不允许重复)pair<iterator, bool> insert_unique(const value_type& x);// 删除指定key的元素size_type erase(const key_type& x);// 查找指定key的元素iterator find(const key_type& x);protected:size_type node_count; // 记录树的节点数量link_type header; // 头节点
};

1.2 关键设计思想解析

  1. 泛型设计:红黑树采用泛型思想实现,通过模板参数灵活控制其支持的场景。红黑树是实现 key 的搜索场景(set),还是 key/value 的搜索场景(map),由第二个模板参数Value决定__rb_tree_node中存储的数据类型。

    • set 实例化红黑树时,第二个模板参数传入key,即红黑树节点存储的是 key 值。
    • map 实例化红黑树时,第二个模板参数传入pair<const Key, T>,即红黑树节点存储的是键值对。
  2. 模板参数的作用

    • 第一个模板参数Key:主要用于finderase等函数的参数类型,因为无论是 set 还是 map,查找和删除操作都是基于 key 进行的。对于 set 而言,KeyValue模板参数是相同的;但对于 map,Key是键的类型,Value是键值对的类型,二者完全不同。
    • 第三个模板参数KeyOfValue:用于从Value类型中提取出Key,以便进行比较操作。例如在 map 中,需要从pair<const Key, T>中提取出Key来进行节点间的比较。
  3. 命名风格问题:需要注意的是,源码中的命名风格存在一定的不规范性。例如 set 的模板参数用Key命名,map 用KeyT命名,而红黑树又用KeyValue命名,这在一定程度上可能会造成理解上的混淆,在实际开发中需要尽量避免这种命名不一致的情况。

二、模拟实现 map 和 set

在理解了 STL 源码框架的基础上,我们开始模拟实现自定义的 mymap 和 myset。实现的核心思路是复用红黑树,并通过仿函数解决数据比较时的 key 提取问题,同时实现迭代器以支持遍历操作,最后为 map 实现 [] 运算符重载。

2.1 红黑树的实现

首先,我们需要实现一个通用的红黑树类,该类需要支持插入操作,并能通过仿函数从存储的数据中提取 key 进行比较。

2.1.1 红黑树节点定义
// 定义节点颜色枚举
enum Colour {RED,BLACK
};// 红黑树节点模板类
template <class T>
struct RBTreeNode {T _data; // 节点存储的数据RBTreeNode<T>* _left; // 左子节点指针RBTreeNode<T>* _right; // 右子节点指针RBTreeNode<T>* _parent; // 父节点指针Colour _col; // 节点颜色// 构造函数RBTreeNode(const T& data): _data(data), _left(nullptr), _right(nullptr), _parent(nullptr), _col(RED) // 新增节点默认为红色,减少红黑树性质破坏的可能性{}
};
2.1.2 红黑树迭代器实现

红黑树的迭代器实现是一个难点,其核心是实现operator++operator--操作,以支持中序遍历(红黑树的中序遍历结果是有序的,这符合 map 和 set 的有序性要求)。

// 红黑树迭代器模板类
template <class T, class Ref, class Ptr>
struct RBTreeIterator {typedef RBTreeNode<T> Node;typedef RBTreeIterator<T, Ref, Ptr> Self;Node* _node; // 当前迭代器指向的节点Node* _root; // 红黑树的根节点,用于--end()时的特殊处理// 构造函数RBTreeIterator(Node* node, Node* root): _node(node), _root(root){}// 前置++运算符重载Self& operator++() {if (_node->_right) {// 右子树不为空,下一个节点是右子树的最左节点(中序遍历的下一个节点)Node* leftMost = _node->_right;while (leftMost->_left) {leftMost = leftMost->_left;}_node = leftMost;} else {// 右子树为空,需要向上找祖先节点,直到找到当前节点是其父节点左孩子的祖先Node* cur = _node;Node* parent = cur->_parent;while (parent && cur == parent->_right) {cur = parent;parent = cur->_parent;}_node = parent;}return *this;}// 前置--运算符重载Self& operator--() {if (_node == nullptr) {// 处理--end()的情况,end()指向nullptr,--end()应指向树的最右节点(中序遍历的最后一个节点)Node* rightMost = _root;while (rightMost && rightMost->_right) {rightMost = rightMost->_right;}_node = rightMost;} else if (_node->_left) {// 左子树不为空,前一个节点是左子树的最右节点Node* rightMost = _node->_left;while (rightMost->_right) {rightMost = rightMost->_right;}_node = rightMost;} else {// 左子树为空,需要向上找祖先节点,直到找到当前节点是其父节点右孩子的祖先Node* cur = _node;Node* parent = cur->_parent;while (parent && cur == parent->_left) {cur = parent;parent = cur->_parent;}_node = parent;}return *this;}// 解引用运算符重载Ref operator*() {return _node->_data;}// ->运算符重载Ptr operator->() {return &_node->_data;}// !=运算符重载bool operator!=(const Self& s) const {return _node != s._node;}// ==运算符重载bool operator==(const Self& s) const {return _node == s._node;}
};
2.1.3 红黑树核心类实现

红黑树的核心操作包括插入、旋转(左旋和右旋)、销毁等。插入操作后需要检查红黑树的性质是否被破坏,并进行相应的调整。

template <class K, class T, class KeyOfT>
class RBTree {typedef RBTreeNode<T> Node;public:// 迭代器类型定义typedef RBTreeIterator<T, T&, T*> Iterator;typedef RBTreeIterator<T, const T&, const T*> ConstIterator;// 默认构造函数RBTree() = default;// 析构函数~RBTree() {Destroy(_root);_root = nullptr;}// 获取begin迭代器(指向中序遍历的第一个节点,即最左节点)Iterator Begin() {Node* leftMost = _root;while (leftMost && leftMost->_left) {leftMost = leftMost->_left;}return Iterator(leftMost, _root);}// 获取end迭代器(指向nullptr,表示遍历结束)Iterator End() {return Iterator(nullptr, _root);}// 获取const版本的begin迭代器ConstIterator Begin() const {Node* leftMost = _root;while (leftMost && leftMost->_left) {leftMost = leftMost->_left;}return ConstIterator(leftMost, _root);}// 获取const版本的end迭代器ConstIterator End() const {return ConstIterator(nullptr, _root);}// 插入元素,返回pair<Iterator, bool>,其中bool表示插入是否成功pair<Iterator, bool> Insert(const T& data) {if (_root == nullptr) {// 树为空,直接创建根节点,根节点颜色为黑色_root = new Node(data);_root->_col = BLACK;return make_pair(Iterator(_root, _root), true);}KeyOfT kot;Node* parent = nullptr;Node* cur = _root;// 查找插入位置while (cur) {if (kot(cur->_data) < kot(data)) {parent = cur;cur = cur->_right;} else if (kot(cur->_data) > kot(data)) {parent = cur;cur = cur->_left;} else {// 元素已存在,插入失败return make_pair(Iterator(cur, _root), false);}}// 创建新节点并插入到树中cur = new Node(data);cur->_col = RED; // 新节点默认为红色if (kot(parent->_data) < kot(data)) {parent->_right = cur;} else {parent->_left = cur;}cur->_parent = parent;// 调整红黑树,使其满足红黑树的性质while (parent && parent->_col == RED) {Node* grandfather = parent->_parent; // 祖父节点if (parent == grandfather->_left) {// 父节点是祖父节点的左孩子Node* uncle = grandfather->_right; // 叔叔节点if (uncle && uncle->_col == RED) {// 情况1:叔叔节点存在且为红色,只需变色parent->_col = BLACK;uncle->_col = BLACK;grandfather->_col = RED;// 继续向上调整cur = grandfather;parent = cur->_parent;} else {// 情况2:叔叔节点不存在或为黑色,需要旋转+变色if (cur == parent->_right) {// 子节点是父节点的右孩子,先左旋父节点RotateL(parent);swap(parent, cur); // 旋转后父节点和子节点的位置交换,需要更新parent和cur}// 右旋祖父节点RotateR(grandfather);parent->_col = BLACK;grandfather->_col = RED;break; // 调整完成,退出循环}} else {// 父节点是祖父节点的右孩子Node* uncle = grandfather->_left; // 叔叔节点if (uncle && uncle->_col == RED) {// 情况1:叔叔节点存在且为红色,只需变色parent->_col = BLACK;uncle->_col = BLACK;grandfather->_col = RED;// 继续向上调整cur = grandfather;parent = cur->_parent;} else {// 情况2:叔叔节点不存在或为黑色,需要旋转+变色if (cur == parent->_left) {// 子节点是父节点的左孩子,先右旋父节点RotateR(parent);swap(parent, cur); // 旋转后父节点和子节点的位置交换,需要更新parent和cur}// 左旋祖父节点RotateL(grandfather);parent->_col = BLACK;grandfather->_col = RED;break; // 调整完成,退出循环}}}// 确保根节点始终为黑色_root->_col = BLACK;return make_pair(Iterator(cur, _root), true);}// 查找指定key的元素,返回对应的迭代器Iterator Find(const K& key) {Node* cur = _root;KeyOfT kot;while (cur) {if (kot(cur->_data) < key) {cur = cur->_right;} else if (kot(cur->_data) > key) {cur = cur->_left;} else {// 找到元素,返回对应的迭代器return Iterator(cur, _root);}}// 未找到元素,返回end迭代器return End();}private:// 左旋操作void RotateL(Node* parent) {Node* subR = parent->_right; // 父节点的右子节点Node* subRL = subR->_left; // 右子节点的左子节点// 更新父节点的右子节点为subRLparent->_right = subRL;if (subRL) {subRL->_parent = parent;}Node* parentParent = parent->_parent; // 父节点的父节点// 更新subR的左子节点为parentsubR->_left = parent;parent->_parent = subR;// 更新parentParent的子节点为subRif (parentParent == nullptr) {// 父节点是根节点,旋转后subR成为新的根节点_root = subR;subR->_parent = nullptr;} else {if (parent == parentParent->_left) {parentParent->_left = subR;} else {parentParent->_right = subR;}subR->_parent = parentParent;}}// 右旋操作void RotateR(Node* parent) {Node* subL = parent->_left; // 父节点的左子节点Node* subLR = subL->_right; // 左子节点的右子节点// 更新父节点的左子节点为subLRparent->_left = subLR;if (subLR) {subLR->_parent = parent;}Node* parentParent = parent->_parent; // 父节点的父节点// 更新subL的右子节点为parentsubL->_right = parent;parent->_parent = subL;// 更新parentParent的子节点为subLif (parentParent == nullptr) {// 父节点是根节点,旋转后subL成为新的根节点_root = subL;subL->_parent = nullptr;} else {if (parent == parentParent->_left) {parentParent->_left = subL;} else {parentParent->_right = subL;}subL->_parent = parentParent;}}// 递归销毁红黑树void Destroy(Node* root) {if (root == nullptr) {return;}Destroy(root->_left);Destroy(root->_right);delete root;}private:Node* _root = nullptr; // 红黑树的根节点
};

2.2 封装实现 set

set 的特点是存储唯一的 key 值,且不允许修改 key。基于红黑树封装 set 时,需要定义一个仿函数SetKeyOfT,用于从存储的 key 中提取 key(因为 set 存储的就是 key 本身,所以仿函数直接返回 key 即可)。

#include "RBTree.h"namespace wc {
template <class K>
class set {// 仿函数:从set存储的数据(K类型)中提取keystruct SetKeyOfT {const K& operator()(const K& key) {return key;}};public:// 迭代器类型定义,set的迭代器不允许修改数据,所以红黑树的第二个模板参数为const Ktypedef typename RBTree<K, const K, SetKeyOfT>::Iterator iterator;typedef typename RBTree<K, const K, SetKeyOfT>::ConstIterator const_iterator;// 获取begin迭代器iterator begin() {return _t.Begin();}// 获取end迭代器iterator end() {return _t.End();}// 获取const版本的begin迭代器const_iterator begin() const {return _t.Begin();}// 获取const版本的end迭代器const_iterator end() const {return _t.End();}// 插入元素,返回pair<iterator, bool>pair<iterator, bool> insert(const K& key) {return _t.Insert(key);}// 查找指定key的元素iterator find(const K& key) {return _t.Find(key);}private:// 底层的红黑树对象RBTree<K, const K, SetKeyOfT> _t;
};// 测试set的函数
void test_set() {set<int> s;int a[] = {4, 2, 6, 1, 3, 5, 15, 7, 16, 14};for (auto e : a) {s.insert(e);}// 遍历set(中序遍历,结果有序)cout << "遍历set(正向):";for (auto e : s) {cout << e << " ";}cout << endl;// 反向遍历setcout << "遍历set(反向):";set<int>::const_iterator it = s.end();while (it != s.begin()) {--it;// *it += 2; // 错误,set的迭代器不允许修改数据cout << *it << " ";}cout << endl;// 查找元素auto findIt = s.find(6);if (findIt != s.end()) {cout << "找到元素:" << *findIt << endl;} else {cout << "未找到元素:6" << endl;}findIt = s.find(10);if (findIt != s.end()) {cout << "找到元素:" << *findIt << endl;} else {cout << "未找到元素:10" << endl;}
}
}

2.3 封装实现 map

map 的特点是存储键值对pair<const K, V>,其中 key 不允许修改,value 允许修改。基于红黑树封装 map 时,需要定义一个仿函数MapKeyOfT,用于从键值对中提取 key。同时,需要实现[]运算符重载,以支持通过 key 快速访问和修改 value。

#include "RBTree.h"namespace wc {
template <class K, class V>
class map {// 仿函数:从map存储的数据(pair<const K, V>类型)中提取keystruct MapKeyOfT {const K& operator()(const pair<const K, V>& kv) {return kv.first;}};public:// 迭代器类型定义typedef typename RBTree<K, pair<const K, V>, MapKeyOfT>::Iterator iterator;typedef typename RBTree<K, pair<const K, V>, MapKeyOfT>::ConstIterator const_iterator;// 获取begin迭代器iterator begin() {return _t.Begin();}// 获取end迭代器iterator end() {return _t.End();}// 获取const版本的begin迭代器const_iterator begin() const {return _t.Begin();}// 获取const版本的end迭代器const_iterator end() const {return _t.End();}// 插入键值对,返回pair<iterator, bool>pair<iterator, bool> insert(const pair<const K, V>& kv) {return _t.Insert(kv);}// 查找指定key的元素iterator find(const K& key) {return _t.Find(key);}// []运算符重载,支持通过key访问和修改valueV& operator[](const K& key) {// 插入键值对,如果key已存在则返回已存在的迭代器,否则插入新的键值对(value为默认构造)pair<iterator, bool> ret = insert(make_pair(key, V()));// 返回对应的value引用return ret.first->second;}private:// 底层的红黑树对象RBTree<K, pair<const K, V>, MapKeyOfT> _t;
};// 测试map的函数
void test_map() {map<string, string> dict;// 插入键值对dict.insert({"sort", "排序"});dict.insert({"left", "左边"});dict.insert({"right", "右边"});// 使用[]修改已存在的valuedict["left"] = "左边,剩余";// 使用[]插入新的键值对(value为默认构造的空字符串,之后再赋值)dict["insert"] = "插入";// 使用[]访问不存在的key,会插入一个键值对(value为默认构造的空字符串)dict["string"];// 遍历mapcout << "遍历map:" << endl;map<string, string>::iterator it = dict.begin();while (it != dict.end()) {// it->first += 'x'; // 错误,map的key不允许修改it->second += "x"; // 正确,map的value允许修改cout << it->first << ":" << it->second << endl;++it;}cout << endl;// 查找元素auto findIt = dict.find("sort");if (findIt != dict.end()) {cout << "找到元素:" << findIt->first << ":" << findIt->second << endl;} else {cout << "未找到元素:sort" << endl;}findIt = dict.find("erase");if (findIt != dict.end()) {cout << "找到元素:" << findIt->first << ":" << findIt->second << endl;} else {cout << "未找到元素:erase" << endl;}
}
}

2.4 测试代码及结果

在 main 函数中调用test_settest_map函数,即可对自定义的 set 和 map 进行测试。

#include <iostream>
#include <string>
#include "Myset.h"
#include "Mymap.h"using namespace std;int main() {wc::test_set();cout << "-------------------------" << endl;wc::test_map();return 0;
}

三、总结

  1. 红黑树的复用:通过泛型设计,红黑树能够同时支持 set(存储 key)和 map(存储键值对)的底层实现,大大提高了代码的复用性。
  2. 仿函数的应用:仿函数KeyOfT解决了红黑树从存储的数据中提取 key 进行比较的问题,使得红黑树能够灵活处理不同类型的数据(key 或键值对)。
  3. 迭代器的设计:红黑树迭代器的核心是operator++operator--的实现,通过中序遍历的规则找到下一个或前一个节点,确保遍历结果的有序性。同时,set 的迭代器通过将红黑树的存储数据类型设为const K,实现了 key 的不可修改;map 的迭代器通过将键值对的 key 设为const K,实现了 key 的不可修改,而 value 可以修改。
  4. map 的 [] 运算符重载:基于红黑树的Insert操作,[]运算符能够实现 key 的查找、插入(如果 key 不存在)以及 value 的访问和修改,大大简化了 map 的使用。

四、结语

今日C++到这里就结束啦,如果觉得文章还不错的话,可以三连支持一下。感兴趣的宝子们欢迎持续订阅小枫,小枫在这里谢谢宝子们啦~小枫の主页还有更多生动有趣的文章,欢迎宝子们去点评鸭~C++的学习很陡,时而巨难时而巨简单,希望宝子们和小枫一起坚持下去~你们的三连就是小枫的动力,感谢支持~


文章转载自:

http://NhK3EeOV.bzfwn.cn
http://vldrsqvY.bzfwn.cn
http://BQ6GEKf8.bzfwn.cn
http://ze9U8Brg.bzfwn.cn
http://MI0mZKSk.bzfwn.cn
http://Xm2sofdA.bzfwn.cn
http://hc3rphWo.bzfwn.cn
http://NcKcoZHq.bzfwn.cn
http://kpf9w0Sz.bzfwn.cn
http://VQERxbkH.bzfwn.cn
http://hG3aIFSW.bzfwn.cn
http://4Inj5jTI.bzfwn.cn
http://YWsLGfvQ.bzfwn.cn
http://wX5VtPP8.bzfwn.cn
http://9UCgJNiZ.bzfwn.cn
http://HKxbg9IJ.bzfwn.cn
http://JZd5HtUi.bzfwn.cn
http://1anQ3HK9.bzfwn.cn
http://iWECW7wY.bzfwn.cn
http://MdetWiVC.bzfwn.cn
http://QmwR6ei3.bzfwn.cn
http://XTNVrg9A.bzfwn.cn
http://e8tY9OIs.bzfwn.cn
http://gU1ArY6t.bzfwn.cn
http://JfJhajZI.bzfwn.cn
http://euW3qEOG.bzfwn.cn
http://498lg9n6.bzfwn.cn
http://25a5TwZF.bzfwn.cn
http://cMLb2Sxf.bzfwn.cn
http://rAVCxH4I.bzfwn.cn
http://www.dtcms.com/a/386223.html

相关文章:

  • Infoseek舆情监测系统:AI驱动的一站式舆情管理解决方案
  • IDEA 连接MySQL数据库
  • Electron的IPC通讯 send/on 和 invoke/handle 的区别
  • 扩展开发:创建 Electron 插件
  • windows下ffmpeg的编译安装(支持硬件加速)--2025最新
  • JAVA后端面试笔记(二)
  • 每日前端宝藏库 | fullPage.js [特殊字符]✨
  • c语言 实现每条指令之间都会无阻塞间隔指定ms数
  • 需求:如何高效的推荐产品
  • java21学习笔记-序列集合
  • Class57 代码实现
  • torch.gather
  • 自学嵌入式第四十二天:单片机-定时器和UART串口
  • 大数据毕业设计选题推荐-基于大数据的旅游网站用户行为数据分析系统-Hadoop-Spark-数据可视化-BigData
  • 深入浅出数据结构:队列(Queue)—— 生活中的排队艺术
  • spring通过Spring Integration实现udp通信
  • Linux内存管理章节十八:内核开发者的武器库:内存分配API实战指南
  • CAD如何输出PDF多页文件
  • 我对 WPF 动摇时的选择:.NET Framework 4.6.2+WPF+Islands+UWP+CompostionApi
  • 1.整流-滤波电路的缺点和PFC的引入
  • QT 项目 线程信号切换 举例
  • 构网型5MW中压储能变流升压一体机技术方案
  • 【数据工程】8. SQL 入门教程
  • C++---前向声明
  • 在Qt项目中使用QtConcurrent::run,实现异步等待和同步调用
  • 经验分享只靠口头传递会带来哪些问题
  • Linux底层-内核数据接口:/proc
  • PEFT+DeepSpeed 1 (微调 分布式 显存优化)
  • Spring Boot 下 Druid 连接池:多维度优化打造卓越性能
  • 提升学术研究能力:从开题构思难题到AI辅助提纲生成