C++STL详解(九)--使用红黑树封装实现set和map

news2025/1/17 4:54:10

文章目录

  • 控制底层红黑树模板参数
  • 模板参数中的仿函数
  • map,set中的正向迭代器
  • map,set中的反向迭代器
  • []下标访问运算符重载
  • map的模拟实现代码
  • map的模拟实现
  • 适用map,set容器的底层红黑树代码(修改版本)

控制底层红黑树模板参数

如果我们用一棵KV模型的红黑树同时实现map和set,我们就需要控制map和set中的所传入红黑树中的模板参数,其中,封装过程中,为了与红黑树的模板参数区分,我们将红黑树中模板参数由V改成T.

template <class K, class T>
struct RBTree
{
   // 
};

对于T来说,如果是set容器,那么T实例化出来的便是Key

namespace mySet
{
    template<class K>
    class set
    {
    public:
        //
    private:
        RBTree<K, K>> _t;
    };

}

如果是map容器,那么T实例化出来的便是pair<K,V>键值对.

namespace myMap
{
    template<class K,class V>
    class map
    {
    public:
        //
    private:
        RBTree<K, pair<K, V>> _t;
    };
}

T实例化例图如下:
在这里插入图片描述

那么,在红黑树中我们可不可以只保留一个模板参数T,根据实参类型转换为所需要的存储类型呢?

不可以,之所以还需要第一个模板参数K,就是因为STL中map所提供的find和erase接口需要key来指定删除或者查找的结点.

模板参数中的仿函数

在插入中,结点中存储的类型T可能为Set容器中的key,也有可能是Map中的pair<K,V>键值对,底层红黑树如何针对不同类型的容器进行比较呢?

我们所需要的是:
如果是set容器,那么T就是类型key,我们需要取数据类型为T的data就行.

如果是map容器,那么T就为键值对pair<K,V>,我们需要取键值对pair中的first与所给值key比较.

所以,综合以上情况,我们需要在上层set,map中添加一个仿函数,根据实参所传的不同类型进而获取不同数据类型来比较两个结点.

例如:
以下为set,map容器分别传入一个仿函数,在比较时会更具容器类型不同进而调用所需要的仿函数.
set仿函数:

namespace mySet
{
    template<class K>
    class set
    {
    public:
        struct SetKeyOfT
        {
            const K& operator()(const K& key )
            {
                return key;
            }
        };
    private:
        RBTree<K, K, SetKeyOfT> _t;
    };
    }

map仿函数:

namespace myMap
{
    template<class K,class V>
    class map
    {
    public:
        struct MapKeyOfT
        {
            const K& operator()(const pair<K, V>& kv)
            {
                return kv.first;
            }
        };
    private:
        RBTree<K, pair<K, V>,MapKeyOfT> _t;
    };
    }

所以,当容器为set时,红黑树中的仿函数就实例化为set容器中的仿函数,当容器为map时,红黑树中的仿函数就实例化为map中的仿函数.

在这里插入图片描述
进而,当我们通过上层容器中的Insert函数调用底层红黑树中的Insert函数时,在比较时就会根据存储类型T,分别调用所需要的仿函数进行结点的比较.
例如:
以下是底层红黑树Insert函数仿函数调用插入新结点部分代码:

bool Insert(const T& data )            //插入函数
	{
		KeyOfT kot;
		
		if (_root == nullptr) //如果为空树,直接插入结点就可以了.
		{
			_root = new Node(data);
			cout << kot(_root->_data);
			return true;
		}
		Node* parent = nullptr;
		Node* cur = _root;
		while (cur)                          //寻找插入位置
		{
			if (kot(data) <  kot(cur->_data))
			{
				parent = cur;
				cur = cur->_right;
			}
			else if ( kot( cur->_data) <  kot(data))
			{
				parent = cur;
				cur = cur->_left;
			}
			else
			{
				return false;
			}
		}
		cur = new Node(data);               //找到插入位置并插入.
		//......
		}

map,set中的正向迭代器

