map、set模拟(底层封装红黑树)

个人主页:Lei宝啊 

愿所有美好如期而遇


前言

前面我们对红黑树进行了模拟实现:

现在我们将使用我们模拟的map和set对我们模拟的红黑树进行封装。

并且,本篇将增加红黑树的迭代器,模拟迭代器++(这里理解原理即可,--反过来就可以,唯一不同在于end()位置--,我们单独判断即可),然后建立我们自己的map和set对红黑树进行封装,同时为map重载operator[],添加map和set迭代器,insert方法。

红黑树代码

#pragma once
#include <iostream>
using namespace std;enum Color
{RED,BLACK
};template<class K, class T>
struct RBTreeNode
{struct RBTreeNode* _left;struct RBTreeNode* _right;struct RBTreeNode* _parent;pair<K, T> _p;int _color;RBTreeNode(const K& key, const T& val): _left(nullptr), _right(nullptr), _parent(nullptr), _color(RED), _p(key, val){}
};template<class K, class T>
class RBTree
{typedef RBTreeNode<K, T> Node;
public:bool Insert(const K& key, const T& val){Node* newnode = new Node(key, val);if (_root == nullptr){_root = newnode;_root->_color = BLACK;return true;}Node* cur = _root;Node* par = nullptr;while (cur){par = cur;if (cur->_p.first == key) return false;else if (cur->_p.first < key) cur = cur->_right;else cur = cur->_left;}if (par->_p.first > key) par->_left = newnode;else par->_right = newnode;newnode->_parent = par;cur = newnode;while (par && par->_color == RED){Node* grandfather = par->_parent;if (grandfather->_left == par){Node* uncle = grandfather->_right;//情况一:鼠鼠存在且为红色if (uncle && uncle->_color == RED){par->_color = BLACK;uncle->_color = BLACK;grandfather->_color = RED;cur = grandfather;par = cur->_parent;}//情况二:鼠鼠不存在或者鼠鼠为黑色(可以一同处理)else{//左左,右单旋if (cur == par->_left){RotateR(grandfather);grandfather->_color = RED;par->_color = BLACK;}//左右,左右双旋else{RotateL(par);RotateR(grandfather);cur->_color = BLACK;grandfather->_color = RED;}break;}}else{Node* uncle = grandfather->_left;//情况一:鼠鼠存在且为红色if (uncle && uncle->_color == RED){par->_color = BLACK;uncle->_color = BLACK;grandfather->_color = RED;cur = grandfather;par = cur->_parent;}//情况二:鼠鼠不存在或者鼠鼠为黑色(可以一同处理)else{//右右,左单旋if (cur == par->_right){RotateL(grandfather);grandfather->_color = RED;par->_color = BLACK;}//右左,右左双旋else{RotateR(par);RotateL(grandfather);cur->_color = BLACK;grandfather->_color = RED;}break;}}		}_root->_color = BLACK;return true;}bool IsBlance(){if (_root && _root->_color == RED)return false;int standard_black_num = 0;Node* cur = _root;while (cur){if (cur->_color == BLACK)standard_black_num++;cur = cur->_left;}return _IsBlance(_root, 0, standard_black_num);}void Inorder(){return _Inorder(_root);}private:// 检查这棵树是否是红黑树// 1、遵守根节点为黑色// 2、遵守不能有连续的红色节点// 3、每条路径黑色节点数量相同// // 遵守最长路径不超过最短路径的两倍,最长路径为一黑一红,最短路径为全黑。bool _IsBlance(Node *root, int real_black_num, int standard_black_num){if (root == nullptr){if (real_black_num != standard_black_num)return false;return true;}if (root != _root && root->_color == RED && root->_parent->_color == RED)return false;if (root->_color == BLACK)real_black_num++;return _IsBlance(root->_left, real_black_num, standard_black_num)&& _IsBlance(root->_right, real_black_num, standard_black_num);}void _Inorder(Node* root){if (root == nullptr)return;_Inorder(root->_left);cout << root->_p.first << " : " << root->_p.second << endl;_Inorder(root->_right);}//左单旋void RotateL(Node* par){Node* subR = par->_right;Node* subRL = subR->_left;par->_right = subRL;if (subRL)subRL->_parent = par;Node* p_par = par->_parent;subR->_left = par;par->_parent = subR;if (p_par == nullptr){_root = subR;subR->_parent = nullptr;}else{if (p_par->_left == par){p_par->_left = subR;subR->_parent = p_par;}else{p_par->_right = subR;subR->_parent = p_par;}}}//右单旋void RotateR(Node* par){Node* subL = par->_left;Node* subLR = subL->_right;par->_left = subLR;if (subLR)subLR->_parent = par;Node* p_par = par->_parent;subL->_right = par;par->_parent = subL;if (p_par == nullptr){_root = subL;subL->_parent = nullptr;}else{if (p_par->_left == par){p_par->_left = subL;subL->_parent = p_par;}else{p_par->_right = subL;subL->_parent = p_par;}}}Node* _root = nullptr;};

map类结构

这是我们最终的结果,可以先大致看一下,我们后面会对每一个细节和结构进行讲解。

#pragma once
#include "Map_Set_use_RBTree.h"template<class K, class T>
class my_map
{struct MapKeyOftypedef typename 
RBTree<K, pair<const K, T>, MapKeyOf>::iterator iterator;
typedef typename 
RBTree<K, pair<const K, const T>, MapKeyOf>::const_iterator const_iterator;public:T& operator[](const K& key)iterator begin()iterator end()const_iterator begin()constconst_iterator end()constpair<iterator, bool> insert(const pair<const K, T>& data)private:RBTree<K, pair<const K, T>, MapKeyOf> _t;
};

set类结构

这是我们最终的结果,可以先大致看一下,我们后面会对每一个细节和结构进行讲解。

#pragma once
#include "Map_Set_use_RBTree.h"template<class T>
class my_set
{//仿函数,提取key值,底层的红黑树进行比较struct SetKeyOftypedef typename 
RBTree<T, const T, SetKeyOf>::iterator iterator;
typedef typename 
RBTree<T, const T, SetKeyOf>::const_iterator const_iterator;public:bool Insert(const T& data)iterator begin()iterator end()const_iterator begin()constconst_iterator end()constpair<iterator, bool> insert(const T& data)private:RBTree<T, const T, SetKeyOf> _t;};

红黑树迭代器

从这里开始,我们将详细说明map、set的封装细节以及迭代器的实现和封装。

首先是红黑树的迭代器,我们的迭代器首先应该是指向红黑树节点的,之后我们的迭代器++和--按照中序遍历的顺序迭代,而我们红黑树的节点直接++是无法到达下一个节点的,同时我们在创建红黑树对象后,使用红黑树的begin()和end()获得迭代器,返回值是迭代器对象,而这个迭代器是指向红黑树节点的,我们不可能获得一个函数类型的返回值,所以我们不在红黑树的类里去实现红黑树的迭代器。

我们写一个迭代器类:RBTreeIterator,这个类的大致结构是这样的:

template <class T, class Ptr, class Ref>
struct RBTreeIterator
{typedef RBTreeNode<T> Node;RBTreeIterator(Node* node): _node(node){}Ptr operator->();Ref operator *();	bool operator==(const RBTreeIterator& s);	bool operator!=(const RBTreeIterator& s);RBTreeIterator& operator++();RBTreeIterator& operator--();Node* _node;
};

 _node是我们通过红黑树类的方法构建这个类的对象而进行初始化的,而这个节点++或--就是我们要实现的。

我们先来实现operator++的运算符重载,返回类型当然还是我们这个类的对象,现在我们给出一个图,思考如何按照中序找到一个节点的下一个节点。

我们现在想要找出cur节点的下一个节点,也就是node6节点,如何得到node6节点呢?

如果我们想要node2的下一个节点,也就是node1节点,又如何得到node1节点呢?

首先我们先来看node2节点,他的右节点是空的,所以直接返回到上一个节点,也就到了node1节点,而如果是node3节点,他的右节点不为空,就到了node5节点,而node5节点的左是空的,那么我们也就得到了node3节点。

也就是说,如果当前节点的右为空,那么就会返回去找未走过的祖先(这里我们指出,就是孩子节点为父节点左孩子的祖先节点),如果右不为空,那么就会走右子树的最左节点。

所以node6节点是cur节点右树的最左节点,也就是我们要的节点。

所以我们这样实现这个operator++:

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

operator--就是逻辑反一下,并且加上一个特殊判断,当节点位于end()位置,也就是空位置时,我们对节点--应该是到最大的那个节点,也就是右树的最右节点,我们单独作处理即可,这里不多做解释和实现。

现在,我们给出完整的迭代器类:

这里我们解释一下Ptr参数和Ref参数,Ptr参数我们将来可以传T*或者const T*,Ref参数将来可以传T&或者const T&。

template <class T, class Ptr, class Ref>
struct RBTreeIterator
{typedef RBTreeNode<T> Node;RBTreeIterator(Node* node): _node(node){}Ptr operator->(){return &_node->_data;}Ref operator *(){return _node->_data;}bool operator==(const RBTreeIterator& s){return _node == s._node;}bool operator!=(const RBTreeIterator& s){return _node != s._node;}//前置++//寻找节点的右子树不为空的最左节点,否则寻找孩子为父亲左孩子的祖先节点		RBTreeIterator& operator++(){if (_node->_right){Node* subRL = _node->_right;while (subRL->_left){subRL = subRL->_left;}_node = subRL;}else{Node* cur = _node;Node* parent = cur->_parent;while (parent && parent->_left != cur){cur = parent;parent = parent->_parent;}_node = parent;}return *this;} Node* _node;
};

现在我们来为红黑树类添加迭代器:

以下代码新增于红黑树类内,

	typedef RBTreeIterator<T, T*, T&> iterator;typedef RBTreeIterator<T, const T*, const T&> const_iterator;iterator begin(){Node* cur = _root;while (cur && cur->_left){cur = cur->_left;}return iterator(cur);}iterator end(){return iterator(nullptr);}const_iterator begin() const{Node* cur = _root;while (cur && cur->_left){cur = cur->_left;}return const_iterator(cur);}const_iterator end() const{return const_iterator(nullptr);}

此时如果我们定义一个红黑树对象a,并定义RBTree<省略>::iterator it = a.begin();  a.begin()返回一个RBTreeIterator类型的迭代器对象,当我们it++时,调用的就是it这个对象的operator++。 

map、set迭代器

接下来我们要为map和set的迭代器做准备,首先先对红黑树进行一些改变,首先是参数:

template<class K, class T, class KeyOf>

我们多了一个参数,KeyOf,这个参数我们将来当做仿函数使用,是为了insert方法做出的改变,我们希望这个仿函数能够提取出节点的key值,方便我们map节点pair<>中的key进行比较。

这里我们的T参数,将来传值可能是set的key,或者是map的pair<>,而K这个参数将在find和erase方法中用到,insert方法的返回值我们参照库里修改为pair<itreator,bool>,这里面的Iterator指的是指向红黑树节点的迭代器,bool是指是否插入成功。

首先我们来看修改后的insert方法:

	pair<iterator, bool> Insert(const T& data){KeyOf compare;Node* newnode = new Node(data);if (_root == nullptr){_root = newnode;_root->_color = BLACK;return make_pair(iterator(newnode), true);}Node* cur = _root;Node* par = nullptr;while (cur){par = cur;if (compare(data) < compare(cur->_data)) cur = cur->_left;else if (compare(cur->_data) < compare(data)) cur = cur->_right;else return make_pair(iterator(cur), false);}if (compare(data) < compare(par->_data)) par->_left = newnode;else par->_right = newnode;newnode->_parent = par;cur = newnode;Node* renode = newnode;while (par && par->_color == RED){Node* grandfather = par->_parent;if (grandfather->_left == par){Node* uncle = grandfather->_right;//情况一:鼠鼠存在且为红色if (uncle && uncle->_color == RED){par->_color = BLACK;uncle->_color = BLACK;grandfather->_color = RED;cur = grandfather;par = cur->_parent;}//情况二:鼠鼠不存在或者鼠鼠为黑色(可以一同处理)else{//左左,右单旋if (cur == par->_left){RotateR(grandfather);grandfather->_color = RED;par->_color = BLACK;}//左右,左右双旋else{RotateL(par);RotateR(grandfather);cur->_color = BLACK;grandfather->_color = RED;}break;}}else{Node* uncle = grandfather->_left;//情况一:鼠鼠存在且为红色if (uncle && uncle->_color == RED){par->_color = BLACK;uncle->_color = BLACK;grandfather->_color = RED;cur = grandfather;par = cur->_parent;}//情况二:鼠鼠不存在或者鼠鼠为黑色(可以一同处理)else{//右右,左单旋if (cur == par->_right){RotateL(grandfather);grandfather->_color = RED;par->_color = BLACK;}//右左,右左双旋else{RotateR(par);RotateL(grandfather);cur->_color = BLACK;grandfather->_color = RED;}break;}}}_root->_color = BLACK;return make_pair(iterator(renode), true);}

接下来就是map和set的迭代器:

map:

这里我们对迭代器进行解释,RBTree<K, pair<const K, T>, MapKeyOf>::iterator 这里的一堆参数是传给RBTree类的,我们这里的Iterator本质是RBTree的Iterator,而RBTree的Iterator本质是RBTreeIterator的,也就是说,实际上,我们在创建好一个map对象后,当我们调用迭代器时,实际是这样的:

map<string, int> m;

map<string,int>::iterator it = m.begin(); 这个begin()的返回值是RBTree<>::iterator,也就是RBTreeIterator的对象,我们将来it++,实际上就是这个对象调用他类里的operator++。

而我们底层用的当然是红黑树,所以也就创建红黑树对象,当我们想要插入值等等操作,直接调用红黑树里的方法即可,事实上,我们这个类除了仿函数,仅仅只是对红黑树的方法做了一层封装。

set也是同样的道理。

template<class K, class T>
class my_map
{struct MapKeyOf{const K& operator()(const pair<const K, T>& data){return data.first;}};typedef typename 
RBTree<K, pair<const K, T>, MapKeyOf>::iterator iterator;
typedef typename 
RBTree<K, pair<const K, const T>, MapKeyOf>::const_iterator const_iterator;public: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<const K, T>& data){return _t.Insert(data);}private:RBTree<K, pair<const K, T>, MapKeyOf> _t;
};
set:
template<class T>
class my_set
{//仿函数,提取key值,底层的红黑树进行比较struct SetKeyOf{const T& operator()(const T& key){return key;}};typedef typename 
RBTree<T, const T, SetKeyOf>::iterator iterator;
typedef typename 
RBTree<T, const T, SetKeyOf>::const_iterator const_iterator;public:bool Insert(const T& data){return _t.Insert(data);}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 T& data){return _t.Insert(data);}private:RBTree<T, const T, SetKeyOf> _t;};

map的operator[]重载

	T& operator[](const K& key){//iterator是map中pair的地址pair<iterator, bool> it = insert(make_pair(key, T()));return it.first->second;}

我们使用map时也就是做插入、查找以及修改value这么些操作,那么它的返回值理所当然是T&,这里的T和RBTRee的T是不一样的,这里的T类型是value的类型,而RETree的T是pair<K,T>类型的。

我们知道insert返回值是pair<iterator,bool>,这里的Iterator就是RBTreeIterator的对象,bool就是是否插入成功,我们返回值是这样的,it.first是取到这个迭代器,这个迭代器是指向一个RBTreeIterator的对象,并且这个对象的类里重载了->()方法,假设这个对象叫做a,我们这样使用:a.operator->()就是取到了pair<>节点的地址,再这样: a.operator->()->second,也就取出了T&的值,但是这样写比较长,我们正常情况应该是这样:a->->second,但是编译器省略了一个,也就是这样:a->second,多写反而是错的。

整体代码

#pragma once
#include "Map_Set_use_RBTree.h"template<class T>
class my_set
{//仿函数,提取key值,底层的红黑树进行比较struct SetKeyOf{const T& operator()(const T& key){return key;}};typedef typename RBTree<T, const T, SetKeyOf>::iterator iterator;typedef typename RBTree<T, const T, SetKeyOf>::const_iterator const_iterator;public:bool Insert(const T& data){return _t.Insert(data);}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 T& data){return _t.Insert(data);}private:RBTree<T, const T, SetKeyOf> _t;};
#pragma once
#include "Map_Set_use_RBTree.h"template<class K, class T>
class my_map
{struct MapKeyOf{const K& operator()(const pair<const K, T>& data){return data.first;}};typedef typename RBTree<K, pair<const K, T>, MapKeyOf>::iterator iterator;typedef typename RBTree<K, pair<const K, const T>, MapKeyOf>::const_iterator const_iterator;public:T& operator[](const K& key){//iterator是map中pair的地址pair<iterator, bool> it = insert(make_pair(key, T()));return it.first->second;}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<const K, T>& data){return _t.Insert(data);}private:RBTree<K, pair<const K, T>, MapKeyOf> _t;
};
#pragma once
#pragma once
#include <iostream>
using namespace std;enum Color
{RED,BLACK
};//树的节点,作为RBTree的成员变量。
template<class T>
struct RBTreeNode
{struct RBTreeNode* _left;struct RBTreeNode* _right;struct RBTreeNode* _parent;T _data;Color _color;RBTreeNode(const T& data): _left(nullptr), _right(nullptr), _parent(nullptr), _color(RED), _data(data){}
};//作为RBTree的迭代器,在RBTree内使用,接收来自RBTree内的Node节点
template <class T, class Ptr, class Ref>
struct RBTreeIterator
{typedef RBTreeNode<T> Node;RBTreeIterator(Node* node): _node(node){}Ptr operator->(){return &_node->_data;}Ref operator *(){return _node->_data;}bool operator==(const RBTreeIterator& s){return _node == s._node;}bool operator!=(const RBTreeIterator& s){return _node != s._node;}//前置++//寻找节点的右子树不为空的最左节点,否则寻找孩子为父亲左孩子的祖先节点		RBTreeIterator& operator++(){if (_node->_right){Node* subRL = _node->_right;while (subRL->_left){subRL = subRL->_left;}_node = subRL;}else{Node* cur = _node;Node* parent = cur->_parent;while (parent && parent->_left != cur){cur = parent;parent = parent->_parent;}_node = parent;}return *this;} /*** @param null* 前置--* 寻找孩子的左子树不为空的最右节点,否则寻找孩子为父亲右孩子的祖先节点*/RBTreeIterator& operator--(){if (_node->_left){Node* cur = _node->_left;while (cur->_right){cur = cur->_right;}_node = cur;}else{Node* cur = _node;Node* parent = cur->_parent;while (parent && cur != parent->_right){cur = parent;parent = parent->_parent;}_node = cur;}return *this;}Node* _node;
};//红黑树的实现
template<class K, class T, class KeyOf>
class RBTree
{public:typedef RBTreeNode<T> Node;typedef RBTreeIterator<T, T*, T&> iterator;typedef RBTreeIterator<T, const T*, const T&> const_iterator;iterator begin(){Node* cur = _root;while (cur && cur->_left){cur = cur->_left;}return iterator(cur);}iterator end(){return iterator(nullptr);}const_iterator begin() const{Node* cur = _root;while (cur && cur->_left){cur = cur->_left;}return const_iterator(cur);}const_iterator end() const{return const_iterator(nullptr);}pair<iterator, bool> Insert(const T& data){KeyOf compare;Node* newnode = new Node(data);if (_root == nullptr){_root = newnode;_root->_color = BLACK;return make_pair(iterator(newnode), true);}Node* cur = _root;Node* par = nullptr;while (cur){par = cur;if (compare(data) < compare(cur->_data)) cur = cur->_left;else if (compare(cur->_data) < compare(data)) cur = cur->_right;else return make_pair(iterator(cur), false);}if (compare(data) < compare(par->_data)) par->_left = newnode;else par->_right = newnode;newnode->_parent = par;cur = newnode;Node* renode = newnode;while (par && par->_color == RED){Node* grandfather = par->_parent;if (grandfather->_left == par){Node* uncle = grandfather->_right;//情况一:鼠鼠存在且为红色if (uncle && uncle->_color == RED){par->_color = BLACK;uncle->_color = BLACK;grandfather->_color = RED;cur = grandfather;par = cur->_parent;}//情况二:鼠鼠不存在或者鼠鼠为黑色(可以一同处理)else{//左左,右单旋if (cur == par->_left){RotateR(grandfather);grandfather->_color = RED;par->_color = BLACK;}//左右,左右双旋else{RotateL(par);RotateR(grandfather);cur->_color = BLACK;grandfather->_color = RED;}break;}}else{Node* uncle = grandfather->_left;//情况一:鼠鼠存在且为红色if (uncle && uncle->_color == RED){par->_color = BLACK;uncle->_color = BLACK;grandfather->_color = RED;cur = grandfather;par = cur->_parent;}//情况二:鼠鼠不存在或者鼠鼠为黑色(可以一同处理)else{//右右,左单旋if (cur == par->_right){RotateL(grandfather);grandfather->_color = RED;par->_color = BLACK;}//右左,右左双旋else{RotateR(par);RotateL(grandfather);cur->_color = BLACK;grandfather->_color = RED;}break;}}}_root->_color = BLACK;return make_pair(iterator(renode), true);}bool IsBlance(){if (_root && _root->_color == RED)return false;int standard_black_num = 0;Node* cur = _root;while (cur){if (cur->_color == BLACK)standard_black_num++;cur = cur->_left;}return _IsBlance(_root, 0, standard_black_num);}void Inorder(){return _Inorder(_root);}private:// 检查这棵树是否是红黑树// 1、遵守根节点为黑色// 2、遵守不能有连续的红色节点// 3、每条路径黑色节点数量相同// // 遵守最长路径不超过最短路径的两倍,最长路径为一黑一红,最短路径为全黑。bool _IsBlance(Node* root, int real_black_num, int standard_black_num){if (root == nullptr){if (real_black_num != standard_black_num)return false;return true;}if (root != _root && root->_color == RED && root->_parent->_color == RED)return false;if (root->_color == BLACK)real_black_num++;return _IsBlance(root->_left, real_black_num, standard_black_num)&& _IsBlance(root->_right, real_black_num, standard_black_num);}void _Inorder(Node* root){if (root == nullptr)return;_Inorder(root->_left);cout << root->_p.first << " : " << root->_p.second << endl;_Inorder(root->_right);}//左单旋void RotateL(Node* par){Node* subR = par->_right;Node* subRL = subR->_left;par->_right = subRL;if (subRL)subRL->_parent = par;Node* p_par = par->_parent;subR->_left = par;par->_parent = subR;if (p_par == nullptr){_root = subR;subR->_parent = nullptr;}else{if (p_par->_left == par){p_par->_left = subR;subR->_parent = p_par;}else{p_par->_right = subR;subR->_parent = p_par;}}}//右单旋void RotateR(Node* par){Node* subL = par->_left;Node* subLR = subL->_right;par->_left = subLR;if (subLR)subLR->_parent = par;Node* p_par = par->_parent;subL->_right = par;par->_parent = subL;if (p_par == nullptr){_root = subL;subL->_parent = nullptr;}else{if (p_par->_left == par){p_par->_left = subL;subL->_parent = p_par;}else{p_par->_right = subL;subL->_parent = p_par;}}}Node* _root = nullptr;};

需要注意的点和容易犯错的点

这里需要注意到的是迭代器是RBTree里的迭代器,因为红黑树需要我们提供一种提取key的方法,所以才有了第三个参数。

 const迭代器并不是改了返回值就是const类型的方法了,需要this指针也是const类型。

明确自己的类里定义的成员变量,以及要传的参数。

 这里需要实现,范围for需要这个方法,同时,参数是本类的类型。

_node和_right写对,不要少了_。 

这些只是重命名,:: 前的是类的类型,后面才是迭代器,表示某个类里的迭代器,这么一层层下去,实际上用的一直都是RBTreeIterator的对象。

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

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

相关文章

重建大师模型构建精细网格失败是什么原因导致的呢?(如下图)

出现图中的报错一般是显存溢出、瓦块过大造成的。 重建大师是一款专为超大规模实景三维数据生产而设计的集群并行处理软件&#xff0c;输入倾斜照片&#xff0c;激光点云&#xff0c;POS信息及像控点&#xff0c;输出高精度彩色网格模型&#xff0c;可一键完成空三、自动建模和…

flex弹性盒子实现左中右居中布局

1、效果展示 2、布局与样式 <!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8"><title>测试弹性盒子</title> </head> <body> <div class"out-parent"><div class"…

计算机网络 —— 运输层

运输层 5.1 运输层概述 运输层的主要任务是&#xff0c;如何为运行在不同主机上的应用进程提供直接的通信服务。运输层协议又称为端到端协议。 根据应用需求的不同&#xff0c;因特网的运输层为应用层提供了两种不同的运输协议&#xff0c;即面向连接的TCP和无连接的UDP 5.2…

Vant中<van-button>中icon的位置设置

一般在<van-button>按钮中加入icon&#xff0c;其位置都在左侧 我们可以用&#xff1a;van-position&#xff0c;改变图标展示位置 如&#xff1a;icon-position"right"

【30天】Python从入门到精通详解版—第一天—Python编程语言简介

Python编程语言简介 Python 简介Python 发展历史Python 特点Python 环境搭建Python下载Python安装环境变量配置在 Unix/Linux 设置环境变量在 Windows 设置环境变量 运行Python Python 简介 Python是一种高级编程语言&#xff0c;由Guido van Rossum于1989年底发明。Python在设…

React入门 学习全记录(适合和我一样有Vue经验想学习react的同学~)

前端目前的三大框架&#xff1a;Vue、React、Angular比较 都采用了组件化开发的方式&#xff0c;都是基于MVVM的框架有着虚拟DOM&#xff0c;Vue 和 Angular 都采用了响应式设计的方式&#xff0c;当数据发生变化时会自动更新视图。React 和 Angular 的复杂性也使得它们更加适…

MongoDB黑窗口操作(CRUD)

目录 连接数据库 插入数据 for循环插入数据 根据条件查询 修改数据 删除数据 连接数据库 对应路径下cmd中输入命令mongo即可 插入数据 j{name:"mongo"} t{x:3} 提交&#xff1a;db.things.svae(j);db.things.svae(t); 查询&#xff1a;db.things.find(); …

Java后端八股文之java基础

文章目录 0.Java 中有 8 种基本数据类型1. 为什么浮点数运算会丢失精度&#xff1f;如何解决&#xff1f;2. 面向对象的三大特征2.1 封装2.2 继承2.3 多态 3. 深拷贝和浅拷贝的区别&#xff1f;什么是引用拷贝&#xff1f;4. equals方法与“”方法4.1 4.2 equals方法 5.hashcod…

数据分析之一些Excel函数

数据分析之Excel的使用 SUM()求和SUMIF()单条件求和SUMIFS()多条件求和日期函数YEAR()提取年份MONTH()提取月份DAY()提取日DATE()函数 SUBTOTAL()求和IF()函数IF嵌套 VLOOKUP()搜索取值MATCH()返回行值或列值INDEX()定位取值 SUM()求和 SUM(number1,[number2],…) 对选中的区域…

00后卷王自述,我真的很卷。。。

&#x1f345; 视频学习&#xff1a;文末有免费的配套视频可观看 &#x1f345; 关注公众号【互联网杂货铺】&#xff0c;回复 1 &#xff0c;免费获取软件测试全套资料&#xff0c;资料在手&#xff0c;涨薪更快 前段时间去面试了一个公司&#xff0c;成功拿到了offer&#xf…

什么洗地机值得推荐?旗舰洗地机希亦、追觅、西屋、海尔实际表现如何?

洗地机这个产品相信大家已经不陌生了&#xff0c;它集合吸尘器和电动扫地拖把的功能&#xff0c;轻轻推拉便可以解决地面上的赃物&#xff0c;且不用我们手动清洗滚刷&#xff0c;深得家务人的喜爱&#xff0c;可是&#xff0c;当我们真正要去选购的时候&#xff0c;还是很纠结…

HarmonyOS NEXT应用开发之深色模式适配

介绍 本示例介绍在开发应用以适应深色模式时&#xff0c;对于深色和浅色模式的适配方案&#xff0c;采取了多种策略如下&#xff1a; 固定属性适配&#xff1a;对于部分组件的颜色属性&#xff0c;如背景色或字体颜色&#xff0c;若保持不变&#xff0c;可直接设定固定色值或…