基于紅黑樹對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.pswp.cn/diannao/45051.shtml
繁體地址,請注明出處:http://hk.pswp.cn/diannao/45051.shtml
英文地址,請注明出處:http://en.pswp.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;學會如何壓縮視頻文件成為了許多人的需求之一。本…

小米官網的數據是怎么優化的?

小米PC端官網首頁的“全部商品分類”功能是用戶瀏覽和選擇商品的重要入口。為了優化這一功能的數據展示和用戶體驗&#xff0c;可以采取以下幾個步驟&#xff1a; 數據加載優化&#xff1a; 懶加載&#xff08;Lazy Loading&#xff09;&#xff1a;當鼠標劃過“全部商品分類”…

實現前端登錄注冊功能(有源碼)

引言 用戶登錄和注冊是任何現代Web應用程序的基本功能。在前端開發中&#xff0c;實現一個安全且用戶友好的登錄注冊系統至關重要。本文將介紹如何使用HTML、CSS和JavaScript&#xff08;包括Vue.js&#xff09;來實現前端的登錄和注冊功能。 1. 項目結構 首先&#xff0c;我們…

軟設之訪問者模式

設計模式中訪問者模式的意圖是&#xff1a; 表示一個作用于某對象結構中的各元素的操作&#xff0c;使得在不改變各元素的類的前提下定義作用于這些元素的新操作。 舉個例子&#xff0c;比如說有個游客想去幾個景點&#xff0c;去每個景點都想按統一的流程。但是每個景點都有…

vue3 學習筆記04 -- axios的使用及封裝

vue3 學習筆記04 – axios的使用及封裝 安裝 Axios 和 TypeScript 類型定義 npm install axios npm install -D types/axios創建一個 Axios 實例并封裝成一個可復用的模塊&#xff0c;這樣可以在整個應用中輕松地進行 API 請求管理。 在 src 目錄下創建一個 services 文件夾&…

關于鋰電池的充電過程

鋰電池的充電階段大概可以分為四個階段&#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;在設備側可以基于訪問需求來…

解決IntelliJ IDEA連接MySQL時“Public Key Retrieval is not Allowed”問題

前言 在使用IntelliJ IDEA開發環境中連接MySQL數據庫時&#xff0c;可能會遇到“Public Key Retrieval is not allowed”這樣的錯誤提示&#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;…