红黑树/红黑树迭代器封装(C++)

        本篇将会较为全面的讲解有关红黑树的特点,插入操作,然后使用代码模拟实现红黑树,同时还会封装出红黑树的迭代器。

        在 STL 库中的 set 和 map 都是使用红黑树封装的,在前文中我们讲解了 AVL树,对于红黑树和 AVL 树来说,这两种树都是效率很高的搜索二叉树,但是相对而言 AVL 树会更加接近平衡二叉树,但是用于封装 set 和 map 的却是红黑树,这是因为虽然红黑树不是很接近平衡二叉树,但是和 AVL 树的搜索效率相比较其实相差不是很多,相反而言,对于红黑树在插入时的效率会比 AVL 树更高,所以封装 set 和 map 选择使用了红黑树。

        如下:

目录

1. 红黑树

All code

红黑树的特性

红黑树节点的定义

红黑树的插入操作

红黑树检测与暴力测试

2. 红黑树迭代器封装

 迭代器的测试

1. 红黑树

All code

#pragma once
#include <iostream>
#include <vector>
#include <assert.h>
using namespace std;enum Colour { RED, BLACK };// 节点
template <class K, class V>
struct BRTreeNode {BRTreeNode<K, V>* _left;BRTreeNode<K, V>* _right;BRTreeNode<K, V>* _parent;pair<K, V> _kv;Colour _col;BRTreeNode(const pair<K, V>& kv): _left(nullptr), _right(nullptr), _parent(nullptr), _kv(kv), _col(RED){}
};// k v
template <class K, class V>
class Iterator {typedef Iterator Self;typedef BRTreeNode<K, V> Node;typedef pair<K, V>& Ref;typedef pair<K, V>* Ptr;
public:Iterator(Node* node):_node(node){}Ref operator*() {return _node->_kv;}Ptr operator->() {return &(_node->_kv);}Self operator++() {// left middle right// 如果有右孩子节点,找出右孩子节点中的最左节点// 如果没有右孩子节点,需要不断向上迭代,找到最左节点if (_node->_right != nullptr) {Node* leftMin = _node->_right;while (leftMin && leftMin->_left) {leftMin = leftMin->_left;}_node = leftMin;}else {Node* parent = _node->_parent;Node* cur = _node;while (parent && cur != parent->_left) {cur = parent;parent = cur->_parent;}_node = parent;}return *this;}Self operator--() {// 若当前迭代器为null,返回最右节点// left middle right// 如果有左孩子,找出左孩子中的最大节点// 如果没有左孩子,需要向上迭代if (_node->_left != nullptr) {Node* rightMin = _node->_left;while (rightMin && rightMin->_right) {rightMin = rightMin->_right;}_node = rightMin;}else {Node* parent = _node->_parent;Node* cur = _node;while (parent && cur != parent->_right) {cur = parent;parent = cur->_parent;}_node = parent;}return *this;}bool operator!=(const Self& it) {return _node != it._node;}private:Node* _node;
};template <class K, class V>
class BRTree {typedef BRTreeNode<K, V> Node;
public:typedef Iterator<K, V> iterator;// 封装迭代器iterator begin() {// 找到最左节点Node* leftMin = _root;while (leftMin && leftMin->_left) {leftMin = leftMin->_left;}return iterator(leftMin);}iterator end() {return iterator(nullptr);}// 插入bool insert(const pair<K, V>& kv) {if (_root == nullptr) {_root = new Node(kv);_root->_col = BLACK;return true;}Node* cur = _root;Node* parent = nullptr;while (cur) {if (kv.first < cur->_kv.first) {parent = cur;cur = cur->_left;}else if (kv.first > cur->_kv.first) {parent = cur;cur = cur->_right;}else {// 遇见相同元素return false;}}cur = new Node(kv);if (parent->_kv.first < cur->_kv.first)parent->_right = cur;elseparent->_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) {parent->_col = BLACK;uncle->_col = BLACK;grandfather->_col = RED;// 更新cur = grandfather;parent = cur->_parent;}else {// 现在这种情况属于叔叔节点不存在或者存在为黑// 需要判断当前属于什么情况if (cur == parent->_left) {//      g           p//   p     u -->  c   g// c                    u// 右旋即可RotateRight(grandfather);parent->_col = BLACK;cur->_col = grandfather->_col = RED;}else {//      g            g          c//   p     u -->   c   u -->  p   g//     c         p                   u// 先左旋,后右旋RotateLeft(parent);RotateRight(grandfather);cur->_col = BLACK;parent->_col = grandfather->_col = RED;}break;}}else {Node* uncle = grandfather->_left;// 一共存在两种情况,叔叔节点存在且叔叔节点为黑色if (uncle && uncle->_col == RED) {parent->_col = BLACK;uncle->_col = BLACK;grandfather->_col = RED;// 更新cur = grandfather;parent = cur->_parent;}else {if (cur == parent->_right) {//     g             p //   u   p   -->   g   c//         c     u// 对g进行左旋RotateLeft(grandfather);grandfather->_col = cur->_col = RED;parent->_col = BLACK;}else {//     g             g            c//   u   p   -->   u   c   -->  g   p//      c                p    u// 先右旋,后左旋RotateRight(parent);RotateLeft(grandfather);cur->_col = BLACK;grandfather->_col = parent->_col = RED;}break;}}}_root->_col = BLACK;return true;}// 右旋void RotateRight(Node* parent) {Node* subL = parent->_left;Node* subLR = subL->_right;Node* grandfather = parent->_parent;parent->_left = subLR;if (subLR) subLR->_parent = parent;parent->_parent = subL;subL->_right = parent;if (grandfather == nullptr) {_root = subL;_root->_parent = nullptr;}else if (grandfather->_left == parent) {grandfather->_left = subL;subL->_parent = grandfather;}else if (grandfather->_right == parent) {grandfather->_right = subL;subL->_parent = grandfather;}else {assert(false);}}void RotateLeft(Node* parent) {Node* subR = parent->_right;Node* subRL = subR->_left;Node* grandfather = parent->_parent;parent->_right = subRL;if (subRL) subRL->_parent = parent;parent->_parent = subR;subR->_left = parent;if (grandfather == nullptr) {_root = subR;_root->_parent = nullptr;}else if (grandfather->_left == parent) {grandfather->_left = subR;subR->_parent = grandfather;}else if (grandfather->_right == parent) {grandfather->_right = subR;subR->_parent = grandfather;}else {assert(false);}}void InOrder() {_InOrder(_root);cout << endl;}bool isBalance() {// 需要检查黑色节点个数,需要检查红色节点的分布int refNum = 0;Node* cur = _root;while (cur) {if (cur->_col == BLACK)refNum++;cur = cur->_left;}return _Cheak(_root, 0, refNum);}
private:bool _Cheak(Node* root, int blackNum, int refNum) {if (root == nullptr) {if (refNum != blackNum) {cout << ":存在黑节点不平衡" << endl;return false;}return true;}if (root->_col == RED && root->_parent->_col == RED) {cout << root->_kv.first << ":存在连续红节点" << endl;return false;}if (root->_col == BLACK) blackNum++;return _Cheak(root->_left, blackNum, refNum) && _Cheak(root->_right, blackNum, refNum);}void _InOrder(Node* root) {if (root == nullptr)return;_InOrder(root->_left);cout << root->_kv.first << " " << root->_kv.second << endl;_InOrder(root->_right);}
private:Node* _root = nullptr;
};

红黑树的特性