map和set中的迭代器实际上就是对指向结点的指针进行封装,正向迭代器中一个内置成员.

其中Ref,Ptr分别代表目标数据类型的引用,指针类型.

template <class T,class Ref,class Ptr>
struct _RBTreeIterator
{
	typedef RBTreeNode<T> Node;       //重新定义红黑树结点类型为Node
	typedef _RBTreeIterator<T, Ref, Ptr> Self; //重新定义迭代器类型为Self
	Node* _node;
}

正向迭代器的构造:

_RBTreeIterator(Node* node = nullptr)
		:_node(node)
	{}

正向迭代器的解引用运算符重载:
当我们使用正向迭代器的解引用运算符重载时,直接返回目标数据的引用.

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

正向迭代器的->运算符重载
当我们使用正向迭代器的->运算符重载,直接返回目标数据的地址.

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

正向迭代器还包括了==,!=运算符重载,我们直接判断两个迭代器中的结点指针是否相同即可.

bool operator!=(const Self& s) const //普通对象和const对象都可以进行调用.
	{
		return _node != s._node;
	}
	bool operator==(const Self& s) const 
	{
		return _node == s._node;
	}

前置++运算符重载:
前置++运算重载是按照中序遍历进行访问的,主要有两个步骤
1: 如果it所指的结点中右子树存在,则访问右子树的最左结点.

2: 如果it所指的结点中右子树不存在,则寻找孩子不是父亲右的那个祖先,并对其进行访问.

在这里插入图片描述

代码如下:

Self& operator++() //前置++,返回的是自己.
	{
		if (_node->_right) //右子树存在,访问的是右子树的最左节点.
		{
			Node* left = _node->_right;
			while ( left->_left)
			{
				left = left->_left;
			}
			_node = left;
		}
		else     //右子树不存在,寻找孩子不是父亲右的那个祖先.
		{
			Node* parent = _node->_parent;
			Node* cur = _node;
			while ( parent && cur == parent->_right)
			{
				cur = cur->_parent;
				parent = parent->_parent;
			}
			_node = parent;
		}
		return *this;  //返回迭代器.
	}

前置–运算符重载:
前置–运算重载是按照中序遍历进行访问的,主要有两个步骤:
1: 如果it所指的结点中左子树存在,则访问左子树的最右结点.

2: 如果it所指的结点中左子树不存在,则寻找孩子不是父亲左的那个祖先,并对其进行访问.
在这里插入图片描述
当正向迭代器实现后,我们可以在上层对该迭代器进一步封装.可以在底层红黑树的public区域中重新定义迭代器的类型为iterator,并且封装两个函数:
1: begin函数中迭代器指向底层红黑树的最左结点.

2: end函数中原本返回的是最后一个数据的下一个地址,如果有哨兵位则返回哨兵位,为了方便,我们采用的是返回nullptr构造的迭代器.(但是这样在it从end–遍历时会访问空指针造成程序崩溃Bug)

begin函数和end函数代码如下:

	iterator begin()          //指向的是底层红黑树的最左结点.
	{
		Node* left = _root;
		while ( left && left->_left ) //不能是的空树.
		{
			left = left->_left;
		}
		return iterator(left);      //构造一个迭代器返回.
	}
	iterator end()            //返回的是最后一个数据的下一个地址,如果有
		                      //哨兵位就返回哨兵位,如果没有哨兵位就返回空的迭代器.
	{
		return iterator(nullptr);
	}

map,set中的反向迭代器

map,set中的反向迭代器底层就是对正向迭代器的封装,反向迭代器实际上是一个迭代器适配器

template<class Iterator,class Ref,class Ptr>
struct ReverseIterator                  //反向迭代器
{
	typedef ReverseIterator<Iterator,Ref,Ptr> Self; //重新定义反向迭代器的类型

	Iterator _it;                     //反向迭代器的内置成员就是正向迭代器

	ReverseIterator(Iterator it)       //初始化列表
		:_it(it)                     //正向迭代器构造一个反向迭代器
	{}

