基于红黑树对map和set的封装

前言

前面我们已经对红黑树做了介绍和实现,本期我们来对红黑树进一步改造,然后基于改造后的红黑树封装出map和set!

本期内容介绍

• 红黑树的改造

• 红黑树的迭代器实现

• map的封装

• set的封装

• 全部源码

● 红黑树的改造

我们目前的实现的红黑树,里面存的是一个pair<K,V>的键值对,但是我们知道set是只有key没有val的,map是<key,val>的!而他两都是基于红黑树实现的,难道是弄两棵红黑树分别封装?这是不是太cuo了呀!显然不是的,库里面他两底层用的是一颗红黑树:

这里人家是将底层存的数据没有写死,给了一个Value的类型!你上层如果封装成map就给pair<K,V>,如果是封装成set就给K即可!(后面的比较器和空间配置器暂时就不看了)

• 问题一:set使用时是一个key为什么底层也要传给红黑树两个key?

由于map和set的底层都用的是同一棵红黑树,而map存的是K,V类型的键值对,所以这里可以认为是set对map的迁就!所以set即使用的时候只是一个key,但底层还是给红黑树一样的V和K,目的是使得红黑树的模板参数的一致性!

• 问题二:红黑树的是在左插入等操作时,如何知道你是pair还是key呢?

的确红黑树那一层是不知道的,但是我们map和set层是知道的!如何让红黑树那一层知道呢?就是第三个参数,是一个获取存储值key的类;当map和set传过去之后,红黑树层可以创建对象获取!

OK,我们也改造一下自己的红黑树出来吧:(我们就把存储的数据类型改成T避免与V混淆)

节点类要就不在是K,V了,而是T了:

insert也是不再是插入(inert后面介绍到[]还会改造

OK,我们先把红黑树的构造和拷贝构造以及赋值拷贝给整出来,在上层例如set进行赋值,拷贝等操作,就会到红黑树这一层,如果红黑树没有就成了浅拷贝了!

• 默认构造

这里本来可以不用写的,但是后面如果实现了赋值拷贝后就自动生不成了,所以得写!

RBTree():_root(nullptr)
{}

还有一种就是即使有了拷贝构造可以强制让编译器生成:

RBTree() = default;//强制让编译器生成默认构造

• 拷贝构造

这里采用二叉树的前序逐一拷贝即可:

RBTree(const RBTree<K, T, KofT>& t)
{_root = Copy(t._root);
}
Node* Copy(Node* root)
{if (root == nullptr)return nullptr;//复制节点和颜色Node* newnode = new Node(root->_data);newnode->_col = root->_col;//复制左孩子newnode->_left = Copy(root->_left);if (newnode->_left)//连接newnode->_left->_parent = newnode;//复制右孩子newnode->_right = Copy(root->_right);if (newnode->_right)//连接newnode->_right->_parent = newnode;return newnode;
}

• 赋值拷贝

直接采用以前的那种现在写法:将形参用值接受,然后与当前交换

RBTree<K, T, KofT>& operator=(RBTree<K, T, KofT> t)
{swap(_root, t._root);
}

• 析构函数

采用二叉树的后序先删除做孩子,再删除右孩子,最后删除根节点

~RBTree()
{Destory(_root);_root = nullptr;
}
void Destory(Node* root)
{if (root == nullptr)return;Destory(root->_left);Destory(root->_right);delete root;root = nullptr;
}

● 红黑树的迭代器实现

红黑树的迭代器和链表的一样,空间不是连续的无法用原生指针来实现,得改一个类来达到“连续”的行为!我们使用的迭代器无非就begin、end、!=, *,->,++等但是begin和end只有红黑树知道,所以我们在迭代器里面只需要实现!=, *, ->,++等即可:

迭代器本质就是也即是一个节点的指针,所以这里的迭代器类和链表的一模一样:

template<class T, class Ref, class Ptr>
struct _RBTree_Iterator
{typedef RBTreeNode<T> Node;typedef _RBTree_Iterator<T, Ref, Ptr> Self;Node* _node;_RBTree_Iterator(Node* node):_node(node){}
}

operator*

直接返回该节点的数据的引用即可!

Ref operator*()
{return _node->_data;
}

operator->

直接返回当前节点的数据的地址(指针)即可!

Ptr operator->()
{return &_node->_data;
}

operator !=

直接比较两个节点的地址是否不相等即可!

bool operator!=(const Self& s)
{return _node != s._node;
}

operator==

直接比较两个节点的地址是否相同即可!

bool operator==(const Self& s)
{return _node == s._node;
}

operator++

实现思路:因为红黑树迭代器走的是中序,即 左->根->右!而*的时候已经访问了该节点的左和他本身++是去找当前节点的下一个节点!所以此时只需要考虑,当前节点的有节点是是否为空!

1、如果当前节点的右节点不为空,下一个要访问的节点就是,右子树的最左节点!

2、如果当前节点的右节点为空,说当树全部都访问完了,此时就得向上找当前节点是父节点的左的父节点!下一个访问的就是这个父节点!

Self& operator++()
{if (_node->_right){Node* leftMin = _node->_right;while (leftMin->_left){leftMin = leftMin->_left;}_node = leftMin;}else{Node* cur = _node;Node* parent = cur->_parent;while (parent && cur == parent->_right){cur = parent;parent = parent->_parent;}_node = parent;}return *this;
}

operator++(int)

有了前置的++后置直接复用即可!后置++的特点是返回+1前的值,这里同理!我们先拷贝一份当前的迭代器,然后当前迭代器复用前置++到下一个要访问的节点,然后返回拷贝即可!

//后置++
Self operator++(int)
{Self tmp = *this;++(*this);return tmp;
}

operator--

前置++的思路是更具中序的:左->根->右!这里的--就和前置++相反:右->根->左!

1、当前节点的左不为空,下一个访问的节点就是当前节点左子树的最右节点!

2、当前节点的左为空,找当前节点是父节点的有孩子的父节点,下一个访问的是该父节点

// 前置--
Self& operator--()
{if (_node->_left){Node* cur = _node->_left;while (cur->_right){cur = cur->_right;}_node = cur;}else{Node* cur = _node, * parent = cur->_parent;while (parent && parent->_left == cur){cur = parent;parent = parent->_parent;}_node = parent;}return *this;
}

operator--(int)

后置--同理直接先拷贝一份当前节点,然后调用前置--带下一个访问的节点,最后返回该拷贝即可!

//后置--
Self operator--(int)
{Self tmp = *this;--(*this);return tmp;
}

begin

因为迭代器是按照中序走的,所以begin是整棵树的最左节点!

Iterator Begin()
{Node* leftMin = _root;while (leftMin && leftMin->_left){leftMin = leftMin->_left;}return Iterator(leftMin);
}

end

这里的end应该是最右节点的下一个节点,这里简单处理为nullptr;

Iterator End()
{return Iterator(nullptr);
}

const_begin

ConstIterator Begin() const
{Node* leftMin = _root;while (leftMin && leftMin->_left){leftMin = leftMin->_left;}return ConstIterator(leftMin);
}

const_end

ConstIterator End() const
{return ConstIterator(nullptr);
}

 ● map的封装

OK,先搞一个框架出来:

#pragma oncenamespace cp
{template<class K, class V>class map{struct MapKeyOfT{const K& operator()(const prai<K,V>& kv){return kv.first;}};public:private:RBTree<K, pair<const K, V>, MapKeyOfT> _m;//map的key不允许修改};
}

• 注意:map的key是不允许修改的所以用const修饰

OK,先来整迭代器出来,直接调用干红黑树的即可:

iterator

typedef typename RBTree<K, pair<const K,V>, MapKeyofT>::Iterator iterator;
typedef typename RBTree<K, pair<const K,V>, MapKeyofT>::ConstIterator const_iterator;iterator begin()
{return _m.Begin();
}iterator end()
{return _m.End();
}const_iterator begin() const 
{return _m.Begin();
}const_iterator end() const
{return _m.End();
}

注意:

typedef typename RBTree<K, const K, SetKofT>::Iterator iterator;
typedef typename RBTree<K, const K, SetKofT>::ConstIterator const_iterator;

这里的RBTree<K, const K, SetKofT>只是类型不是实体,所以编译器不认识,得加上typename告诉编译器这是类型;

find


iterator find(const K& key)
{return _m.Find(key);
}

insert

这里的insert,当前的红黑树是不要满足的,因为我们以前介绍使用的时候介绍过它的返回值是一个pair<iterator, bool>,如果插入成功返回当前插入节点的迭代器,bool设置为true;如果插入的元素失败,返回当前存在元素的迭代器,bool是false;

所以,我么先得改造红黑树的insert:挂在燥起来也不难,就是将返回值换成pair<iterator, bool>即可,唯一注意的一个点就是最后返回的那个节点的迭代器;一定要在前面提前记录变色前的那个新节点的指针,方便后面构造迭代器;如果不记录后面直接返回cur这个cur就不一定是前面的cur了因为会旋转 + 变色改变!!!

pair<Iterator, bool> Insert(const T& data)
{//第一次插入if (_root == nullptr){_root = new Node(data);_root->_col = BLACK;//根节点是黑色return make_pair(Iterator(_root), true);}Node* cur = _root;//当前节点Node* parent = nullptr;//当前节点的父节点KofT kot;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), false);//插入的值已经存在}}//找到了插入位置cur = new Node(data);Node* newnode = cur;//提前保存返回的元素的位置,方便构造迭代器//链接if (kot(cur->_data) > kot(parent->_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){grandfather->_col = RED;parent->_col = uncle->_col = BLACK;//继续更新cur = grandfather;parent = cur->_parent;}else//叔叔不存在或存在在且为黑 --> 旋转 + 变色{if (cur == parent->_left){//     g//   p   u// cRotateR(grandfather);//右旋parent->_col = BLACK;//变色grandfather->_col = RED;}else{//     g//   p   u//     cRotateL(parent);//旋转RotateR(grandfather);cur->_col = BLACK;//变色grandfather->_col = RED;}break;//旋转+变色完了就结束掉}}else{Node* uncle = grandfather->_left;//叔叔在爷爷的左//叔叔存在且为红 -> 变色if (uncle && uncle->_col == RED){grandfather->_col = RED;parent->_col = uncle->_col = BLACK;//继续更新cur = grandfather;parent = cur->_parent;}else//叔叔不存在或为黑 --> 旋转+变色{if (cur == parent->_left){//     g//   u   p//     cRotateR(parent);RotateL(grandfather);cur->_col = BLACK;grandfather->_col = RED;}else{//     g//   u   p//         cRotateL(grandfather);parent->_col = BLACK;//变色grandfather->_col = RED;}break;//旋转+变色完了就结束掉}}}_root->_col = BLACK;//保证根节点永远是黑色return make_pair(Iterator(newnode), true);
}

成功改造完红黑的的insert之后map和set的就直接可以调用了!

pair<iterator, bool>  insert(const pair<K, V>& kv)
{return _m.Insert(kv);
}

operator[]

[]是基于insert实现的,在介绍使用的时候介绍过,[]按照K值插入,不管插入成功与否,都返回V的引用~!

V& operator[](const K& key)
{pair<iterator, bool> ret = _m.Insert(make_pair(key, V()));return ret.first->second;
}

OK,测试一下:

void Print_map(const cp::map<string, int> m)
{for (auto& e : m){cout << e.first << " : " << e.second << endl;}
}void TestMap()
{cp::map<int, int> m1;m1.insert({ 1,1 });m1.insert({ 2,2 });m1.insert({ 3,3 });m1.insert({ 4,4 });m1.insert({ 5,5 });cp::map<int, int>::iterator it = m1.begin();while (it != m1.end()){cout << it->first << ":" << it->second << endl;it++;}cout << "--------------------" << endl;cp::map<string, int> m2;string s[] = { "aaa", "bbb", "ccc", "bbb", "cpdd", "000", "苹果", "西瓜", "aaa" };for (auto& e : s)m2[e]++;Print_map(m2);
}

● set的封装

同理,我们可以搭建出一个set的基本框架出来:

#pragma oncenamespace cp
{template<class K>class set{struct SetKeyOfT{const K& operator()(const K& key){return key;}};public:private:RBTree<K, K, SetKeyOfT> _s;};
}

OK,先来整迭代器出来,直接调用干红黑树的即可:

inerator

typedef typename RBTree<K, const K, SetKofT>::Iterator iterator;
typedef typename RBTree<K, const K, SetKofT>::ConstIterator const_iterator;iterator begin()
{return _s.Begin();
}iterator end()
{return _s.End();
}const_iterator begin() const
{return _s.Begin();
}const_iterator end() const
{return _s.End();
}

find

iterator find(const K& key)
{return _s.Find(key);
}

insert

pair<iterator, bool>  insert(const K& key)
{return _s.Insert(key);
}

OK,测试一下:

void Print_set(const cp::set<int>& s)
{for (auto& e : s){cout << e << endl;}
}void TestSet()
{vector<int> v = { 8, 3, 1, 10, 6, 4, 7, 14, 13, 8, 6 };cp::set<int> s;for (auto& e : v){s.insert(e);}Print_set(s);
}

OK,到这里map和set的封装就已经实现完了!一些接口例如size、clear等前面实现过很多次这里就不在实现了!这里想说的是map和set就是套了一层红黑树的壳,真正的核心还是红黑!

● 全部源码

RBTree.h

#pragma once#pragma onceenum Col
{RED = 0,BLACK
};template <class T>
struct RBTreeNode
{RBTreeNode<T>* _left;RBTreeNode<T>* _right;RBTreeNode<T>* _parent;T _data;Col _col;RBTreeNode(const T& data):_left(nullptr), _right(nullptr), _parent(nullptr), _data(data), _col(RED)//默认新的节点是红色{}
};template<class T, class Ref, class Ptr>
struct _RBTree_Iterator
{typedef RBTreeNode<T> Node;typedef _RBTree_Iterator<T, Ref, Ptr> Self;Node* _node;_RBTree_Iterator(Node* node):_node(node){}Ref operator*(){return _node->_data;}Ptr operator->(){return &_node->_data;}bool operator!=(const Self& s){return _node != s._node;}	bool operator==(const Self& s){return _node == s._node;}//前置++Self& operator++(){if (_node->_right){Node* leftMin = _node->_right;while (leftMin->_left){leftMin = leftMin->_left;}_node = leftMin;}else{Node* cur = _node;Node* parent = cur->_parent;while (parent && cur == parent->_right){cur = parent;parent = parent->_parent;}_node = parent;}return *this;}//后置++Self operator++(int){Self tmp = *this;++(*this);return tmp;}// 前置--Self& operator--(){if (_node->_left){Node* cur = _node->_left;while (cur->_right){cur = cur->_right;}_node = cur;}else{Node* cur = _node, * parent = cur->_parent;while (parent && parent->_right != cur){cur = parent;parent = parent->_parent;}_node = parent;}return *this;}//后置--Self operator--(int){Self tmp = *this;--(*this);return tmp;}
};template <class K, class T, class KofT>
class RBTree
{typedef RBTreeNode<T> Node;
public:typedef _RBTree_Iterator<T, T&, T*> Iterator;typedef _RBTree_Iterator<T, const T&, const T*> ConstIterator;RBTree() = default;//强制让编译器生成默认构造//RBTree()//	:_root(nullptr)//{}RBTree(const RBTree<K, T, KofT>& t){_root = Copy(t._root);}RBTree<K, T, KofT>& operator=(RBTree<K, T, KofT> t){swap(_root, t._root);}~RBTree(){Destory(_root);_root = nullptr;}Iterator Begin(){Node* leftMin = _root;while (leftMin && leftMin->_left){leftMin = leftMin->_left;}return Iterator(leftMin);}Iterator End(){return Iterator(nullptr);}ConstIterator Begin() const{Node* leftMin = _root;while (leftMin && leftMin->_left){leftMin = leftMin->_left;}return ConstIterator(leftMin);}ConstIterator End() const{return ConstIterator(nullptr);}Iterator Find(const K& key){KofT kot;Node* cur = _root;while (cur){if (kot(cur->_data) < key){cur = cur->_right;}else if (kot(cur->_data) > key){cur = cur->_left;}else{return Iterator(cur);}}return End();}pair<Iterator, bool> Insert(const T& data){//第一次插入if (_root == nullptr){_root = new Node(data);_root->_col = BLACK;//根节点是黑色return make_pair(Iterator(_root), true);}Node* cur = _root;//当前节点Node* parent = nullptr;//当前节点的父节点KofT kot;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), false);//插入的值已经存在}}//找到了插入位置cur = new Node(data);Node* newnode = cur;//提前保存返回的元素的位置,方便构造迭代器//链接if (kot(cur->_data) > kot(parent->_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){grandfather->_col = RED;parent->_col = uncle->_col = BLACK;//继续更新cur = grandfather;parent = cur->_parent;}else//叔叔不存在或存在在且为黑 --> 旋转 + 变色{if (cur == parent->_left){//     g//   p   u// cRotateR(grandfather);//右旋parent->_col = BLACK;//变色grandfather->_col = RED;}else{//     g//   p   u//     cRotateL(parent);//旋转RotateR(grandfather);cur->_col = BLACK;//变色grandfather->_col = RED;}break;//旋转+变色完了就结束掉}}else{Node* uncle = grandfather->_left;//叔叔在爷爷的左//叔叔存在且为红 -> 变色if (uncle && uncle->_col == RED){grandfather->_col = RED;parent->_col = uncle->_col = BLACK;//继续更新cur = grandfather;parent = cur->_parent;}else//叔叔不存在或为黑 --> 旋转+变色{if (cur == parent->_left){//     g//   u   p//     cRotateR(parent);RotateL(grandfather);cur->_col = BLACK;grandfather->_col = RED;}else{//     g//   u   p//         cRotateL(grandfather);parent->_col = BLACK;//变色grandfather->_col = RED;}break;//旋转+变色完了就结束掉}}}_root->_col = BLACK;//保证根节点永远是黑色return make_pair(Iterator(newnode), true);}void InOrder(){return _InOrder(_root);}bool IsBalance(){if (_root && _root->_col == RED)return false;//根节点不可能为红int black = 0;//根节点到任意一条从根节点到叶子节点的黑色节点的数目Node* cur = _root;while (cur){if (cur->_col == BLACK)black++;cur = cur->_left;}return Check(_root, black, 0);}private:Node* Copy(Node* root){if (root == nullptr)return nullptr;Node* newnode = new Node(root->_data);newnode->_col = root->_col;newnode->_left = Copy(root->_left);if (newnode->_left)newnode->_left->_parent = newnode;newnode->_right = Copy(root->_right);if (newnode->_right)newnode->_right->_parent = newnode;return newnode;}void Destory(Node* root){if (root == nullptr)return;Destory(root->_left);Destory(root->_right);delete root;root = nullptr;}void _InOrder(Node* root){if (root == nullptr)return;_InOrder(root->_left);cout << root->_kv.first << ":" << root->_kv.second << endl;_InOrder(root->_right);}bool Check(Node* root, const int black, int num){if (root == nullptr){if (num != black)return false;return true;}if (root->_col == RED && root->_parent && root->_parent->_col == RED){cout << "存在连续的红色节点:" << endl;return false;}if (root->_col == BLACK){++num;}return Check(root->_left, black, num) && Check(root->_right, black, num);}void RotateR(Node* parent){Node* subL = parent->_left;//父亲的左Node* subLR = subL->_right;//左子树的右Node* ppNode = parent->_parent;//parent的父节点,方便旋转后的链接parent->_left = subLR;//将左子树的右给父亲的做if (subLR)subLR->_parent = parent;subL->_right = parent;//parent做左子树的右parent->_parent = subL;if (parent == _root)//parent是根{_root = subL;//此时的新根就是subL_root->_parent = ppNode;}else//parent不是根{//将新的根连接到ppNodeif (ppNode->_left == parent){ppNode->_left = subL;}else{ppNode->_right = subL;}subL->_parent = ppNode;}}void RotateL(Node* parent){Node* subR = parent->_right;//父亲的右Node* subRL = subR->_left;//右子树的左Node* ppNode = parent->_parent;//parent的父节点,方便旋转后的链接parent->_right = subRL;//将右子树的左连接到parent的右if (subRL)subRL->_parent = parent;subR->_left = parent;//parent连接到subR的左parent->_parent = subR;if (parent == _root)//parent是根{_root = subR;//此时的新根就是subR_root->_parent = ppNode;}else//parent不是根{//将新的根连接到ppNodeif (ppNode->_left == parent){ppNode->_left = subR;}else{ppNode->_right = subR;}subR->_parent = ppNode;}}private:Node* _root = nullptr;
};

Mymap.h

#pragma oncenamespace cp
{template<class K, class V>class map{struct MapKeyofT{const K& operator()(const pair<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;iterator begin(){return _m.Begin();}iterator end(){return _m.End();}const_iterator begin() const{return _m.Begin();}const_iterator end() const{return _m.End();}iterator find(const K& key){return _m.Find(key);}pair<iterator, bool>  insert(const pair<K, V>& kv){return _m.Insert(kv);}V& operator[](const K& key){pair<iterator, bool> ret = _m.Insert(make_pair(key, V()));return ret.first->second;}private:RBTree<K, pair<const K, V>, MapKeyofT> _m;};
}

Myset.h

#pragma oncenamespace cp
{template<class K>class set{struct SetKofT{const K& operator()(const K& key){return key;}};public:typedef typename RBTree<K, const K, SetKofT>::Iterator iterator;typedef typename RBTree<K, const K, SetKofT>::ConstIterator const_iterator;iterator begin(){return _s.Begin();}iterator end(){return _s.End();}const_iterator begin() const{return _s.Begin();}const_iterator end() const{return _s.End();}iterator find(const K& key){return _s.Find(key);}pair<iterator, bool>  insert(const K& key){return _s.Insert(key);}private:RBTree<K, const K, SetKofT> _s;};
}

OK,本期分享就到这里,好兄弟我们下期再见~!

结束语:我们的目标是星辰大海!

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

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

相关文章

未来互联网的新篇章:深度解析Facebook的技术与战略

随着科技的飞速发展和社会的不断变迁&#xff0c;互联网作为全球信息交流的重要平台&#xff0c;正经历着前所未有的变革和演进。作为全球最大的社交媒体平台之一&#xff0c;Facebook不仅是人们沟通、分享和互动的重要场所&#xff0c;更是科技创新和数字化进程的推动者。本文…

音视频开发—FFmpeg 从MP4文件中抽取视频H264数据

文章目录 MP4文件存放H264数据方式MP4 文件结构概述H.264 数据在 MP4 中的存储1. ftyp 盒子2. moov 盒子3. mdat 盒子 H.264 数据在 stsd 盒子中的存储&#xff08;AVC1&#xff09;AVC1与Annex-B 格式&#xff08;裸 H.264 流&#xff09;的区别 从MP4文件中提取H264裸流步骤&…

java使用easypoi模版导出word详细步骤

文章目录 第一步、引入pom依赖第二步、新建导出工具类WordUtil第三步、创建模版word4.编写接口代码5.导出结果示例 第一步、引入pom依赖 <dependency><groupId>cn.afterturn</groupId><artifactId>easypoi-spring-boot-starter</artifactId><…

怎么压缩视频?推荐7款必备视频压缩软件免费版(强烈建议收藏)

如今&#xff0c;视频内容日益丰富&#xff0c;并占据了许多人的日常娱乐和工作生活。然而&#xff0c;随着高清和超高清视频的普及&#xff0c;视频文件的体积也越来越大&#xff0c;给存储和传输带来了挑战。因此&#xff0c;学会如何压缩视频文件成为了许多人的需求之一。本…

关于锂电池的充电过程

锂电池的充电阶段大概可以分为四个阶段&#xff1a;涓流充电、恒流充电、恒压充电以及充电终止。 涓流充电&#xff1a;这是充电过程的第一阶段&#xff0c;主要用于对完全放电的电池单元进行预充&#xff08;恢复性充电&#xff09;。当电池电压低于大概3V时&#xff0c;采用最…

【学习css1】flex布局-页面footer部分保持在网页底部

中间内容高度不够屏幕高度撑不开的页面时候&#xff0c;页面footer部分都能保持在网页页脚&#xff08;最底部&#xff09;的方法 1、首先上图看显示效果 2、奉上源码 2.1、html部分 <body><header>头部</header><main>主区域</main><foot…

PaintsUndo - 一张照片一键生成绘画过程视频 本地一键整合包下载

这就是ControlNet作者张吕敏大佬的新作&#xff0c;PaintsUndo。只要你有一张图片&#xff0c;PaintsUndo 就能让它变成完整的绘画过程视频。这科技&#xff0c;绝了。 你有没有想过&#xff0c;一张静态图片也能变成一个绘画教程? PaintsUndo 就是这么神奇。你只需要提供一…

通过手机供网、可修改WIFI_MAC的网络设备

一、修改WIFI mac&#xff08;bssid&#xff09; 取一根网线&#xff0c;一头连着设备黄色网口、一头连着电脑按住设备reset按键&#xff0c;插入电源线&#xff0c;观察到蓝灯闪烁后再松开reset按键 打开电脑浏览器&#xff0c;进入192.168.1.1&#xff0c;选择“MAC 地址修改…

【Spring Boot】Spring原理:Bean的作用域和生命周期

目录 Spring原理一. 知识回顾1.1 回顾Spring IOC1.2 回顾Spring DI1.3 回顾如何获取对象 二. Bean的作用域三. Bean的生命周期 Spring原理 一. 知识回顾 在之前IOC/DI的学习中我们也用到了Bean对象&#xff0c;现在先来回顾一下IOC/DI的知识吧&#xff01; 首先Spring IOC&am…

可视化学习:如何用WebGL绘制3D物体

在之前的文章中&#xff0c;我们使用WebGL绘制了很多二维的图形和图像&#xff0c;在学习2D绘图的时候&#xff0c;我们提过很多次关于GPU的高效渲染&#xff0c;但是2D图形的绘制只展示了WebGL部分的能力&#xff0c;WebGL更强大的地方在于&#xff0c;它可以绘制各种3D图形&a…

C语言之数据在内存中的存储(2),浮点数在内存中的存储

目录 前言 一、引例 二、浮点型在内存中的存储 三、浮点数在内存中的存和取过程 1.浮点数的存储过程 2.浮点数的取过程 四、引例解析 总结 前言 想知道浮点数在内存中是如何存储的吗&#xff0c;本文就告诉你答案&#xff0c;虽然一般情况题目还是面试涉及到浮点数在内…

新华三H3CNE网络工程师认证—ACL使用场景

ACL主要用于实现流量的过滤&#xff0c;业务中网络的需求不止局限于能够连同。 一、过略工具 你的公司当中有研发部门&#xff0c;包括有财务部门&#xff0c;财务部门的访问是要做到控制的&#xff0c;防止被攻击。 这种的过滤方法为&#xff0c;在设备侧可以基于访问需求来…

AI算力发展现状与趋势分析

综合算力发展现状与趋势分析 在数字经济的疾速推动下&#xff0c;综合算力作为驱动各类应用和服务的新型生产力&#xff0c;其价值日益凸显。我们深入探讨了综合算力的定义、重要性以及当前发展状况&#xff1b;并从算力形态、运力性能和存储技术等角度&#xff0c;预见了其发展…

基于Java技术的校友社交系统

你好呀&#xff0c;我是计算机学姐码农小野&#xff01;如果你对校友社交系统感兴趣或者有相关需求&#xff0c;可以私信联系我。 开发语言 Java 数据库 MySQL 技术 Java技术SpringBoot框架 工具 IDEA/Eclipse、Navicat、Maven 系统展示 首页 校友会信息界面 校友活动…

Sqli-labs 3

1.按照路径http://localhost/sqli-labs/sqli-labs-master/Less-3/进入 2.判断注入类型----字符型 Payload&#xff1a;?id1’) and 11-- 注&#xff1a;根据报错提示的语法错误&#xff0c;在第一行中使用接近’union select 1,2,3--’)的正确语法 3.判断注入点&#xff1a;…

【Linux】vim详解

1.什么是vi/vim? 简单来说&#xff0c;vi是老式的文本编辑器&#xff0c;不过功能已经很齐全了&#xff0c;但是还是有可以进步的地方。vim则可以说是程序开发者的一项很好用的工具&#xff0c;就连 vim的官方网站&#xff08; http://www.vim.org&#xff09;自己也说vim是一…

区块链项目全球成功指南:全面覆盖的媒体宣发策略与实践

随着区块链技术的迅速普及和发展&#xff0c;全球范围内对区块链项目的关注度不断提升。为了在国际市场上取得成功&#xff0c;区块链项目需要通过有效的媒体宣传策略来提高知名度&#xff0c;吸引投资&#xff0c;并建立强大的社区支持。本文将详细介绍区块链项目在海外媒体宣…

为企业提升销售工作效率的工作手机管理系统

在竞争日益激烈的市场环境中&#xff0c;企业的销售团队如同前线战士&#xff0c;其作战效率直接关乎企业的生存与发展。然而&#xff0c;传统销售管理模式下的信息孤岛、沟通不畅、数据混乱等问题&#xff0c;正悄然成为制约销售效率提升的瓶颈。今天&#xff0c;我们为您揭秘…

在 Windows 平台搭建 MQTT 服务

引言 MQTT 是一种轻量级、基于发布/订阅模式的消息传输协议&#xff0c;旨在用极小的代码空间和网络带宽为物联网设备提供简单、可靠的消息传递服务。MQTT 经过多年的发展&#xff0c;如今已被广泛应用于资源开采、工业制造、移动通信、智能汽车等各行各业&#xff0c;使得 MQ…

汇聚荣做拼多多电商怎么样?

汇聚荣做拼多多电商怎么样?在当前电商平台竞争激烈的背景下&#xff0c;拼多多凭借其独特的商业模式和市场定位迅速崛起。对于想要加入拼多多的商家而言&#xff0c;了解平台的特点、优势及挑战是至关重要的。本文将深入分析加入拼多多电商的多个方面&#xff0c;帮助读者全面…