       首先先给出红黑树的概念

        红黑树是一种二叉搜索树在每个节点上增加一共存储位表示节点的颜色,可以为 Red 或者 Black,通过对任何一条从根节点到叶子节点上各个节点着色方式的限制,红黑树确保没有一条路径会比其他路径长出两倍,所以接近平衡。

关于红黑树的特性,一共可以总结为以下四点:

        1. 每个节点不是黑色就是红色;

        2. 根节点是黑色的;

        3. 如果一个节点是红色的,则他的两个孩子节点的颜色是黑色的。

        4. 对于每个节点,从该节点到其所有后代叶节点的简单路径上,均包含相同数目的黑色节点;

        5. 对于叶子节点都是黑色的(这里所指的黑色节点是指的是空结点)。

        红黑树的特性如上,那么关于红黑树是如何做到从根节点到叶子节点的路径中,最长的不会超过最短的两倍的呢

        其实关于这个答案很简单,这是由上的1、3、4条性质所决定的,每条路径的黑色节点个数相同,并且一个节点不是黑色节点就是红色节点,所以对于最短路径可能为全都是黑色节点的路径,对于最长路径也不过是红色节点和黑色节点相混合的(因为红色节点的子节点必须为黑色节点),刚好是最短路径的两倍。

红黑树节点的定义