	Ref operator*()
	{
		return *_it; //调用正向迭代器的operator*返回结点数据的引用
	}
	Ptr operator->()
	{
		return _it.operator->(); //调用正向迭代器的operator->返回结点数据的指针
	}
	Self& operator++()	          //前置++
	{
		--_it; //调用正向迭代器的前置--
		return *this;
	}
	//前置--
	Self& operator--()
	{
		++_it; //调用正向迭代器的前置++
		return *this;
	}

	bool operator!=(const Self& s) const
	{
		return _it != s._it; //调用正向迭代器的operator!=
	}
	bool operator==(const Self& s) const
	{
		return _it == s._it; //调用正向迭代器的operator==
	}
};

当反向迭代器实现后,我们可以在底层红黑树中的public区域重新定义一个反向迭代器的类型Riterator并且封装两个函数:
1: rbegin函数返回底层红黑树的最右结点.

2: rend函数中如果有哨兵位则返回哨兵位,为了方便,我们采用的是返回nullptr构造的迭代器.(但是这样在it从rend++遍历时会造成访问空指针造成程序崩溃Bug)


	   Riterator rbegin()          //指向的是底层红黑树的最右结点.
	   {
		  Node* right = _root;
	       while ( right  && right->_right) //不能是的空树.
		    {
			right = right->_right;
		    }
		  return Riterator(iterator(right));      //构造一个迭代器返回.
	   }
	    Riterator  rend()
	   {
		   return Riterator( iterator(nullptr));
	   }

[]下标访问运算符重载

[]下标访问运算符主要复用插入函数:
1: 当插入成功,返回的是刚插入结点的迭代器和返回结果为true所构成的pair键值对.

2: 当插入失败,说明已经有存有目标数据的结点,我们只要返回目标结点和返回结果false所组成的pair键值对.

3:KV模型结构才能重载[]下标访问运算符,底层红黑树不知道上层容器所传的模型结构类型,所以只能在上层容器中写.

例如:以下为在map中的[]下标访问运算符重载代码:

//因为只有K,V形式才有[],所以不能在底层红黑树写[],只能在上层容器map中写.
        V& operator[](const K& key)
        {
            pair<iterator, bool> ret = Insert(make_pair(key, V()));           //如果插入的是未有数据,返回的是存有刚插入结点迭代器的pair,V()一般采用默认值.
            return ret.first->second;  //不管插入成没成功没,返回的都是指向那个存有目标数据结点迭代器的pair.
        }

map的模拟实现代码

#include "RBTree.h"
#include <iostream>
#include "Map.h"
using namespace std;
namespace myMap
{
    template<class K, class V>
    class map
    {
    public:
        struct MapKeyOfT         //仿函数
        {
            const K& operator()(const pair<K, V>& kv)
            {
                return kv.first;
            }
        };
        typedef typename RBTree<K, pair<K, V>, MapKeyOfT>::iterator iterator;         //从类模板中取定义的类型,因为类模板的静态变量也是这样获取的,为了告诉编译器我所取的是,是定义的类型而不是静态变量,所以要加上typename加以区分.
        typedef typename RBTree<K, pair<K, V>, MapKeyOfT>::Riterator Riterator;
        pair<iterator, bool> Insert(const pair<K, V>& kv)
        {
            return _t.Insert(kv);
        }
        iterator begin()
        {
            return _t.begin();
        }
        iterator end()
        {
            return _t.end();
        }
        Riterator rbegin()
        {
            return _t.rbegin();
        }
        Riterator rend()
        {
            return _t.rend();
        }

        V& operator[](const K& key)
        {
            pair<iterator, bool> ret = Insert(make_pair(key, V()));
            return ret.first->second;  //不管插入成没成功没,返回的都是指向
        }

    private:
        RBTree<K, pair<K, V>, MapKeyOfT> _t;
    };
    }

map的模拟实现

