set与map的详细封装步骤

目录

一.set与map在STL中的源码

二.修改红黑树

1.插入与查找时的比较方式

2.插入时的返回值

3.补充成员函数

三.封装set与map

1.迭代器的实现

2.函数接口

3.map中的operator[]

四.完整代码

set.h

map.h

RBTree.h


一.set与map在STL中的源码

想要简单实现set与map 需要我们稍微理解其底层代码。以下是简化部分

set:

template <class Key, class Compare = less<Key>, class Alloc = alloc>
class set {
public:// typedefs:typedef Key key_type;typedef Key value_type;typedef Compare key_compare;typedef Compare value_compare;
private:typedef rb_tree<key_type, value_type, identity<value_type>, key_compare, Alloc> rep_type;rep_type t;  // red-black tree representing set//·····
}

map:

template <class Key, class T, class Compare = less<Key>, class Alloc = alloc>
class map {
public:// typedefs:typedef Key key_type;typedef T data_type;typedef T mapped_type;typedef pair<const Key, T> value_type;typedef Compare key_compare;//·····private:typedef rb_tree<key_type, value_type, select1st<value_type>, key_compare, Alloc> rep_type;rep_type t;  // red-black tree representing map//·····
}

从两者私有部分不难看出,两者的底层都使用了红黑树。但一棵红黑树只能为K-V模型或K模型,那是如何同时满足map的K-V模型和set的K模型的呢?

不难看出,set与map虽然共用了一颗红黑树,但传入值的方式不一样

set传入的为<K,K>, map传入的是<K, pair<const K, V> >

关于两类模板参数,第一个K方便find和erase接口,第二个则决定我们的红黑树是那种模型,那怎样将两种模型泛化到一起呢?

由于用户只能从上层调用接口,我们在底层添加模板参数T,set对于T传入K,map对于T传入

pari<const K, V>。具体如下

set:

template<class K>
class set
{public:private:RBTree<K, const K, SetKeyOfT> _t;
};

map:

template<class K, class V>
class map
{public:private:// key不能修改(第一个k为什么不加const?//第一个K 只用于删除和查找, 外层修改不到。RBTree<K, pair<const K, V>, MapKeyOfT> _t;
};

RBTree:

enum Colour
{RED,   //0BLACK  //1
};//RBTree树节点
template<class T>
struct RBTreeNode
{RBTreeNode<T>* _left;RBTreeNode<T>* _right;RBTreeNode<T>* _parent;T _data;Colour _col;RBTreeNode(const T& data):_left(nullptr), _right(nullptr), _parent(nullptr), _data(data), _col(RED){ }
};template<class K, class T>
class RBTree
{typedef RBTreeNode<T> Node;public:private:Node* _root = nullptr;
};

二.修改红黑树

1.插入与查找时的比较方式

当我们插入元素时,set插入的元素为K,比较大小时可以直接比较,map插入的元素为pair<K,V>,在C++中pair直接比较则会首先按照first的大小比较,first相同时再按照second大小比较,这显然不是我们期望的只用K关键字比较。那么如何才能使用一种统一的方式解决这个问题呢?

采用仿函数

在map和set类中添加各自的仿函数,其功能是拿到各自的Key,便于红黑树的比较。

set:

template<class K>
class set
{//仿函数  因为map中红黑树的T 为pair, set中为K, 为了泛型编程//我们使用仿函数对map和set进行统一处理struct SetKeyOfT{const K& operator()(const K& key){return key;}};
public:private:RBTree<K, const K, SetKeyOfT> _t;
};

map:

template<class K, class V>
class map
{//仿函数  因为map中红黑树的T 为pair, set中为K, 为了泛型编程//我们使用仿函数对map和set进行统一处理struct MapKeyOfT{const K& operator()(const pair<K, V>& kv){return kv.first;}};
public:private:// key不能修改(第一个k为什么不加const?//第一个K 只用于删除和查找, 外层修改不到。RBTree<K, pair<const K, V>, MapKeyOfT> _t;
};

RBTree:模板中添加仿函数