        首先先给出红黑树的节点定义,该节点的定义和平衡二叉树十分相似,节点的指针包含父节点的指针、左右孩子的节点的指针,然后还有表示保存演示的变量,然后就是保存键值的变量,如下:

// 颜色
enum Colour { RED, BLACK };// 节点
template <class K, class V>
struct BRTreeNode {BRTreeNode<K, V>* _left;BRTreeNode<K, V>* _right;BRTreeNode<K, V>* _parent;pair<K, V> _kv;Colour _col;BRTreeNode(const pair<K, V>& kv): _left(nullptr), _right(nullptr), _parent(nullptr), _kv(kv), _col(RED){}
};

        如上的构造函数,我们将最新生成的节点默认为红色节点,那么为什么需要将节点默认设置为红色节点呢?

        这是因为倘若我们将节点的颜色设置为黑色节点,那么每一次在叶子节点处插入的节点也是黑色节点,但是这个使用就会出现一个问题,也就是刚插入节点的分支相对于其他分支多了一个黑色节点,和其他的性质对不上了。但是当我们插入的节点为红色节点的时候,就算是在红色节点下添加红色节点,我们也可以通过不断向上进行调整,调整到符合红黑树的性质。

红黑树的插入操作

        关于红黑树的操作而言,其大致逻辑和二叉搜索树一致,不过我们需要在插入之后对插入的节点进行调整,调整到满足红黑树的特性。上面我们已经说过,我们每一次插入是节点为红色节点,所以现在我们需要分别讨论出现连续红色节点的调整方法:

        首先先给出一些特殊的节点:插入的节点为根节点的时候,直接将根节点的颜色设置为黑色。另外,若插入在黑色节点的后面就不需要调整

        由于二叉树是对称的,所以对应的操作也是对称的,出现一种情况就会有与之对应的第二种情况出现(比如插入的位置为爷爷节点的左子树或者右子树),所以只需要分析一种即可:

        关于红黑树的调整,其中一个比较关键的节点就算叔叔节点(父亲节点的兄弟节点),叔叔节点的特性将决定我们如何调整我们的红黑树。有关叔叔节点的特性,一共存在两种情况:(叔叔节点存在且不为红色),以及(叔叔节点不存在或者叔叔节点存在为黑色)。首

        先先谈论叔叔节点存在且为红色节点的情况

        如上所示的调整方法,当我们插入的位置为红色节点的后面,并且叔叔节点存在且为红色,我们可以将爷爷节点调整为红色,然后将父亲节点和叔叔节点调整为黑色节点即可,这样调整的话,也不会影响各子树的黑色节点的数量,同时还可以将连续的红色节点拆分,但是需要注意的一点是,关于爷爷节点的上方的节点,若原爷爷节点上方的节点为红色节点,那么我们还需要进行迭代调整

        第二种情况,叔叔不存在或者叔叔节点存在且为黑色节点的情况,如下:

        如上图所示,叔叔节点存在且为黑和叔叔节点不存在,进行的操作其实是相同的:我们只需要对爷爷节点进行右旋,然后将父节点设置为黑色节点,然后将爷爷节点设置为红色即可,根本不需要管叔叔节点处于什么样的状况。并且这样调整之后,不需要继续向上调整,这是因为调整之后原爷爷节点的位置还是黑色,所以不用在继续向上调整。

        以上只是当插入节点的位置为父节点的左边,还会有插入节点为父节点的右边的情况,如下:

        对于这种情况我们只需要对父节点进行左旋,然后对爷爷节点右旋。就可以解决问题了。

        关于如上的情况我们一直讨论情况是插入的节点在爷爷节点的左边,所以当插入在爷爷节点右边的时候,只需要相对以上情况对称选择调整或者向上调整即可。另外关于旋转的代码直接给出。

        所以关于的插入的代码为:

// 插入
bool insert(const pair<K, V>& kv) {if (_root == nullptr) {_root = new Node(kv);_root->_col = BLACK;return true;}Node* cur = _root;Node* parent = nullptr;while (cur) {if (kv.first < cur->_kv.first) {parent = cur;cur = cur->_left;}else if (kv.first > cur->_kv.first) {parent = cur;cur = cur->_right;}else {// 遇见相同元素return false;}}cur = new Node(kv);if (parent->_kv.first < cur->_kv.first)parent->_right = cur;elseparent->_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) {parent->_col = BLACK;uncle->_col = BLACK;grandfather->_col = RED;// 更新cur = grandfather;parent = cur->_parent;}else {// 现在这种情况属于叔叔节点不存在或者存在为黑// 需要判断当前属于什么情况if (cur == parent->_left) {//      g           p//   p     u -->  c   g// c                    u// 右旋即可RotateRight(grandfather);parent->_col = BLACK;cur->_col = grandfather->_col = RED;}else {//      g            g          c//   p     u -->   c   u -->  p   g//     c         p                   u// 先左旋,后右旋RotateLeft(parent);RotateRight(grandfather);cur->_col = BLACK;parent->_col = grandfather->_col = RED;}break;}}else {Node* uncle = grandfather->_left;// 一共存在两种情况,叔叔节点存在且叔叔节点为黑色if (uncle && uncle->_col == RED) {parent->_col = BLACK;uncle->_col = BLACK;grandfather->_col = RED;// 更新cur = grandfather;parent = cur->_parent;}else {if (cur == parent->_right) {//     g             p //   u   p   -->   g   c//         c     u// 对g进行左旋RotateLeft(grandfather);grandfather->_col = cur->_col = RED;parent->_col = BLACK;}else {//     g             g            c//   u   p   -->   u   c   -->  g   p//      c                p    u// 先右旋,后左旋RotateRight(parent);RotateLeft(grandfather);cur->_col = BLACK;grandfather->_col = parent->_col = RED;}break;}}}_root->_col = BLACK;return true;
}void RotateRight(Node* parent) {Node* subL = parent->_left;Node* subLR = subL->_right;Node* grandfather = parent->_parent;parent->_left = subLR;if (subLR) subLR->_parent = parent;parent->_parent = subL;subL->_right = parent;if (grandfather == nullptr) {_root = subL;_root->_parent = nullptr;}else if (grandfather->_left == parent) {grandfather->_left = subL;subL->_parent = grandfather;}else if (grandfather->_right == parent) {grandfather->_right = subL;subL->_parent = grandfather;}else {assert(false);}
}void RotateLeft(Node* parent) {Node* subR = parent->_right;Node* subRL = subR->_left;Node* grandfather = parent->_parent;parent->_right = subRL;if (subRL) subRL->_parent = parent;parent->_parent = subR;subR->_left = parent;if (grandfather == nullptr) {_root = subR;_root->_parent = nullptr;}else if (grandfather->_left == parent) {grandfather->_left = subR;subR->_parent = grandfather;}else if (grandfather->_right == parent) {grandfather->_right = subR;subR->_parent = grandfather;}else {assert(false);}
}

红黑树检测与暴力测试

        将根据以上写出的代码给出对应的检测代码,其中主要检测红黑树是否平衡(满足红黑树特性),如下:

bool _Cheak(Node* root, int blackNum, int refNum) {if (root == nullptr) {if (refNum != blackNum) {cout << ":存在黑节点不平衡" << endl;return false;}return true;}if (root->_col == RED && root->_parent->_col == RED) {cout << root->_kv.first << ":存在连续红节点" << endl;return false;}if (root->_col == BLACK) blackNum++;return _Cheak(root->_left, blackNum, refNum) && _Cheak(root->_right, blackNum, refNum);
}bool isBalance() {// 需要检查黑色节点个数,需要检查红色节点的分布int refNum = 0;Node* cur = _root;while (cur) {if (cur->_col == BLACK)refNum++;cur = cur->_left;}return _Cheak(_root, 0, refNum);
}

        检测代码如下:

void BRTreeTest01() {int a[] = { 4, 2, 6, 1, 3, 5, 15, 7, 16, 14,16, 3, 7, 11, 9, 26, 18, 14, 15 };// {16, 3, 7, 11, 9, 26, 18, 14, 15}BRTree<int, int> tree;for (auto e : a) {tree.insert(make_pair(e, e));}tree.InOrder();cout << tree.isBalance() << endl;
}void BRTreeTest02() {const int N = 1000000;vector<int> v;v.reserve(N);srand(time(0));for (int i = 0; i < N; i++) {v.push_back(rand() + 1);}size_t begin1 = clock();BRTree<int, int> tree;for (auto e : v)tree.insert({ e, e });size_t end1 = clock();cout << "insert" << end1 - begin1 << endl;cout << tree.isBalance() << endl;}

        其中第一个测试为普通测试,第二个测试为压力测试。如下:

2. 红黑树迭代器封装

        我们前文中已经提到关于红黑树可以用于封装 set 和 map,那么关于这两个容器也会存在迭代器,我们现在直接在红黑树阶段封装一个迭代器。

        关于红黑树的迭代器,其中主要指向的是红黑树的节点,所以我们的迭代器主要为一个指向节点的指针,然后我们还需要对我们的迭代器进行运算符重载,比如常用的 * 解引用,  ->,还有 != 操作符。还有++ 与 -- 运算符重载。如下:

template <class K, class V>
class Iterator {typedef Iterator Self;typedef BRTreeNode<K, V> Node;typedef pair<K, V>& Ref;typedef pair<K, V>* Ptr;
public:Iterator(Node* node):_node(node){}Ref operator*() {return _node->_kv;}Ptr operator->() {return &(_node->_kv);}Self operator++() {// left middle right// 如果有右孩子节点,找出右孩子节点中的最左节点// 如果没有右孩子节点,需要不断向上迭代,找到最左节点if (_node->_right != nullptr) {Node* leftMin = _node->_right;while (leftMin && leftMin->_left) {leftMin = leftMin->_left;}_node = leftMin;}else {Node* parent = _node->_parent;Node* cur = _node;while (parent && cur != parent->_left) {cur = parent;parent = cur->_parent;}_node = parent;}return *this;}Self operator--() {// left middle right// 如果有左孩子,找出左孩子中的最大节点// 如果没有左孩子,需要向上迭代if (_node->_left != nullptr) {Node* rightMin = _node->_left;while (rightMin && rightMin->_right) {rightMin = rightMin->_right;}_node = rightMin;}else {Node* parent = _node->_parent;Node* cur = _node;while (parent && cur != parent->_right) {cur = parent;parent = cur->_parent;}_node = parent;}return *this;}bool operator!=(const Self& it) {return _node != it._node;}private:Node* _node;
};

        如上的操作中,最为麻烦的操作就算 ++ 和 --,因为在红黑树中的节点并不是连续的,而是相互之间都是使用左右子树和父亲指针所维护的指针所连接起来的。

        对于 ++ 操作,我们需要取得下一个节点,根据中序遍历原则(左 中 右),当右子树不为空的时候,返回右子树中的最小节点,若右子树为空的时候,这个时候只能向上迭代,直到为祖先节点的左子树的时候,就可以停止迭代了,这个祖先节点就是我们要找的节点。

        -- 运算符重载和 ++ 相似,只不过一切操作都是相反的而已。

        另外,我们还需要在红黑树中定义 begin 和 end 函数,关于 begin 函数就是返回树的最左节点,但是关于 end 函数,我们这里直接返回的是指向 nullptr 的迭代器,但是这样会存在一个问题,也就是若我们直接给一个指向 end 的迭代器,我们并不能找到以上的树中的节点。

 迭代器的测试

        关于迭代器的测试如下:

void BRTreeTest03() {int a[] = { 4, 2, 6, 1, 3, 5, 15, 7, 16, 14,16, 3, 7, 11, 9, 26, 18, 14, 15 };BRTree<int, int> tree;for (auto e : a) tree.insert(make_pair(e, e));auto it = tree.begin();while (it != tree.end()) {cout << it->first << ":" << it->second << endl;++it;}
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/diannao/26003.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【设计模式】创建型设计模式之 原型模式

介绍 原型模式是一种创建型设计模式&#xff0c;主要用于创建重复的对象&#xff0c;而无需重新初始化它们&#xff0c;从而提高效率并简化对象的创建过程。此模式的核心思想是利用已存在的对象实例&#xff0c;通过复制&#xff08;克隆&#xff09;的方式来生成新的对象&…

k8s 1.28 搭建rabbitmq集群

1.环境 1.1 k8s 1.28 1.2 rabbit 3.8 1.3 工作空间default 1.4 注意&#xff0c;内存最好充足一点&#xff0c;因为我就两个节点一个master、一个node&#xff0c;起初我的node是8g&#xff0c;还剩3~4G&#xff0c;集群竟然一直起不来&#xff0c;后来将虚拟机内存扩大&#x…

Word中插入Mathtype右编号,调整公式与编号的位置

当你已经将mathtype内置于word后&#xff0c;可以使用右编号快速插入公式 但是往往会出现公式和编号出现的位置或之间的距离不合适 比如我在双栏下插入公式&#xff0c;会发现插入的公式与编号是适用于单栏的 解决办法&#xff1a; 开始->样式->MTDisplayLquation -&g…

37python数据分析numpy基础之save以二进制保存数组数据到文件

1 python数据分析numpy基础之save以二进制保存数组数据到文件 python的numpy库的save(file,arr)函数&#xff0c;将数组以二进制格式保存到一个npy后缀的文件中。 用法 numpy.save(file, arr, allow_pickleTrue, fix_importsTrue)描述 numpy.save(file,arr)&#xff0c;可以…

AWT常用组件

AWT中常用组件 前言一、基本组件组件名标签(Label类)Label类的构造方法注意要点 按钮(Button)Button的构造方法注意要点 文本框(TextField)TextField类的构造方法注意要点 文本域&#xff08;TextArea&#xff09;TextArea 的构造方法参数scrollbars的静态常量值 复选框&#x…

【Spring Boot】Spring Boot 的世界之旅1

目录 1 Spring Boot 的诞生背景 2 Spring Boot 的核心价值 3 为什么选择Spring Boot 4 Spring Boot 与传统Spring应用的对比 5 踏上Spring Boot之旅 1 Spring Boot 的诞生背景 在软件开发的历史长河中&#xff0c;随着技术的不断演进&#xff0c;开发者们面临着越来越多的…

Java基础知识:为面试做好准备

基本概念 Java的特性&#xff1a;Java是一门面向对象的编程语言&#xff0c;具有跨平台性、自动内存管理等特点。Java平台的组成&#xff1a;Java平台主要分为Java SE&#xff08;Standard Edition&#xff09;、Java EE&#xff08;Enterprise Edition&#xff09;和Java ME&…

排序-读取数据流并实时返回中位数

目录 一、问题描述 二、解题思路 1.顺序表排序法 2.使用大根堆、小根堆 三、代码实现 1.顺序表排序法实现 2.大根堆、小根堆法实现 四、刷题链接 一、问题描述 二、解题思路 1.顺序表排序法 &#xff08;1&#xff09;每次读取一个数就对列表排一次序&#xff0c;对排…

如何使用Python中的枚举类型(enum)

在Python中&#xff0c;枚举类型可以通过内置的enum模块来实现。枚举类型是一种特殊的类&#xff0c;它用于定义一组命名的常量。这些常量通常用于表示固定的、有限的集合的值&#xff0c;比如一周的几天、颜色的名称等。 下面是如何使用Python中的enum模块来定义和使用枚举类…

AQS实现原理

AQS&#xff08;AbstractQueuedSynchronizer&#xff09;是一个用于构建锁和同步器的框架&#xff0c;许多同步器都可以通过AQS很容易并且高效地构造出来。 不仅 ReentrantLock 和 Semaphore 是基于AQS构建的&#xff0c;还包括 CountDownLatch、ReentrantReadWriteLock、Synch…

速盾:图片cdn加速 免费

随着互联网的快速发展&#xff0c;图片在网页设计和内容传播中起着重要的作用。然而&#xff0c;随着网站访问量的增加和图片文件大小的增加&#xff0c;图片加载速度可能会成为一个问题。为了解决这个问题&#xff0c;许多网站使用图片CDN加速服务。 CDN&#xff08;Content …

Oracle函数有哪些

目录 数值函数 字符串函数 日期函数 转换函数 聚合函数 分析函数 Oracle数据库提供了大量的内置函数,这些函数可以分为多个类别,每个类别都有特定的用途。以下是一些常见的Oracle函数及其简要描述。 数值函数 ABS(n):返回数字的绝对值。 CEIL(n)或CEILING(n):返回大…

Python异步爬虫批量下载图片-协程

import aiofiles import aiohttp import asyncio import requests from lxml import etree from aiohttp import TCPConnectorclass Spider:def __init__(self, value):# 起始urlself.start_url value# 下载单个图片staticmethodasync def download_one(url):name url[0].spl…

Redis 5种常用数据类型

目录 Redis简介 1.字符串 string 2.哈希 hash 3.列表 list 4.集合 set 5.有序集合 sorted set / zset Redis简介 Redis&#xff0c;全称Remote Dictionary Server&#xff0c;是一个开源的、内存中的数据结构存储系统。它可以用作数据库、缓存和消息中间件&#xff0c;支…

Hash String 学习笔记

目录 咕咕咕 Trie 树/字典树 P8306 【模板】字典树 咕咕咕&#xff08;感觉比较简单&#xff08;吗&#xff09;&#xff09;&#xff08;我才不会说是我懒呢&#xff09; KMP 一个求最长公共前后缀的东西 P3375 【模板】KMP 写法一 #include<bits/stdc.h> using name…

【JavaScript脚本宇宙】表格大变身:探秘JavaScript库的数据表格魔法

优化数据展示&#xff1a;精选JavaScript表格增强库对比 前言 在现代Web开发中&#xff0c;利用各种库和框架来增强数据表格的功能已经成为常态。通过使用特定的JavaScript库和插件&#xff0c;开发人员可以轻松地实现交互性强、美观且高性能的数据表格&#xff0c;从而提升用…

JavaScript前端技术入门教程

引言 在前端开发的广阔天地中&#xff0c;JavaScript无疑是最耀眼的一颗明星。它赋予了网页动态交互的能力&#xff0c;让网页从静态的文本和图片展示&#xff0c;进化为可以与用户进行实时交互的丰富应用。本文将带您走进JavaScript的世界&#xff0c;为您提供一个入门级的教…

Nginx访问日志

Nginx日志是Nginx Web服务器产生的记录文件&#xff0c;主要用于跟踪和分析服务器的访问情况以及错误信息。Nginx日志主要分为两大类&#xff1a;访问日志 (access_log): 访问日志记录了每一次客户端对Nginx服务器的HTTP请求的详细信息&#xff0c;这对于统计分析、流量监控、用…

SpringBoot3+Mybatis-Plus+h2数据库,入门Mybatis-Plus

SpringBoot3Mybatis-Plush2数据库&#xff0c;入门Mybatis-Plus mybatis-plus官网地址maven依赖数据库脚本配置文件实体类Mapper入门程序启动程序测试单元测试测试结果 Service层接口service层接口单元测试测试结果 项目结构 mybatis-plus官网地址 https://www.baomidou.com/ …

vue manually select

1 vuex 一个包含多个页面的应用程序&#xff0c;每个页面包含多个组件&#xff0c;这些组件拥有各自的表单&#xff0c;并且希望这些表单展示的数据能够在不同组件之间共享&#xff0c;那么可以使用 Vuex 来管理这些数据。在这种情况下&#xff0c;您可以将这些需要共享的数据存…