#include "RBTree.h"
namespace mySet
{
    template<class K>
    class set
    {
    public:
        struct SetKeyOfT
        {
            const K& operator()(const K& key )
            {
                return key;
            }
        };
        typedef typename RBTree<K, K,SetKeyOfT>::iterator iterator;
        typedef typename RBTree<K, K, SetKeyOfT>::Riterator Riterator;
        pair<iterator, bool> Insert(const K& key)
        {
            return _t.Insert(key);
        }
        iterator begin()
        {
            return _t.begin();
        }
        iterator end()
        {
            return _t.end();
        }
        Riterator rbegin()
        {
            return _t.rbegin();
        }
        Riterator rend()
        {
            return _t.rend();
        }
    private:
        RBTree<K, K, SetKeyOfT> _t;
    };
    }

适用map,set容器的底层红黑树代码(修改版本)

#include <iostream>
#include <assert.h>
using namespace std;
enum Colour
{
	RED,
	BLACK
};
template <class T>
struct RBTreeNode            //三叉链
{
	RBTreeNode<T>* _left;
	RBTreeNode<T>* _right;
	RBTreeNode<T>* _parent;

	//pair<K, V> _kv;         //存储的键值对
	T _data;                //map就实例化为pair,set就实例化为key.
	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 = nullptr)
		:_node(node)
	{}
 
    Ref operator*()
	{
		return _node->_data;
	}
	Ptr operator->()
	{
		return &_node->_data;
	}
	bool operator!=(const Self& s) const //普通对象和const对象都可以进行调用.
	{
		return _node != s._node;
	}
	bool operator==(const Self& s) const 
	{
		return _node == s._node;
	}

	Self& operator++() //前置++,返回的是自己.
	{
		Node* flag = nullptr;
		if (_node->_right) //右子树存在,访问的是右子树的最左节点.
		{
			Node* left = _node->_right;
			while ( left->_left)
			{
				left = left->_left;
			}
			_node = left;
			flag = _node;
		}
		else if (_node == nullptr)
		{
			_node = flag;
		}
		else     //右子树不存在,寻找孩子不是父亲右的那个祖先.
		{
			Node* parent = _node->_parent;
			Node* cur = _node;
			while ( parent && cur == parent->_right)
			{
				cur = cur->_parent;
				parent = parent->_parent;
			}
			_node = parent;
		}
		return *this;  //返回迭代器.
	}

	Self& operator--()
	{ 
		Node* flag = nullptr;
		if (_node->_left) //左子树存在,访问左子树中的最右那个结点.
		{
			Node* right = _node->_left;
			while (right && right->_right )
			{
				right = right->_right;
			}
			_node = right;
		}
		else if( _node == nullptr )
		{
			_node = flag;
		}
		
		else             //左子树不存在,寻找孩子不是父亲左的那个祖先.
		{
			Node* parent = _node->_parent;
			Node* cur = _node;
			while ( parent && cur == parent->_left )
			{
				cur = parent;
				parent = parent->_parent;
			}
			_node = parent;
		}
		return *this;
	}
};

//反向迭代器---迭代器适配器
template<class Iterator,class Ref,class Ptr>
struct ReverseIterator
{
	typedef ReverseIterator<Iterator,Ref,Ptr> Self; //反向迭代器的类型
	Iterator _it; //反向迭代器所封装的正向迭代器

	//构造函数
	ReverseIterator(Iterator it = nullptr)
		:_it(it) //根据所给正向迭代器构造一个反向迭代器
	{}

	Ref operator*()
	{
		return *_it; //通过调用正向迭代器的operator*返回结点数据的引用
	}
	Ptr operator->()
	{
		return _it.operator->(); //通过调用正向迭代器的operator->返回结点数据的指针
	}

	//前置++
	Self& operator++()
	{
		--_it; //调用正向迭代器的前置--
		return *this;
	}
	//前置--
	Self& operator--()
	{
		++_it; //调用正向迭代器的前置++
		return *this;
	}

	bool operator!=(const Self& s) const
	{
		return _it != s._it; //调用正向迭代器的operator!=
	}
	bool operator==(const Self& s) const
	{
		return _it == s._it; //调用正向迭代器的operator==
	}
};

