diff --git a/senior/3.数据结构.md b/senior/3.数据结构.md index d2be761..be8e814 100644 --- a/senior/3.数据结构.md +++ b/senior/3.数据结构.md @@ -779,7 +779,7 @@ public: UnionFind(int size) { parent.resize(size); // 动态数组存储每个节点的父节点 rank.resize(size, 1); // 动态数组存储每个节点的秩(树的高度) - + // 初始化每个节点的父节点为自己 for (int i = 0; i < size; i++) { parent[i] = i; @@ -1272,101 +1272,417 @@ int main() { ## 笛卡尔树 ### [维基百科用途解释](https://zh.wikipedia.org/wiki/%E7%AC%9B%E5%8D%A1%E5%B0%94%E6%A0%91) + ![1729697900947](image/3.数据结构/1729697900947.png) +### [bilibili其它up解释](https://www.bilibili.com/video/BV1Fh411E7A4) + 笛卡尔树是一种基于树结构的数据结构,结合了二叉搜索树和堆的特性。它通常用于动态集合的操作,比如合并和查找。笛卡尔树的基本原理是: 1. **树的结构**:对于每个节点,满足左子树的所有节点小于该节点,右子树的所有节点大于该节点(符合二叉搜索树的性质),同时每个节点的值也是该节点的优先级(符合堆的性质)。 - 2. **构建方法**:通过将元素依次插入,保持上述性质。插入时,如果新节点的优先级大于当前节点,则将其插入到当前节点的右子树,否则插入到左子树。 - 3. **合并操作**:可以通过递归方法实现合并两个笛卡尔树,比较根节点的优先级,并将较小的树作为左子树合并。 -下面是一个简单的C++实现示例: +![1729735106127](image/3.数据结构/1729735106127.png) +### 自己的代码解释 +笛卡尔树(Cartesian Tree)是一种特殊的二叉树,它满足以下两个性质: + +1. **堆性质**:对于每个节点,节点的值大于其子节点的值(或小于,取决于构建的方式)。 +2. **二叉搜索树性质**:对于每个节点,左子树的值小于节点的值,右子树的值大于节点的值。 + +以下是使用 C++ 构建笛卡尔树的详细代码,带有注释: +```cpp +#include +#include +#include +#include + +using namespace std; + +// 定义笛卡尔树节点 +struct Node { + int value; // 节点的值 + Node* left; // 左子树 + Node* right; // 右子树 + + Node(int val) : value(val), left(nullptr), right(nullptr) {} // 构造函数 +}; + +// 函数:插入节点到笛卡尔树中 +Node* insert(Node* root, Node* node) { + // 如果树为空,直接返回新节点 + if (!root) { + return node; + } + + // 根据值的大小关系插入节点 + if (node->value > root->value) { + // 插入到右子树 + root->right = insert(root->right, node); + } else { + // 插入到左子树 + root->left = insert(root->left, node); + } + + return root; +} + +// 函数:构建笛卡尔树 +Node* buildCartesianTree(const vector& arr) { + if (arr.empty()) return nullptr; + + // 使用栈来帮助构建树 + stack nodeStack; + Node* root = nullptr; + + for (int val : arr) { + Node* newNode = new Node(val); // 创建新节点 + + // 确保新节点满足堆性质 + while (!nodeStack.empty() && nodeStack.top()->value < newNode->value) { + // 当前节点小于新节点,作为新节点的左子树 + newNode->left = nodeStack.top(); + nodeStack.pop(); + } + + // 如果栈不为空,当前节点作为新节点的右子树 + if (!nodeStack.empty()) { + nodeStack.top()->right = newNode; + } else { + // 栈为空,当前节点成为根节点 + root = newNode; + } + + // 将新节点压入栈中 + nodeStack.push(newNode); + } + + return root; +} + +// 函数:中序遍历打印树节点 +void inorderTraversal(Node* root) { + if (!root) return; + inorderTraversal(root->left); + cout << root->value << " "; + inorderTraversal(root->right); +} + +// 主函数 +int main() { + vector arr = {3, 1, 4, 2}; // 示例数组 + Node* root = buildCartesianTree(arr); + + // 打印中序遍历结果 + cout << "Inorder Traversal of Cartesian Tree: "; + inorderTraversal(root); + cout << endl; + + return 0; +} +``` + +### 代码说明: +1. **Node 结构体**:定义了树的节点,包括值、左子树和右子树指针。 +2. **insert 函数**:负责将新节点插入到笛卡尔树中,确保树的性质。 +3. **buildCartesianTree 函数**:遍历输入数组,使用栈构建笛卡尔树。根据堆性质和二叉搜索树性质逐步插入节点。 +4. **inorderTraversal 函数**:用于打印树的中序遍历结果。 +5. **main 函数**:测试构建和遍历笛卡尔树的功能。 +### [模板题代码](https://www.luogu.com.cn/problem/P5854) +```cpp +#include +#include +#include +#include + +#ifdef ONLINE_JUDGE + #define DEBUG(code) +#else + #define DEBUG(code)code +#endif + +using ll = long long; + +const ll max_n = 1e7+5; +struct Node{ + ll left_child, right_child, val; + friend std::ostream& operator<<(std::ostream &os, const Node &n){ + os<<"Node { val: "< stk; +ll root, ans1, ans2; + +int main(){ + std::iostream::sync_with_stdio(false), std::cin.tie(nullptr), std::cout.tie(nullptr); + + std::cin>>n; + for(ll i{1};i<=n;i++){ + std::cin>>nodes[i].val; + } + for(ll i{1};i<=n;i++){ + while(!stk.empty() && nodes[i].val < nodes[stk.top()].val){ + nodes[i].left_child = stk.top(); + stk.pop(); + } + if(!stk.empty()){ + nodes[stk.top()].right_child = i; + }else{ + root = i; + } + stk.push(i); + } + DEBUG( + std::cout< using namespace std; +// AVL树节点结构 struct Node { - int value; - int priority; - Node *left, *right; + int key; // 节点的键值 + Node* left; // 左子树指针 + Node* right; // 右子树指针 + int height; // 节点的高度 - Node(int v) : value(v), priority(rand()), left(nullptr), right(nullptr) {} + // 构造函数,初始化节点 + Node(int value) : key(value), left(nullptr), right(nullptr), height(1) {} }; -Node* merge(Node* left, Node* right) { - if (!left) return right; - if (!right) return left; +// 获取节点的高度 +int getHeight(Node* node) { + return node ? node->height : 0; // 如果节点为空,高度为0 +} - if (left->priority > right->priority) { - left->right = merge(left->right, right); - return left; +// 获取节点的平衡因子 +int getBalance(Node* node) { + return node ? getHeight(node->left) - getHeight(node->right) : 0; // 左右子树高度差 +} + +// 右旋操作 +Node* rightRotate(Node* y) { + Node* x = y->left; // 取左子树为新根 + Node* T2 = x->right; // 保存x的右子树 + + // 进行旋转 + x->right = y; // y变为x的右子树 + y->left = T2; // 将T2作为y的左子树 + + // 更新节点高度 + y->height = max(getHeight(y->left), getHeight(y->right)) + 1; + x->height = max(getHeight(x->left), getHeight(x->right)) + 1; + + return x; // 返回新的根节点 +} + +// 左旋操作 +Node* leftRotate(Node* x) { + Node* y = x->right; // 取右子树为新根 + Node* T2 = y->left; // 保存y的左子树 + + // 进行旋转 + y->left = x; // x变为y的左子树 + x->right = T2; // 将T2作为x的右子树 + + // 更新节点高度 + x->height = max(getHeight(x->left), getHeight(x->right)) + 1; + y->height = max(getHeight(y->left), getHeight(y->right)) + 1; + + return y; // 返回新的根节点 +} + +// 插入节点 +Node* insert(Node* node, int key) { + // 1. 正常的二叉搜索树插入 + if (!node) return new Node(key); // 如果树为空,创建新节点 + if (key < node->key) { + node->left = insert(node->left, key); // 插入到左子树 } else { - right->left = merge(left, right->left); - return right; + node->right = insert(node->right, key); // 插入到右子树 + } + + // 2. 更新当前节点的高度 + node->height = max(getHeight(node->left), getHeight(node->right)) + 1; + + // 3. 获取平衡因子并进行旋转以保持树的平衡 + int balance = getBalance(node); + + // 左左情况:右旋 + if (balance > 1 && key < node->left->key) return rightRotate(node); + // 右右情况:左旋 + if (balance < -1 && key > node->right->key) return leftRotate(node); + // 左右情况:先左旋再右旋 + if (balance > 1 && key > node->left->key) { + node->left = leftRotate(node->left); + return rightRotate(node); + } + // 右左情况:先右旋再左旋 + if (balance < -1 && key < node->right->key) { + node->right = rightRotate(node->right); + return leftRotate(node); + } + + return node; // 返回(可能已平衡的)当前节点 +} + +// 中序遍历函数 +void inOrder(Node* root) { + if (root) { + inOrder(root->left); // 遍历左子树 + cout << root->key << " "; // 输出当前节点的键值 + inOrder(root->right); // 遍历右子树 } } -void split(Node* root, int key, Node*& left, Node*& right) { - if (!root) { - left = right = nullptr; - return; - } - if (root->value < key) { - left = root; - split(root->right, key, left->right, right); - } else { - right = root; - split(root->left, key, left, right->left); - } -} - -void insert(Node*& root, int value) { - Node *newNode = new Node(value); - if (!root) { - root = newNode; - return; - } - if (root->priority > newNode->priority) { - split(root, value, root->left, root->right); - root = newNode; - root->left = root->left; - root->right = root->right; - } else { - insert(root->value < value ? root->right : root->left, value); - } -} - -// 使用示例 int main() { - Node* root = nullptr; - insert(root, 10); - insert(root, 20); - insert(root, 15); - // 可以继续实现其他操作,如查找、删除等 + Node* root = nullptr; // 初始化根节点为空 + // 插入节点 + root = insert(root, 10); + root = insert(root, 20); + root = insert(root, 30); + root = insert(root, 40); + root = insert(root, 50); + root = insert(root, 25); + + // 中序遍历结果 + cout << "中序遍历结果: "; + inOrder(root); // 输出: 10 20 25 30 40 50 return 0; } ``` -这个示例展示了如何插入节点并保持笛卡尔树的性质。可以根据需要扩展实现其他功能,比如查找和删除。 -## 平衡树(AVL, treap, splay) +### 代码说明 + +1. **节点结构**:定义了一个`Node`结构体,包含键值、左右子树指针和高度信息。 +2. **高度和平衡因子计算**:`getHeight`和`getBalance`函数分别用于获取节点的高度和平衡因子。 +3. **旋转操作**:实现了右旋和左旋操作,以保持树的平衡。 +4. **插入操作**:插入节点时,首先按照二叉搜索树的规则插入,然后更新高度并检查是否需要旋转来保持平衡。 +5. **中序遍历**:实现了中序遍历,输出树的节点值,确保按升序排列。 + # 常见图 ## 稀疏图 +稀疏图(sparse graph)是指边的数量相对较少的图。在图论中,通常用图的顶点数量 \( V \) 和边数量 \( E \) 来描述图的稀疏性。对于稀疏图,边的数量 \( E \) 通常满足以下条件: +\[ E \ll V^2 \] + +这意味着对于一个拥有 \( V \) 个顶点的图,边的数量远远小于 \( V^2 \)。例如,在一个稀疏图中,边的数量可能在 \( O(V) \) 或 \( O(V \log V) \) 的数量级,而不是 \( O(V^2) \)。 + +稀疏图的特点包括: + +1. **存储效率**:由于边的数量较少,稀疏图通常使用邻接表(adjacency list)存储,而不是邻接矩阵(adjacency matrix),以节省存储空间。 + +2. **算法选择**:在处理稀疏图时,一些算法(如 Dijkstra 算法和 Prim 算法)在稀疏图上会有更好的性能,因为它们可以更高效地处理较少的边。 + +3. **应用场景**:稀疏图常见于实际应用中,例如社交网络、道路网络和互联网连接等,这些场景中节点(顶点)相对较多,但连接(边)通常较少。 ## 偶图(二分图) +二分图(Bipartite Graph)是一个特殊的图结构,其顶点可以被分为两个不相交的子集 \( U \) 和 \( V \),并且图中每条边连接的两个顶点分别来自这两个子集。换句话说,二分图中的每条边都连接一个属于 \( U \) 的顶点和一个属于 \( V \) 的顶点。 +### 特征 +1. **颜色标记**:二分图可以用两种颜色标记顶点,使得任何一条边连接的两个顶点颜色不同。 +2. **无奇环**:二分图中不存在长度为奇数的环。这是二分图的一个重要性质,能够通过深度优先搜索(DFS)或广度优先搜索(BFS)来验证。 + +### 应用 +二分图在许多实际问题中非常有用,例如: +- **匹配问题**:如婚姻匹配、任务分配等。 +- **推荐系统**:用户和物品之间的关系。 +- **网络流问题**:如流量分配和最小割问题。 + +### 例子 +假设我们有一个二分图,顶点集 \( U = \{A, B, C\} \) 和 \( V = \{1, 2\} \),边集为 \( \{(A, 1), (A, 2), (B, 2), (C, 1)\} \)。在这个图中: +- \( A \) 和 \( B \) 都连接到 \( 2 \),而 \( C \) 连接到 \( 1 \)。 +- 我们可以将 \( U \) 的顶点涂成一种颜色(例如红色),而 \( V \) 的顶点涂成另一种颜色(例如蓝色),确保相连的顶点颜色不同。 ## 欧拉图 - +欧拉图是指一种特殊的图,它包含一条经过图中每一条边恰好一次的闭合路径,称为欧拉回路。若图中存在这样的路径但不要求回到起点,则称为欧拉路径。对于一个连通的图,存在欧拉回路的必要条件是每个顶点的度数为偶数;而存在欧拉路径的条件是最多有两个顶点的度数为奇数。欧拉图在图论和网络分析中有广泛的应用。 ## 有向无环图 +有向无环图(Directed Acyclic Graph,简称 DAG)是一种图结构,其特点是: + +1. **有向性**:图中的边是有方向的,即每条边都有一个起始顶点和一个终止顶点。也就是说,从一个顶点到另一个顶点的边只能单向访问。 + +2. **无环性**:在图中不存在从某个顶点出发,通过若干条边再次回到该顶点的路径。换句话说,DAG 中没有环。 + +由于这些特性,DAG 被广泛应用于多个领域,包括: + +- **任务调度**:在多任务环境中,DAG 可以用来表示任务之间的依赖关系,确保在执行某个任务之前,所有依赖的任务都已完成。 + +- **版本控制系统**:例如 Git 使用 DAG 来管理提交历史,确保每个提交都有一个线性的历史。 + +- **数据处理管道**:在数据处理或流处理系统中,DAG 用于表示数据流的处理步骤。 + +- **拓扑排序**:DAG 可以进行拓扑排序,生成一个线性序列,使得对于每一条有向边,起始顶点在终止顶点之前。 ## 连通图与强连通图 +在图论中,连通图和强连通图是描述图中节点连接性质的两个重要概念。 +### 连通图 +一个无向图称为**连通图**,如果从图中的任意一个节点出发,都可以通过图中的边到达其他任意节点。这意味着图中的任意两个节点之间都有路径相连。如果一个无向图不是连通的,它会被分成多个连通分量,每个连通分量都是一个连通的子图。 + +### 强连通图 +一个有向图称为**强连通图**,如果图中的任意两个节点 \( u \) 和 \( v \) 都存在路径从 \( u \) 到 \( v \) 且从 \( v \) 到 \( u \)。也就是说,强连通图中的任意两个节点都是彼此可达的。如果一个有向图不是强连通的,它也可以分成多个强连通分量,每个强连通分量都是一个强连通的子图。 + +### 示例 +- **连通图**:考虑一个无向图,节点 A、B、C 和 D 之间的边连接形成一个连通图,因为从任意节点出发都可以访问到其他节点。 + +- **强连通图**:考虑一个有向图,节点 A、B、C 之间有边 A→B、B→C 和 C→A,这样就形成了一个强连通图,因为每对节点之间都可以找到相应的路径。 ## 双连通图 +双连通图(biconnected graph)是图论中的一个重要概念。一个无向图是双连通的,如果它是连通的,并且去掉任意一个顶点后,剩下的图仍然是连通的。换句话说,在双连通图中,任意两个顶点之间有至少两条不同的路径连接它们,这些路径不会经过同一个顶点。 +双连通图的一个重要性质是它的桥(bridge),即边的删去会导致图不连通。双连通图没有桥,因此,任何边的删去都不会破坏图的连通性。 + +双连通分量(biconnected component)是双连通图的一个子图,它是最大双连通子图的集合。可以使用深度优先搜索(DFS)来找到图的所有双连通分量。 # 哈希表 ## 数值哈希函数构造 diff --git a/senior/image/3.数据结构/1729735106127.png b/senior/image/3.数据结构/1729735106127.png new file mode 100644 index 0000000..a9b10df Binary files /dev/null and b/senior/image/3.数据结构/1729735106127.png differ