enum Colour
{RED,   //0BLACK  //1
};//RBTree树节点
template<class T>
struct RBTreeNode
{RBTreeNode<T>* _left;RBTreeNode<T>* _right;RBTreeNode<T>* _parent;T _data;Colour _col;RBTreeNode(const T& data):_left(nullptr), _right(nullptr), _parent(nullptr), _data(data), _col(RED){ }
};template<class K, class T,class KeyOfT>
class RBTree
{typedef RBTreeNode<T> Node;public:private:Node* _root = nullptr;
};

2.插入时的返回值

在C++源码中, set与map插入的返回值都是pair类型:其意为如果插入成功与否,都返回一个pair类型,如果成功则返回插入位置的迭代器与true,如果失败则返回重复元素位置的迭代器与false。所以,我们应修改Insert函数的返回值。

完善后的Insert函数(内含的迭代器部分在下面会讲,先关注返回值与比较部分即可):

//插入成功--->返回插入位置和true
//插入失败--->返回该元素存在的位置和false
pair<Iterator, bool> Insert(const T& data)
{if (_root == nullptr){_root = new Node(data);_root->_col = BLACK;return make_pair(Iterator(_root), true);}KeyOfT kot;Node* parent = nullptr;Node* cur = _root;while (cur){// K// pair<K, V>// kot对象,是用来取T类型的data对象中的keyif (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);cur->_col = RED;//记录cur位置Node* newnode = cur;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){parent->_col = uncle->_col = BLACK;grandfather->_col = RED;//继续往上cur = grandfather;parent = cur->_parent;}// 叔叔不存在或者存在且为黑else{if (cur == parent->_left){//     g  //   p   u// c RotateR(grandfather);parent->_col = BLACK;grandfather->_col = RED;}else{//    g// p     u//    cRotateL(parent);RotateR(grandfather);//cur去了g的位置  g去了u的位置cur->_col = BLACK;grandfather->_col = RED;}//根一定是黑色的break;}}else//叔叔在左边{Node* uncle = grandfather->_left;if (uncle && uncle->_col == RED){parent->_col = uncle->_col = BLACK;grandfather->_col = RED;cur = grandfather;parent = cur->_parent;}else // 叔叔不存在,或者存在且为黑{//    g// u     p//          cif (cur == parent->_right){RotateL(grandfather);parent->_col = BLACK;grandfather->_col = RED;}else{//    g// u     p//    cRotateR(parent);RotateL(grandfather);cur->_col = BLACK;grandfather->_col = RED;}break;}}}// 如果根节点变红了 设回黑_root->_col = BLACK;return make_pair(Iterator(newnode), true);}

3.补充成员函数

在简单实现中,我们需要把必要的默认成员函数补上。

//我们手写了默认成员函数后 编译器不在为我们自动生成// + default 要编译器给我们生成默认构造 运行效率更好
RBTree() = default;//拷贝
RBTree(const RBTree<K, T, KeyOfT>& t)
{//深拷贝_root = _Copy(t._root);
}
//赋值 重载运算符
RBTree<K, T, KeyOfT>& operator=(const RBTree<K, T, KeyOfT> t)
{//t是形参 相当于和副本交换了swap(_root, t._root);return *this;
}~RBTree()
{_Distory(_root);_root = nullptr;
}private://递归式删除void _Distory(Node* root){if (root == nullptr)return;_Distory(root->_left);_Distory(root->_right);delete root;root = nullptr;}Node* _Copy(Node* root){if (root == nullptr)return nullptr;Node* newroot = new Node(root->_data);newroot->_col = root->_col;newroot->_left = _Copy(root->_left);if (newroot->_left)newroot->_left->_parent = newroot;newroot->_right = _Copy(root->_right);if (newroot->_right)newroot->_right->_parent = newroot;return newroot;}

三.封装set与map

1.迭代器的实现

对于迭代器,我们要实现begin()、end()、*iterator、iterator->、++iterator、!=这四种常见功能。另外,我们希望从外部访问迭代器,只访问到树节点的数据:set只访问K,map只访问pair<const K, V>,因为用户访问树节点的左右父亲颜色等没有意义。

初始化:用一个树的节点来初始化

begin()与end():

对于一棵红黑树,其中序遍历时有序的,也就是说我们在用迭代器从begin()访问到end()的过程中也是有序的基于此,迭代器的begin()指向的位置应当是整棵树中最小的数即最左的节点(图中改为1)。end()作为最大节点的后一个元素,为空即可。

*iterator:对迭代器解引用,应返回对数据的引用

iterator->:引出迭代器的元素,应返回对应数据的地址(编译器会帮我们优化)

++iterator:对于前置++操作,在上图中访问顺序为1-6-8-11-13-15-17-22-25-27

规律为:若一个节点有右子树,则访问其右子树的最左节点;若没有右子树,则倒着往上寻找一个孩子为父亲左的一个祖先。

!=:直接比较两个值相不相等即可。

在树外新建一个类进行实现,当然也可以嵌在树中当内部类。

template<class T, class Ref, class Ptr>
struct __RBTreeIterator
{typedef RBTreeNode<T> Node;typedef __RBTreeIterator<T, Ref, Ptr> Self;Node* _node;__RBTreeIterator(Node* node):_node(node){}// 迭代器常见操作//返回引用Ref operator*(){return _node->_data;}Ptr operator->(){return &_node->_data;}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 = cur->_parent;}_node = parent;}return *this;}};Iterator Begin()
{//空不空无所谓Node* leftMin = _root;while (leftMin && leftMin->_left){leftMin = leftMin->_left;}return Iterator(leftMin);
}Iterator End()
{return Iterator(nullptr);
}

2.函数接口

在我们map与set里面对迭代器的接口封装以下即可。

set:

template<class K>
class set
{//仿函数  因为map中红黑树的T 为pair, set中为K, 为了泛型编程//我们使用仿函数对map和set进行统一处理struct SetKeyOfT{const K& operator()(const K& key){return key;}};
public://消除二义性 让编译器知道你要给一个类型取别名 而不是 定义一个变量typedef typename RBTree<K, const K, SetKeyOfT>::Iterator iterator;iterator begin(){return _t.Begin();}iterator end(){return _t.End();}private:RBTree<K, const K, SetKeyOfT> _t;
};

map:

template<class K, class V>
class map
{//仿函数  因为map中红黑树的T 为pair, set中为K, 为了泛型编程//我们使用仿函数对map和set进行统一处理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;	iterator begin(){return _t.Begin();}iterator end(){return _t.End();}private:// key不能修改(第一个k为什么不加const?//第一个K 只用于删除和查找, 外层修改不到。RBTree<K, pair<const K, V>, MapKeyOfT> _t;
};

3.map中的operator[]

对于map,其支持方括号下标访问。

形如:

map<string, int> mp;

mp[”x”] = 1;

其原理为若有”x“,则修改其对应值为1,若无“x”,则插入”x“,值为1。

所以重载[]时应进行插入,用pair类型接收。

V& operator[](const K& key)
{pair<iterator, bool> ret = _t.Insert({key,V()});//map迭代器是一个节点的指针 "->"我们重载过 得到pair类型 pair<const K, V> 返回second;return ret.first->second;
}

四.完整代码

最后补充const迭代器以及一些细节,代码里有注释。附测试。

set.h

#pragma once//template <class Key, class Compare = less<Key>, class Alloc = alloc>
//class set {
//public:
//    typedef Key key_type;
//    typedef Key value_type;
//private:
//    typedef rb_tree<key_type, value_type,
//        identity<value_type>, key_compare, Alloc> rep_type;
//    rep_type t;  // red-black tree representing set
//}// 抓重点
// 化繁为简,只关注跟抓重点部分有关系的,其他通通不关注namespace Xiang
{template<class K>class set{//仿函数  因为map中红黑树的T 为pair, set中为K, 为了泛型编程//我们使用仿函数对map和set进行统一处理struct SetKeyOfT{const K& operator()(const K& key){return key;}};public://消除二义性 让编译器知道你要给一个类型取别名 而不是 定义一个变量typedef typename RBTree<K, const K, SetKeyOfT>::Iterator iterator;typedef typename RBTree<K, const K, SetKeyOfT>::ConstIterator const_iterator;iterator begin(){return _t.Begin();}iterator end(){return _t.End();}const_iterator begin() const{return _t.Begin();}const_iterator end() const{return _t.End();}pair<iterator, bool> insert(const K& key){return _t.Insert(key);}iterator find(const K& key){return _t.Find(key);}private:RBTree<K, const K, SetKeyOfT> _t;};void PrintSet(const set<int>& s){for (auto e : s){cout << e << endl;}}void test_set(){set<int> s;s.insert(4);s.insert(2);s.insert(5);s.insert(15);s.insert(7);s.insert(1);s.insert(5);s.insert(7);PrintSet(s);set<int>::iterator it = s.begin();while (it != s.end()){//*it += 5;cout << *it << " ";++it;}cout << endl;for (auto e : s){cout << e << " ";}cout << endl;set<int> copy = s;for (auto e : copy){cout << e << " ";}cout << endl;//cout << copy._t.IsBalance() << endl;}
}

map.h

#pragma once
//template <class Key, class T, class Compare = less<Key>, class Alloc = alloc>
//class map {
//public:
//    typedef Key key_type;
//    typedef pair<const Key, T> value_type;
//
//private:
//    typedef rb_tree<key_type, value_type,
//        select1st<value_type>, key_compare, Alloc> rep_type;
//    rep_type t;  // red-black tree representing map
//};namespace Xiang
{template<class K, class V>class map{//仿函数  因为map中红黑树的T 为pair, set中为K, 为了泛型编程//我们使用仿函数对map和set进行统一处理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 _t.Begin();}iterator end(){return _t.End();}const_iterator begin() const{return _t.Begin();}const_iterator end() const{return _t.End();}pair<iterator, bool> insert(const pair<K, V>& kv){return _t.Insert(kv);}//find 返回迭代器iterator find(const K& key){return _t.Find(key);}V& operator[](const K& key){pair<iterator, bool> ret = _t.Insert({key,V()});//map迭代器是一个节点的指针 "->"我们重载过 得到pair类型 pair<const K, V> 返回second;return ret.first->second;}private:// key不能修改(第一个k为什么不加const?//第一个K 只用于删除和查找, 外层修改不到。RBTree<K, pair<const K, V>, MapKeyOfT> _t;};void test_map1(){map<string, int> m;m.insert({ "乐",1 });m.insert({ "通",1 });m.insert({ "给",1 });m.insert({ "哦",3 });cout << m["乐"] << endl << "有乐" << endl;map<string, int>::iterator it = m.begin();while (it != m.end()){//it->first += 'x';it->second += 1;//cout << it.operator->()->first << ":" << it->second << endl;cout << it->first << ":" << it->second << endl;++it;}cout << endl;}void test_map2(){string arr[] = { "a", "", "a", "", "d", "c", "","他怕", "李", "好", "哦","给","888", "达瓦","h" };map<string, int> countMap;for (auto& e : arr){countMap[e]++;}for (auto& kv : countMap){cout << kv.first << ":" << kv.second << endl;}cout << endl;}
}

RBTree.h

#pragma onceenum Colour
{RED,   //0BLACK  //1
};//RBTree树节点
template<class T>
struct RBTreeNode
{RBTreeNode<T>* _left;RBTreeNode<T>* _right;RBTreeNode<T>* _parent;T _data;Colour _col;RBTreeNode(const T& data):_left(nullptr), _right(nullptr), _parent(nullptr), _data(data)// 新插入节点默认红的//因为插入红节点可能违反规则三,但插入黑节点一定违反规则四, _col(RED){ }
};template<class T, class Ref, class Ptr>
struct __RBTreeIterator
{typedef RBTreeNode<T> Node;typedef __RBTreeIterator<T, Ref, Ptr> Self;Node* _node;__RBTreeIterator(Node* node):_node(node){}// 迭代器常见操作//返回引用Ref operator*(){return _node->_data;}Ptr operator->(){return &_node->_data;}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 = cur->_parent;}_node = parent;}return *this;}};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;//我们手写了默认成员函数后 编译器不在为我们自动生成// + default 要编译器给我们生成默认构造 运行效率更好RBTree() = default; //拷贝RBTree(const RBTree<K, T, KeyOfT>& t){//深拷贝_root = _Copy(t._root);}//赋值 重载运算符RBTree<K, T, KeyOfT>& operator=(const RBTree<K, T, KeyOfT> t){//t是形参 相当于和副本交换了swap(_root, t._root);return *this;}~RBTree(){_Distory(_root);_root = nullptr;}//begin 为最左节点   end为空节点Iterator Begin(){//空不空无所谓Node* leftMin = _root;while (leftMin && leftMin->_left){leftMin = leftMin->_left;}return Iterator(leftMin);}ConstIterator Begin() const{Node* leftMin = _root;while (leftMin && leftMin->_left){leftMin = leftMin->_left;}return ConstIterator(leftMin);}Iterator End(){return Iterator(nullptr);}ConstIterator End() const{return ConstIterator(nullptr);}Iterator Find(const K& key){Node* cur = _root;while (cur){if (KeyOfT(cur->_data) < key){cur = cur->_right;}else if (KeyOfT(cur->_data) > key){cur = cur->_left;}else{return Iterator(cur);}}return End();}//插入成功--->返回插入位置和true//插入失败--->返回该元素存在的位置和falsepair<Iterator, bool> Insert(const T& data){if (_root == nullptr){_root = new Node(data);_root->_col = BLACK;return make_pair(Iterator(_root), true);}KeyOfT kot;Node* parent = nullptr;Node* cur = _root;while (cur){// K// pair<K, V>// kot对象,是用来取T类型的data对象中的keyif (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);cur->_col = RED;//记录cur位置Node* newnode = cur;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){parent->_col = uncle->_col = BLACK;grandfather->_col = RED;//继续往上cur = grandfather;parent = cur->_parent;}// 叔叔不存在或者存在且为黑else{if (cur == parent->_left){//     g  //   p   u// c RotateR(grandfather);parent->_col = BLACK;grandfather->_col = RED;}else{//    g// p     u//    cRotateL(parent);RotateR(grandfather);//cur去了g的位置  g去了u的位置cur->_col = BLACK;grandfather->_col = RED;}//根一定是黑色的break;}}else//叔叔在左边{Node* uncle = grandfather->_left;if (uncle && uncle->_col == RED){parent->_col = uncle->_col = BLACK;grandfather->_col = RED;cur = grandfather;parent = cur->_parent;}else // 叔叔不存在,或者存在且为黑{//    g// u     p//          cif (cur == parent->_right){RotateL(grandfather);parent->_col = BLACK;grandfather->_col = RED;}else{//    g// u     p//    cRotateR(parent);RotateL(grandfather);cur->_col = BLACK;grandfather->_col = RED;}break;}}}// 如果根节点变红了 设回黑_root->_col = BLACK;return make_pair(Iterator(newnode), true);}//右旋void RotateR(Node* parent){// 要更改六条边Node* subL = parent->_left;Node* subLR = subL->_right;// 先处理parent和SubLRparent->_left = subLR;if (subLR)subLR->_parent = parent;//再处理parent和 subl//因为 后面需要把 parent的parent和subl链接//需要先保存parent的parentsubL->_right = parent;Node* ppNode = parent->_parent;parent->_parent = subL;//处理 subl的 父节点if (parent == _root){_root = subL;_root->_parent = nullptr;}else{if (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;parent->_right = subRL;if (subRL)subRL->_parent = parent;subR->_left = parent;Node* ppNode = parent->_parent;parent->_parent = subR;if (parent == _root){_root = subR;_root->_parent = nullptr;}else{if (ppNode->_left == parent){ppNode->_left = subR;}else{ppNode->_right = subR;}subR->_parent = ppNode;}}void InOrder(){_InOrder(_root);cout << endl;}bool IsBalance(){if (_root->_col == RED){return false;}int refNum = 0;Node* cur = _root;while (cur){if (cur->_col == BLACK){++refNum;}cur = cur->_left;}return _Check(_root, 0, refNum);}int Height(){return _Height(_root);}int Size(){return _Size(_root);}private://递归式删除void _Distory(Node* root){if (root == nullptr)return;_Distory(root->_left);_Distory(root->_right);delete root;root = nullptr;}Node* _Copy(Node* root){if (root == nullptr)return nullptr;Node* newroot = new Node(root->_data);newroot->_col = root->_col;newroot->_left = _Copy(root->_left);if (newroot->_left)newroot->_left->_parent = newroot;newroot->_right = _Copy(root->_right);if (newroot->_right)newroot->_right->_parent = newroot;return newroot;}int _Size(Node* root){return root == nullptr ? 0 : _Size(root->_left) + _Size(root->_right) + 1;}int _Height(Node* root){if (root == nullptr)return 0;return max(_Height(root->_left), _Height(root->_right)) + 1;}bool _Check(Node* root, int blackNum, const int refNum){if (root == nullptr){//cout << blackNum << endl;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 _Check(root->_left, blackNum, refNum)&& _Check(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;
};

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

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

相关文章

【x264】变换量化模块的简单分析

【x264】变换量化模块的简单分析 1. 变换量化1.1 变换&#xff08;transform&#xff09;1.2 量化&#xff08;quant&#xff09; 2. 编码入口&#xff08;x264_macroblock_encode&#xff09;2.1 内部编码&#xff08;macroblock_encode_internal&#xff09;2.1.1 SKIP模式2.…

多源最短路径算法 -- 弗洛伊德(Floyd)算法

1. 简介 Floyd算法&#xff0c;全名为Floyd-Warshall算法&#xff0c;亦称弗洛伊德算法或佛洛依德算法&#xff0c;是一种用于寻找给定加权图中所有顶点对之间的最短路径的算法。这种算法以1978年图灵奖获得者、斯坦福大学计算机科学系教授罗伯特弗洛伊德的名字命名。 2. 核心思…

从报名到领证:软考初级【信息系统运行管理员】报名考试全攻略

本文共计9991字&#xff0c;预计阅读33分钟。包括七个篇章&#xff1a;报名、准考证打印、备考、考试、成绩查询、证书领取及常见问题。 一、报名篇 报名条件要求&#xff1a; 1.凡遵守中华人民共和国宪法和各项法律&#xff0c;恪守职业道德&#xff0c;具有一定计算机技术…

centos7.9部署k8s的几种方式

文章目录 一、常见的k8s部署方式1、使用kubeadm工具部署2、基于二进制文件的部署方式3、云服务提供商的托管 Kubernetes 服务4、使用容器镜像部署或自动化部署工具 二、使用kubeadm工具部署1、硬件准备&#xff08;虚拟主机&#xff09;2、环境准备2.1、所有机器关闭防火墙2.2、…

sizeof和strlen

1.sizeof和strlen的对比 1.1sizeof sizeof是计算变量所占内存空间大小的&#xff0c;单位是&#xff1a;字节 如果操作数是类型的话&#xff0c;计算的是使用类型创建的变量所占内存空间的大小。 sizeof只关注占用内存空间的大小&#xff0c;不在乎内存中存放的是什么数据 …

vuInhub靶场实战系列--Kioptrix Level #4

免责声明 本文档仅供学习和研究使用,请勿使用文中的技术源码用于非法用途,任何人造成的任何负面影响,与本人无关。 目录 免责声明前言一、环境配置1.1 靶场信息1.2 靶场配置 二、信息收集2.1 主机发现2.1.1 netdiscover2.1.2 arp-scan主机扫描 2.2 端口扫描2.3 指纹识别2.4 目…

CBoard开源数据可视化工具

CBoard开源数据可视化工具 文章目录 CBoard开源数据可视化工具介绍资源列表基础环境一、安装JDK二、安装Maven2.1、安装Maven2.2、配置Maven 三、安装Tomcat8四、安装MySQL5版本4.1、安装相关依赖4.2、二进制安装4.3、设定配置文件4.4、配置systemcatl方式启动4.5、访问MySQL数…

韩国版AlphaFold?深度学习模型AlphaPPIMd:用于蛋白质-蛋白质复合物构象集合探索

在生命的舞台上&#xff0c;蛋白质扮演着不可或缺的角色。它们是生物体中最为活跃的分子&#xff0c;参与细胞的构建、修复、能量转换、信号传递以及无数关键的生物学功能。同时&#xff0c;蛋白质的结构与其功能密切相关&#xff0c;而它们的功能又通过与蛋白质、多肽、核苷酸…

新疆在线测宽仪配套软件实现的9大功能!

在线测宽仪可应用于各种热轧、冷轧板带材的宽度尺寸检测&#xff0c;材质不限&#xff0c;木质、钢制、铁质、金属、纸质、塑料、橡胶等都可以进行无损非接触式的检测&#xff0c;在各式各样的产线应用中&#xff0c;有些厂家&#xff0c;需要更加详尽完备的分析信息&#xff0…

[2024-06]-[大模型]-[DEBUG]- ollama webui 11434 connection refused

报错&#xff1a;host.docker.internal:11434 ssl:default [Connection refused] 将/etc/systemd/system/ollama.service中加上如下红框两行 Environment"OLLAMA_HOST0.0.0.0" Environment"OLLAMA_ORIGINS*"然后 systemctl daemon-reload systemctl rest…

vue3 监听器,组合式API的watch用法

watch函数 在组合式 API 中&#xff0c;我们可以使用 watch 函数在每次响应式状态发生变化时触发回调函数 watch(ref,callback&#xff08;newValue,oldValue&#xff09;&#xff0c;option:{}) ref:被监听的响应式量&#xff0c;可以是一个 ref (包括计算属性)、一个响应式…

SpringMVC:拦截器(Interceptor)

1. 简介 拦截器&#xff08;Interceptor&#xff09;类似于过滤器&#xff08;Filter&#xff09; Spring MVC的拦截器作用是在请求到达控制器之前或之后进行拦截&#xff0c;可以对请求和响应进行一些特定的处理。拦截器可以用于很多场景下&#xff1a; 1. 登录验证&#xf…

修改版的VectorDBBench更好用

原版本VectorDBBench的几个问题 在这里就不介绍VectorDBBench是干什么的了&#xff0c;上官网即可。 1.并发数设置的太少 2.测试时长30秒太长 3.连接milvus无用户和密码框&#xff0c;这个是最大的问题 4.修改了一下其它参数 由于很多网友发私信问一些milvus的相关技术问…

php redis分布式锁

一&#xff0c;概念 在PHP中实现分布式锁通常可以使用数据库、缓存系统&#xff08;如Redis&#xff09;或者其他中央存储系统来保证在分布式系统中的数据一致性与同步。秒杀下单、抢红包等等业务场景&#xff0c;都需要用到分布式锁。 常规方案大概有七中 方案一&#xff1a;…

defer+recover机制处理错误

问题&#xff1a;多个协程工作&#xff0c;其中一个协程出现panic&#xff0c;导致程序崩溃 解决办法&#xff1a;利用deferrecover捕获panic进行处理&#xff0c;即使协程出现错误&#xff0c;主线程仍然不受影响可以继续执行 package mainimport ("fmt""tim…

23种设计模式之组合模式

组合模式 1、定义 组合模式&#xff1a;组合多个对象形成树状结构以表示具有部分-整体关系的层次结构。组合模式让客户端可以统一对待单个对象和组合对象 2、组合模式结构 Component&#xff08;抽象构件&#xff09;&#xff1a;可以是接口或抽象类&#xff0c;为叶子构件…

JAVA:通过电信ctg.ag.sdk从电信物联平台AIOT获取设备上报数据的简单示例

一、问题场景 物联设备比如NB设备通过NB协议将数据传到电信平台后&#xff0c;我们的应用服务如何从电信平台获取可用的上报数据。以下通过电信开发者平台提供的SDK来简单演示下整个过程。 二、使用电信 SDK进行开发 电信AIOT物联平台提供了两种方式获取平台数据&#xff0c…

Mac 下载并激活IDEA

1.https://3.jetbra.in 打开这个网站,点击第一个网速比较快的连接 2.在新页面顶部有一个蓝色的下载链接文字< jetbra.zip(20220801) >点击下载 3.步骤2打开的页面不要关闭后面还有用 4.在idea官网下载idea对应的版本 https://www.jetbrains.com/idea/download/other.htm…

视频格式转换avi格式怎么弄?分享视频转换方法

视频格式转换avi格式怎么弄&#xff1f;AVI作为一种广泛支持的视频格式&#xff0c;能够在多种设备和播放器上顺畅播放&#xff0c;确保我们的视频内容能够无障碍地分享给朋友或上传至各大平台。其次&#xff0c;AVI格式通常具有较好的兼容性&#xff0c;能够避免格式转换过程中…

【方法】Word文档如何添加“打开密码”?

Word文档是很常用的办公文档&#xff0c;对于重要的文档&#xff0c;不想被他人随意查看&#xff0c;或者只有指定的人可以查看&#xff0c;我们可以给Word文档设置密码保护&#xff0c;这样只有知道密码的人才可以打开文档。 下面分享两种Word文档添加“打开密码”的方法&…