template <class K, class T,class KeyOfT>
struct RBTree
{
	typedef RBTreeNode<T> Node;
	typedef  _RBTreeIterator<T, T&, T*> iterator;
	typedef ReverseIterator< iterator, T&, T* > Riterator;
public:
	iterator begin()          //指向的是底层红黑树的最左结点.
	{
		Node* left = _root;
		while ( left && left->_left ) //不能是的空树.
		{
			left = left->_left;
		}
		return iterator(left);      //构造一个迭代器返回.
	}
	iterator end()            //返回的是最后一个数据的下一个地址,如果有
		                      //哨兵位就返回哨兵位,如果没有哨兵位就返回空的迭代器.
	{
		return iterator(nullptr);
	}

	   Riterator rbegin()          //指向的是底层红黑树的最右结点.
	   {
		  Node* right = _root;
	      while ( right  && right->_right) //不能是的空树.
		 {
			right = right->_right;
		 }
		  return Riterator(iterator(right));      //构造一个迭代器返回.
	   }
	    Riterator  rend()
	   {
		   return Riterator( iterator(nullptr));
	   }
	   
	Node* Find(const K& key)
	{
		KeyOfT kot;
		Node* cur = _root;
		while (cur)
		{
			if (kot(cur->_data) < key) //如果key大于当前结点的first
			{
				cur = cur->_right;
			}
			else if (kot(cur->_data) > key) //如果key小于当前结点的first
			{
				cur = cur->_left;
			}
			else           //寻找到了
			{
				return cur;
			}
		}
		return nullptr;
	}
	void InOrder()
	{
		_InOrder(_root);
	}
	pair<iterator,bool>  Insert(const T& data )            //插入函数
	{
		KeyOfT kot;
		
		if (_root == nullptr) //如果为空树,直接插入结点就可以了.
		{
			_root = new Node(data);
		//	cout << kot(_root->_data);
			_root->_col = BLACK;
			return make_pair(iterator(_root), true); //返回的是杠插入结点的迭代器和返回结果所构成的pair
		}
		Node* parent = nullptr;
		Node* cur = _root;
		while (cur)                          //寻找插入位置
		{
			if (kot(data) >  kot(cur->_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);//返回的是已经有数据的迭代器和返回结果所构成的pair.
			}
		}
		cur = new Node(data);               //找到插入位置并插入.
		Node* newnode = cur;               //要保留新插入的结点,因为有可能情况一调整平衡时,cur指向的结点
		                                   //已经改变.
		//cout << kot(cur->_data);
		cur->_col = RED;     //新插入结点的颜色为红色.
		if (kot(parent->_data) < kot(data))  //与父亲结点建立联系
		{
			parent->_right = cur;
		}
		else
		{
			parent->_left = cur;
		}
		cur->_parent = parent;
		while (parent && parent->_col == RED) //达到平衡条件,需要调整平衡.
		{
			Node* grandfater = parent->_parent;
			assert(grandfater);
			assert(grandfater->_col == BLACK);
			if (parent == grandfater->_left) //判断父亲在祖父的位置,如果父亲在祖父的左边
			{
				Node* uncle = grandfater->_right;
				if (uncle && uncle->_col == RED) //将父亲和叔叔的颜色变黑,祖父的颜色变红.
				{
					parent->_col = uncle->_col = BLACK;
					grandfater->_col = RED;

					cur = grandfater;             //继续向上寻找
					parent = cur->_parent;
				}
				else           //uncle不存在或者存在且为黑
				{
					//     g 
					//  p
				   //cur

					if (cur == parent->_left)   //如果祖父,父亲,孩子路径为一条直线
					{
						RotateR(grandfater);
						parent->_col = BLACK;
						grandfater->_col = RED;
					}
					//    g
					// p
					//    cur
					else                       //如果祖父,父亲,孩子路径为一条折线.
					{
						RotateL(parent);
						RotateR(grandfater);
						cur->_col = BLACK;
						grandfater->_col = RED;
					}
					//走到这里大该子树的根已经变成黑色结点了,不用再往上循环了.
					break;
				}
			}
			else                    //如果父亲在祖父的右边.
			{
				//   g
				// u   p 
				//       c
				//
				Node* uncle = grandfater->_left;
				if (uncle && uncle->_col == RED)    //如果叔叔存在且为红色结点.
				{
					parent->_col = uncle->_col = BLACK;
					grandfater->_col = RED;

					cur = grandfater;      //继续向上变色循环.
					parent = cur->_parent;

				}
				else                    //如果父亲不存在,或者存在且为黑色结点.
				{
					//    g 
					//      p
					//        c 		
					if (parent->_right == cur)      //如果父亲,祖父,孩子路径为一条直线
					{
						RotateL(grandfater);
						parent->_col = BLACK;
						grandfater->_col = RED;
					}
					//    g
					//      p
					//    c
					else                          //如果父亲,祖父,孩子路径为一条折线
					{
						RotateR(parent);
						RotateL(grandfater);
						grandfater->_col = RED;
						cur->_col = BLACK;
					}
					break;
				}

			}
		}
		_root->_col = BLACK; //走到这说明这棵树没有的祖先没有parent,或者有parent但是父亲是黑色的,不需要处理.
		return make_pair(iterator(newnode), true);
	}

	bool IsBalance()
	{
		return _IsBalance(_root);
	}
private:
	bool PreCheck(Node* root, int& benchMark, int blackNum) //判断性质四
	{
		if (root == nullptr)
		{
			if (benchMark == 0)
			{
				benchMark = blackNum;;
				return true;
			}
			if (blackNum != benchMark)
			{
				cout << "某条黑色结点数量不相等." << endl;
				return false;
			}
			else
			{
				return true;
			}
		}
		if (root->_col == RED && root->_parent->_col == RED)//判断性质三
		{
			return false;
		}
		if (root->_col == BLACK)
		{
			++blackNum;
		}
		return PreCheck(root->_left, benchMark, blackNum) &&
			PreCheck(root->_right, benchMark, blackNum);
	}
	bool _IsBalance(Node* root) //要从红黑树的四个性质分别判断
	{
		if (root == nullptr)
		{
			return true;
		}
		if (root->_col == RED)       //判断性质一
		{
			cout << "根节点不是黑色结点" << endl;
			return false;
		}
		int benchMark = 0;         //黑色结点数量基准值.
		return PreCheck(root, benchMark, 0);
	}
	void _InOrder(Node* root)
	{
		if (root == nullptr)
		{
			return;
		}
		_InOrder(root->_left);
		cout << root->_kv.first << ":" << root->_kv.second << endl;
		_InOrder(root->_right);
	}
	void RotateL(Node* parent)         //左单旋
	{
		Node* subR = parent->_right;
		Node* subRL = subR->_left;

		parent->_right = subRL;
		if (subRL)
			subRL->_parent = parent;

		Node* ppNode = parent->_parent;

		subR->_left = parent;
		parent->_parent = subR;

		if (_root == parent)
		{
			_root = subR;
			subR->_parent = nullptr;
		}
		else
		{
			if (ppNode->_left == parent)
			{
				ppNode->_left = subR;
			}
			else
			{
				ppNode->_right = subR;
			}

			subR->_parent = ppNode;
		}

	}
	void RotateR(Node* parent)             //右单旋
	{
		Node* subL = parent->_left;
		Node* subLR = subL->_right;

		parent->_left = subLR;
		if (subLR)
		{
			subLR->_parent = parent;
		}

		Node* ppNode = parent->_parent;

		subL->_right = parent;
		parent->_parent = subL;

		if (_root == parent)
		{
			_root = subL;
			subL->_parent = nullptr;
		}
		else
		{
			if (ppNode->_left == parent)
			{
				ppNode->_left = subL;
			}
			else
			{
				ppNode->_right = subL;
			}

			subL->_parent = ppNode;
		}

	}
	Node* _root = nullptr;

};

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

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

相关文章

【数据结构】八大排序算法(梭哈)

目录 1.直接插入排序2. * 希尔排序关于希尔排序的时间复杂度 3.选择排序4. * 堆排序5.冒泡排序6. * 快速排序6.1递归快排6.1.1 hoare版6.1.2 挖坑法6.1.3 前后指针法6.1.4 关于每个区间操作的结束位置总是小于key6.1.5 关于有序原数据的效率优化 6.2 非递归快排 7. * 归并排序7…

计算机网络考试复习——第五章

本章考察范围为5.1 5.3 5.4这三部分。 该层传输的单位是报文段 5.1 运输层协议概述&#xff1a; 5.1.1 进程之间的通信&#xff1a; 运输层是向它上面的应用层提供通信服务。它属于面向通信部分的最高层&#xff0c;同时也是用户功能的最低层。 屏蔽作用&#xff1a;运输层…

flutter系列之:如何自定义动画路由

文章目录 简介自定义跳转使用flutter动画基础实现一个自定义的route总结 简介 flutter中有默认的Route组件&#xff0c;叫做MaterialPageRoute&#xff0c;一般情况下我们在flutter中进行跳转的话&#xff0c;只需要向Navigator中传入一个MaterialPageRoute就可以了。 但是Ma…

让你的three.js动起来

让你的three.js动起来 简介 本节主要是给实例添加动画效果&#xff0c;以及加了一些小插件用以实现帧率检测、gui可视化配置、动态监听屏幕大小变化刷新和鼠标操控功能。 引入的插件js&#xff1a; three.jsdat.gui.jsStats.jsTrackballControls.js 实际效果&#xff1a; …

Java 线程状态有哪些?

文章目录 Java 线程状态有哪些&#xff1f;初始状态&#xff08;NEW&#xff09;可运行状态&#xff08;RUNNABLE&#xff09;就绪状态运行状态 阻塞状态&#xff08;BLOCKED&#xff09;等待状态&#xff08;WAITING&#xff09;超时等待&#xff08;TIMED_WAITING&#xff09…

一次打靶场记录

题目提示 1、目录爆破 在对靶场进行信息收集、目录扫描之后发现结果存在www.zip,data.zp 两个备份文件 下载回来解压之后发现www.zip是网站备份文件&#xff0c;data.zip是数据库文件&#xff0c;存在一个maccms的数据库 苹果cms的数据库&#xff0c;导入本地数据库。 admin…

【并发编程Python】为什么Python这么慢

Python速度慢的所有原因 Python作为一门非常优秀的计算机语言&#xff0c;其速度慢给Python减了很多分&#xff0c;也是其一直被诟病的主要原因之一&#xff0c;通常情况下&#xff0c;Python比Java/C慢约5-10倍&#xff0c;在一些特殊的情况下Python甚至比C慢100~200倍&#x…

数据结构——B树和B+树

数据结构——B树和B树 一、B树1.B树的特征2.B树的插入操作3.B树的删除操作4.B树的缺点 二、B树B树的特征 平衡二叉树或红黑树的查找效率最高&#xff0c;时间复杂度是O(nlogn)。但不适合用来做数据库的索引树。 因为磁盘和内存读写速度有明显的差距&#xff0c;磁盘中存储的数…

玩转肺癌目标检测数据集Lung-PET-CT-Dx ——③整理、验证数据,建立Dataset对象

文章目录 数据整理整理出所有的dcm文件整理出所有的xml标注文件整理数据①——舍弃错误的标注文件整理数据②——两个标注文件指向同一个目标图片的情况封装函数&#xff0c;传入xml文件&#xff0c;显示标注效果 整理数据③——将PETCT的三通道图像转成平扫CT的单通道图像格式…

企业费控,驶向「一体化」

在数字化于企业内部各个环节实现平权、成为标配的当下&#xff0c;财务&#xff0c;这个被称为“控制企业生命力”的核心环节&#xff0c;是否应该拥有新的价值&#xff1f; 作者| 皮爷 出品|产业家 2022年年中&#xff0c;施伟和分贝通的团队接到一项“特殊需求”。 这个…

在职读研专业——劳动经济学

研究方向&#xff1a;劳动经济学主要研究劳动力市场现象及其所引起的劳动力资源配置等相关问题&#xff0c;包括劳动力供给、劳动力需求、就业、工资、人力资本投资、收入分配等。例如&#xff1a;劳动力市场失衡背后各种因素的变化&#xff0c;如何通过资源的配置使劳动资源配…

MySQL innobackupex 备份工具使用总结

前言 在MySQL备份以及搭建从库的过程中&#xff0c;我们针对比较小的库大多采用的是mysqldump&#xff0c;简单&#xff0c;方便。但是在大库进行搭建从库的时候&#xff0c;且数据库主机没有一个很好的存储来满足备份&#xff0c;这个时候就需要使用innobackupex来协助你来做一…

Pocsuite3框架POC/EXP编写练习:Flask(Jinja2) 服务端模板注入漏洞

Pocsuite3 是由知道创宇 404 实验室打造的一款基于 GPLv2 许可证开源的远程漏洞测试框架。可以用来编写POC/EXP&#xff0c;今天借助Flask框架下的SSTI漏洞练习记录一下Pocsuite3框架的配置和编写过程。 官方文档&#xff1a;Pocsuite3 是什么&#xff1f; | Pocsuite3 安装 …

小黑继续跟着沛奇老师学携程:携程基础3

3.异步编程 3.1事件循环 理解成一个死循环&#xff0c;去检测并执行某些代码 # 伪代码任务列表 [任务1&#xff0c;任务2&#xff0c;任务3&#xff0c;...] while True:可执行的任务列表&#xff0c;已完成的任务列表 去任务列表中检查所有任务&#xff0c;将可执行和已完…

安装、启动和登陆doccano

一、安装 1、使用的Pycharm安装的doccano 2、初始化数据库 doccano init 3、创建用户名和密码 # admin是用户名&#xff0c;pass是密码&#xff0c;都可以自定义 doccano createuser --username admin --password pass 二、启动 1、在一个Terminal终端启动webserver服务 …

自适应控制专栏目录及介绍

目录 自适应控制专栏目录及介绍第一篇&#xff1a;[具有不确定参数系统的自适应跟踪控制设计_ADi_hhh的博客-CSDN博客](https://blog.csdn.net/qq_45830323/article/details/129713051)第二篇&#xff1a;[&#xff08;SISO&#xff09;单输入单输出系统的模型参考自适应控制_A…

cdh Hue集成sentry的权限管理详细步骤

hue登录hue的第一个用户要用hue用户创建,默认hue为超级用户,hue用户页面的权限可以管理很多用户操作,比如查看hdfs目录,使用其他组件,授权功能等等 一.hive的配置 1.关闭模拟,开启sentry 2.添加配置(注意配置的位置) <property> <name>sentry.hive.tes…

【C++11】lambda表达式

目录 lambda表达式 1.1 lambda表达式出现的原因 1.2 lambda表达式语法 1.3 函数对象与lambda表达式 lambda表达式 1.1 lambda表达式出现的原因 在C98中&#xff0c;如果想要对一个数据集合中的元素进行排序&#xff0c;可以使用 std::sort 方法 测试代码 #include <…

苹果撤离中国市场?中国市场太重要,印度制造是备胎

苹果在中国之行后&#xff0c;却计划进一步扩大印度制造iPhone的比例&#xff0c;甚至将iPhone15全数交给印度制造&#xff0c;业界因此认为苹果正在离开中国市场&#xff0c;然而这完全是臆想&#xff0c;中国市场对苹果来说仍然非常重要&#xff0c;它不会轻易舍弃这个市场。…

【Unity VR开发】结合VRTK4.0:添加碰撞忽略器

语录&#xff1a; 最远的旅行&#xff0c;是从自己的身体到自己的心&#xff0c;是从一个人的心到另一个人的心。坚强不是面对悲伤不流一滴泪&#xff0c;而是擦干眼泪后微笑面对以后的生活。 前言&#xff1a; 模块化提供了一种允许两个或者多个对象忽略彼此碰撞的方法&#x…