C++ | 红黑树以及map与set的封装

news2024/9/24 11:24:43

目录

前言

一、红黑树

1、红黑树的基本概念

2、红黑树相关特性

3、红黑树结点的定义

4、红黑树的查找

5、红黑树的插入

6、二叉树的拷贝构造与析构

7、红黑树的检测

8、红黑树总结

二、map与set的封装

1、红黑树的结点

2、红黑树迭代器

3、set的封装

4、map的封装

三、源码仓库 


前言

        前面我们讲过普通的二叉搜索树以及特殊的二叉搜索树 --- AVL树,今天,我们也要实现一种特殊的二叉搜索树,该树被广泛使用在STL库中的map与set的封装,这就是我们的 红黑树 ,怎么样,听名字就知道很霸气吧;本文就带着大家实现一个简单版本的红黑树以及用这个版本的红黑树对map与set进行封装;

一、红黑树

1、红黑树的基本概念

        红黑树是二叉搜索树的一种,它与普通二叉搜索树不同之处是它有一些规则对其进行了限制,使其最长路径结点个数不会超出最短路径结点个数的两倍,下面我们就来看看红黑树相关特性;

2、红黑树相关特性

1、每个结点不是红色就是黑色

2、根节点是黑色的

3、如果一个节点是红色的,则它的两个孩子结点是黑色的(不会有连续的红色结点)

4、对于每个结点,从该结点到其所有后代叶结点的简单路径上,均 、包含相同数目的黑色结点(每条路径上的黑色结点数量相同)

5、每个叶子结点都是黑色的(此处的叶子结点指的是空结点

 思考:为什么满足以上特性,最长路径的结点个数不会超出最短路径节点个数的两倍呢?

        根据上述5个条件,假设每条路径的黑色结点个数为3,那么最短路径应就是全是黑色结点,最长是黑红将间的路径结点;

        既然最长路径与最短路径我们都可以得到,根据上述,我们不难求证出最长路径的结点个数不会超出最短路径节点个数的两倍;

3、红黑树结点的定义

        我们首先枚举出颜色,然后我们的红黑树结点用三叉链的方式进行连接,方便后续旋转调整;这里,其实有一个问题,我们构造的结点默认是红色还是黑色呢?

        其实这个问题本质就是,对于新插入的结点,你更愿意违反规则3来进行调整,还是愿意违法规则4来进行后续的调整呢?答案是肯定的,当然愿意违反规则3,如果出现了连续的红色结点,我们可能仅仅只需要调整该红色结点所在子树;而违反规则4,我们需要调整整棵树,因为一颗树的黑色节点增多了,其他该所在路径也会增多,调整更麻烦;因此构造函数我们更愿意默认将结点置为红色;

	enum Color
	{
		RED,
		BLACK
	};
	template<class K, class V>
	struct RBTreeNode
	{
		RBTreeNode(const std::pair<K, V> kv)
			:_left(nullptr)
			, _right(nullptr)
			, _parent(nullptr)
			, _color(RED)
			, _kv(kv)
		{}

		RBTreeNode<K, V>* _left;
		RBTreeNode<K, V>* _right;
		RBTreeNode<K, V>* _parent;
		Color _color;
		std::pair<K, V> _kv;
	};

4、红黑树的查找

        红黑树的查找方式与普通二叉搜索树并无二异,这里就不做过多介绍了;此处顺便将红黑树的框架写出来了;

	template<class K, class V>
	class RBTree
	{
	public:
		typedef RBTreeNode<K, V> Node;
		// 查找
		bool find(const K& key)
		{
			if (_root == nullptr)
			{
				return false;
			}
			Node* cur = _root;
			while (cur)
			{
				if (key < cur->_kv.first)
				{
					cur = cur->_left;
				}
				else if (key > cur->_kv.first)
				{
					cur->_right;
				}
				else
				{
					// 找到了
					return true;
				}
			}
			return false;
		}
	private:
		Node* _root = nullptr;
	};

5、红黑树的插入

        红黑树的插入操作才是关键,也是本文讲解红黑树的核心;红黑树插入数据与以前的二叉搜索树插入数据的代码相同,主要是后面的调整操作,调整主要分为三种情况,我们先将插入逻辑的框架搭建起来,框架如下所示;

		// 插入
		bool insert(const std::pair<K, V> kv)
		{
			// 首次插入
			if (_root == nullptr)
			{
				_root = new Node(kv);
				_root->_color = BLACK;
				return true;
			}
			Node* parent = nullptr;
			Node* cur = _root;
			while (cur)
			{
				if (kv.first < cur->_kv.first)
				{
					parent = cur;
					cur = cur->_left;
				}
				else if (kv.first > cur->_kv.first)
				{
					parent = cur;
					cur = cur->_right;
				}
				else
				{
					//有对应的key,返回false
					return false;
				}
			}
			// 找到插入位置了,插入数据
			cur = new Node(kv);
			if (kv.first < parent->_kv.first)
			{
				parent->_left = cur;
			}
			else
			{
				parent->_right = cur;
			}
			cur->_parent = parent;

            // 以下为调整代码......暂未填充
            // .......


            return true;
        }

        接下来我带着大家逐步分析以下的三种情况;情况一是cur为红,parent为红,grandfather为黑,uncle为红,以下分别简称为c、p、g、u;

注意:我们举例三种情况均为parent为grandfather的左孩子,右孩子的情况与左孩子类似

        以下为具象图,这种情况可以一只沿着祖父往上调整,可能会调整几次,如下就调整了两次;

        接着就是情况二,情况二看起来分为两种,实际就是一种情况,以下分别情况二的两种具象图;

注意:情况二调整完以后直接退出循环,无需继续往上调整;

        这里的右单旋就是我们前面AVL树的右单旋,代码也一模一样;如果parent是grandfather的左边就是左单旋,所以上面说我们进分析一边即可;

        情况三实则是由情况一变成的,我们可以把情况三变成情况二,进行处理,因为cur的位置不同,而必须进行双旋;

注意:情况三调整完以后直接退出循环,无需继续往上调整;

        上述就是三种调整方式,我们根据上图可写出如下代码;

		// 插入
		bool insert(const std::pair<K, V> kv)
		{
			// 首次插入
			if (_root == nullptr)
			{
				_root = new Node(kv);
				_root->_color = BLACK;
				return true;
			}
			Node* parent = nullptr;
			Node* cur = _root;
			while (cur)
			{
				if (kv.first < cur->_kv.first)
				{
					parent = cur;
					cur = cur->_left;
				}
				else if (kv.first > cur->_kv.first)
				{
					parent = cur;
					cur = cur->_right;
				}
				else
				{
					//有对应的key,返回false
					return false;
				}
			}
			// 找到插入位置了,插入数据
			cur = new Node(kv);
			if (kv.first < parent->_kv.first)
			{
				parent->_left = cur;
			}
			else
			{
				parent->_right = cur;
			}
			cur->_parent = parent;
			// 调整
			while (parent && parent->_color == RED)
			{
				// grandfather必然存在,不用判空,因为parent为红,不可能为根
				Node* grandfather = parent->_parent;
				if (grandfather->_left == parent)
				{
					// 情况一 uncle 存在且为红
					Node* uncle = grandfather->_right;
					if (uncle && uncle->_color == RED)
					{
						// 直接变色
						grandfather->_color = RED;
						parent->_color = BLACK;
						uncle->_color = BLACK;
						// 继续向上调整
						cur = grandfather;
						parent = cur->_parent;
					}
					else // 情况二 + 情况三
					{
						// 情况二
						if (parent->_left == cur)
						{
							rotateR(grandfather);
							parent->_color = BLACK;
							grandfather->_color = RED;
						}
						else // 情况三
						{
							rotateL(parent);
							rotateR(grandfather);
							cur->_color = BLACK;
							grandfather->_color = RED;
						}
						break;
					}
				}
				else // grandfather->right == parent
				{
					Node* uncle = grandfather->_left;
					// 情况一
					if (uncle && uncle->_color == RED)
					{
						// 变色
						parent->_color = BLACK;
						uncle->_color = BLACK;
						grandfather->_color = RED;
						// 继续像上调整
						cur = grandfather;
						parent = cur->_parent;
					}
					else // 情况二 + 情况三
					{
						// 情况二
						if (parent->_right == cur)
						{
							rotateL(grandfather);
							grandfather->_color = RED;
							parent->_color = BLACK;
						}
						else
						{
							rotateR(parent);
							rotateL(grandfather);
							cur->_color = BLACK;
							grandfather->_color = RED;
						}
						break;
					}
				}
				// 上面可能会将root改为红色
				_root->_color = BLACK;
			}
			return true;
		}

        补充一下我们的旋转代码; 

		void rotateL(Node* parent)
		{
			Node* subR = parent->_right;
			Node* subRL = subR->_left;
			// subRL挂在parent的右边
			parent->_right = subRL;
			if (subRL)
				subRL->_parent = parent;

			// 提前保存parent的父亲结点
			Node* pparent = parent->_parent;

			// parent挂到subR的左边
			subR->_left = parent;
			parent->_parent = subR;

			// 将subR与上层链接
			if (_root == parent)
			{
				_root = subR;
				subR->_parent = nullptr;
			}
			else
			{
				if (pparent->_left == parent)
				{
					pparent->_left = subR;
					subR->_parent = pparent;
				}
				else
				{
					pparent->_right = subR;
					subR->_parent = pparent;
				}
			}
		}

		void rotateR(Node* parent)
		{
			Node* subL = parent->_left;
			Node* subLR = subL->_right;
			// 将subLR挂到parent的左边
			parent->_left = subLR;
			if (subLR)
				subLR->_parent = parent;

			// 提前保存parent的父亲结点
			Node* pparent = parent->_parent;

			// 将parent挂到subL的右边
			subL->_right = parent;
			parent->_parent = subL;

			// 将这颗子树的根结点与上面结点链接
			if (parent == _root)
			{
				_root = subL;
				subL->_parent = nullptr;
			}
			else
			{
				if (pparent->_left == parent)
				{
					pparent->_left = subL;
					subL->_parent = pparent;
				}
				else
				{
					pparent->_right = subL;
					subL->_parent = pparent;
				}
			}
		}

 

6、二叉树的拷贝构造与析构

        二叉树的拷贝构造我们通过先序的方式进行拷贝构造;

		RBTree(const RBTree& rbt)
		{
			if (this != &rbt)
			{
				_root = _construction(rbt._root, nullptr);
			}
		}
		Node* _construction(Node* root, Node* parent)
		{
			if (root == nullptr)
				return nullptr;

			Node* newroot = new Node(root->_val);
			newroot->_color = root->_color;
			newroot->_parent = parent;
			parent = newroot;
			newroot->_left = _construction(root->_left, parent);
			newroot->_right = _construction(root->_right, parent);
			return newroot;
		}

        二叉树的析构我们则需用类似后序遍历的方式进行逐个结点析构;

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

7、红黑树的检测

        前面代码我们完成了对红黑树的各种操作,现在我们来检测一下我们的红黑树是否为正确的红黑树;具体代码如下;

		void inorder()
		{
			_inorder(_root);
			std::cout << std::endl;
		}

		bool is_balance()
		{
			if (_root->_color == RED)
				return false;
			int maxBlackNode = -1;
			return _is_balance(_root, 0, maxBlackNode);
		}

		int height()
		{
			return _height(_root);
		}

		int _height(Node* root)
		{
			if (root == nullptr)
			{
				return 0;
			}
			int left_height = _height(root->_left);
			int right_height = _height(root->_right);
			return left_height > right_height ? left_height + 1 : right_height + 1;
		}

		bool _is_balance(Node* root, int curBlackNode, int& maxBlackNode)
		{
			if (root == nullptr)
			{
				// 首次进入更新最大黑节点数量
				if (maxBlackNode == -1)
				{
					maxBlackNode = curBlackNode;
					//cout << "黑色结点个数:" << maxBlackNode << endl;
				}

				if (curBlackNode != maxBlackNode)
				{
					cout << "路径上的黑色结点数量不一致" << endl;
					return false;
				}
				//cout << curBlackNode << endl;
				return true;
			}

			if (root->_color == BLACK)
			{
				curBlackNode++;
			}

			if (root->_color == RED && root->_parent->_color == RED)
			{
				//cout << root->_parent->_kv.first << "颜色异常" << endl;
				return false;
			}
			return _is_balance(root->_left, curBlackNode, maxBlackNode) 
                && _is_balance(root->_right, curBlackNode, maxBlackNode);
		}

		void _inorder(Node* root)
		{
			if (root == nullptr)
				return;
			_inorder(root->_left);
			cout << KeyOfT()(root->_val) << " ";
			_inorder(root->_right);
		}

        我们可以通过如上的is_balance函数检测红黑树是否有误,如果还不放心我们可以通过如下代码进行测试;同时也对比了与AVL树的差别;

void test_RBTree2()
{
	srand((unsigned int)time(nullptr));
	const int N = 1000000;
	MySpace::RBTree<int, int> rb1;
	MySpace::AVLTree<int, int> avl1;
	for (int i = 0; i < N; i++)
	{
		int num = rand() + i;
		rb1.insert(make_pair(num, num));
		avl1.insert(make_pair(num, num));
	}
	cout << "RB max height :" << rb1.height() << endl;
	cout << "RB is balance :" << rb1.is_balance() << endl;

	cout << "AVL max height :" << avl1.height() << endl;
	cout << "AVL is balance :" << avl1.is_balance() << endl;
}

8、红黑树总结

        有人发现我们红黑树并没有AVL树那样完全平衡,那么红黑树的意义是什么呢?实际上,我们的map与set都是使用红黑树来进行封装的,那么有的小伙伴就疑惑了,那么为什么不用AVL树进行封装呢?AVL树不是更加平衡,搜索效率更高么?实际上的确如此,搜索效率,有时候红黑树确实不如AVL树,可是我们不能但从查找来评判一棵树的好坏,我们的红黑树总体效率实际上要比我们的AVL树高的,因为我们的红黑树不是绝对平衡的,因此我们旋转的次数并不会有AVL树那么频繁,总体效率是比AVL树高的;

二、map与set的封装

        实际上,我们的map与set仅仅只是套了一层红黑树的外壳接下来,我们稍稍修改上述的红黑树,对map与set完成封装;

1、红黑树的结点

        这里的模板参数变成了T,当我们的set来的时候,传入的是Key,当我们的map来的时候传入的是pair<Key, Value>;

	template<class T>
	struct RBTreeNode
	{
		RBTreeNode(const T& val)
			:_left(nullptr)
			, _right(nullptr)
			, _parent(nullptr)
			, _color(RED)
			, _val(val)
		{}

		RBTreeNode<T>* _left;
		RBTreeNode<T>* _right;
		RBTreeNode<T>* _parent;
		Color _color;
		T _val;
	};

2、红黑树迭代器

        我们要实现map与set的迭代器,就得先实现红黑树的迭代器;

	template<class T, class Ref, class Ptr>
	struct RBTree_iterator
	{
		typedef RBTreeNode<T> Node;
		typedef RBTree_iterator<T, Ref, Ptr> Self;
		typedef RBTree_iterator<T, T&, T*> iterator;
		RBTree_iterator(Node* node)
			:_node(node)
		{}
		// 1、如果该类是普通迭代器,则作用为拷贝构造
		// 2、如果该类是const迭代器,则作用为用普通迭代器取构造const迭代器
		RBTree_iterator(const iterator& it)
			:_node(it._node)
		{}

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

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

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

		Self& operator++()
		{
			Node* cur = _node;
			if(cur)
			{
				if (cur->_right != nullptr)
				{
					// 右子树的最左节点
					cur = cur->_right;
					while (cur->_left)
					{
						cur = cur->_left;
					}
					_node = cur;
				}
				else
				{
					// 找当前结点是父亲左结点的父亲
					Node* parent = cur->_parent;
					while (parent && parent->_right == cur)
					{
						cur = parent;
						parent = parent->_parent;
					}
					_node = parent;
				}
			}
			return *this;
		}
		Self& operator--()
		{
			Node* cur = _node;
			if (cur)
			{
				if (cur->_left != nullptr)
				{
					// 左子树的最右结点
					Node* subR = cur->_left;
					while (subR->_right)
					{
						subR = subR->_right;
					}
					_node = subR;
				}
				else
				{
					// 找当前结点是父亲的右节点的父亲
					Node* parent = cur->_parent;
					while (parent && parent->_left)
					{
						parent = parent->_parent;
					}
					_node = parent;
				}
			}
			return *this;
		}

		Node* _node;
	};

3、set的封装

        接下来我们仅仅只需要将红黑树封装成set即可,直接套一层外壳;

	template<class Key, class Value>
	struct SetKeyOfT
	{
		const Key& operator()(const Value& val)
		{
			return val;
		}
	};

	template<class Key>
	class set
	{
	public:
		typedef Key key_type;
		typedef Key value_type;
		typedef RBTree<key_type, value_type, SetKeyOfT<key_type, value_type>> Tree;

		typedef typename RBTree<key_type, value_type, SetKeyOfT<key_type, value_type>>::const_iterator iterator;
		typedef typename RBTree<key_type, value_type, SetKeyOfT<key_type, value_type>>::const_iterator const_iterator;

		pair<iterator, bool> insert(const value_type& val)
		{
			return _t.insert(val);
		}

		iterator begin()
		{
			return _t.begin();
		}
		iterator end()
		{
			return _t.end();
		}

		const_iterator begin() const
		{
			return _t.begin();
		}
		const_iterator end() const
		{
			return _t.end();
		}

	private:
		Tree _t;
	};

        解释一下这里的KeyOfT,这里只是一个仿函数,我们仅仅想通过这个取出Key值,因为红黑树的T也不确定是Key还是KeyValue模型;

注意:由于set的结点中的Key在树中不能被修改,因此这里set的普通迭代器和const迭代器都是红黑树的const迭代器;

4、map的封装

        map的封装同样如此,我们仅仅只是套了一层红黑树的外壳;

	template<class Key, class T>
	struct MapKeyOfT
	{
		const Key& operator()(const T& val)
		{
			return val.first;
		}
	};

	template<class Key, class Value>
	class map
	{
	public:
		typedef Key key_type;
		typedef std::pair<Key, Value> value_type;
		typedef RBTree<const Key, value_type, MapKeyOfT<key_type, value_type>> Tree;

		typedef typename RBTree<const key_type, value_type, MapKeyOfT<key_type, value_type>>::iterator iterator;
		typedef typename RBTree<const key_type, value_type, MapKeyOfT<key_type, value_type>>::const_iterator 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();
		}

		std::pair<iterator, bool> insert(const value_type& val)
		{
			return _t.insert(val);
		}


	private:
		Tree _t;
	};

三、源码仓库 

        关于红黑树、map与set的所有源码小编都放进代码仓库中供大家参考;以下为源码仓库;

源码仓库

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

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

相关文章

error:0308010C:digital envelope routines::unsupported(Vue2报错)

原因:node.js版本过高&#xff0c; 解决方案&#xff0c;在终端输入以下命令 set NODE_OPTIONS--openssl-legacy-provider 然后再package.json里面添加一行 "dev_t": "set NODE_OPTIONS\"--openssl-legacy-provider\" & npm run dev\n" 然后…

又一个产业即将被中国超越,韩国急了,将提供1.2万亿支持

日前韩媒报道指韩国计划推出1.2万亿韩元的OLED面板产业资助计划&#xff0c;希望帮助韩国两大OLED面板企业三星和LGD巩固OLED面板的技术领先优势&#xff0c;主要是因为它们正面临中国面板厂商的狙击&#xff0c;即将被超越。 据悉韩国推出的1.2万亿韩元资助计划&#xff0c;其…

在EF Core中为数据表按列加密存储

假设有User表 public class User : Entity<int> {public int Id { get; set; }public string UserName { get; set; }public string Name { get; set; }public string IdentificationNumber { get; set; } }其中有身份证号码IdentificationNumber列&#xff0c;需要加密…

计算机网络知识点汇总(持续更新)

文章目录 第一章 概述1.1 计算机网络在信息时代的作用信息服务基础设施我国互联网发展状况 1.2 因特网概述网络、互联网、因特网的基本概述因特网发展的三个阶段因特网的标准化工作 1.3 三种交换方式电路交换分组交换报文交换 1.4 计算机网络的定义和分类定义分类按交换技术按使…

【雕爷学编程】Arduino动手做(175)---机智云ESP8266开发板模块4

37款传感器与执行器的提法&#xff0c;在网络上广泛流传&#xff0c;其实Arduino能够兼容的传感器模块肯定是不止这37种的。鉴于本人手头积累了一些传感器和执行器模块&#xff0c;依照实践出真知&#xff08;一定要动手做&#xff09;的理念&#xff0c;以学习和交流为目的&am…

互联网广告投放算法是怎么回事?这本书给你答案

目录 内容简介 作者简介 读者对象 书本目录 文末自购链接 广告平台的建设和完善是一项长期工程。例如&#xff0c;谷歌早于2003年通过收购Applied Semantics开展Google AdSense 项目&#xff0c;而直到20年后的今天&#xff0c;谷歌展示广告平台仍在持续创新和提升。广告平…

QT编写的串口助手

QT编写的串口助手 提前的知识 创建UI界面工程 找帮助文档 添加串口的宏

list与erase()

运行代码&#xff1a; //list与erase() #include"std_lib_facilities.h" //声明Item类 struct Item {string name;int iid;double value;Item():name(" "),iid(0),value(0.0){}Item(string ss,int ii,double vv):name(ss),iid(ii),value(vv){}friend istr…

2023-07-11——华中科技大计算机组成原理

windows下用nginx配置https服务器 1.安装nginx 先到nginx官网下在nginx http://nginx.org/en/download.html 将下载好的文件解压出来修改文件名为 nginx ,然后拷贝到C盘下&#xff0c;目录如下&#xff1a; 运行 nginx start nginx 验证 在浏览器中输入 localhost 访问即可&a…

随笔:信息系统项目管理师(软考高级2023)考试指南

1、软考的级别设置 1、全国计算机软件资格考试设三个级别层次&#xff0c;五个专业&#xff0c;共有27种岗位资格考试 2、除了初级信息处理技术员为上机考试&#xff0c;其他均为笔试 3、信息系统项目管理师、系统规划与管理师、系统集成项目管理工程师考试形式相对考验记忆…

ObjectArx 设置填充透明度问题

初始化透明度参数AcCmTransparency对象时,需要调用setAlpha设置透明度值,这里传入的值是0255,但cad特性面板上显示的是090,且经过测试发现,传入值与特性面板显示的值也是不同的,比如传入90,显示64,百度搜索了个寂寞,最后还是在谷歌找到了答案,原来设置的值和特性面板…

【Rasa】入门案例学习

Rasa初体验--构建对话机器人 NLU数据 version: "3.1"nlu:- intent: greetexamples: |- Hi- Hey!- Hello- Good day- Good morning- intent: subscribeexamples: |- I want to get the newsletter- Can you send me the newsletter?- Can you sign me up for the ne…

寄存器分配:图着色算法

寄存器分配&#xff1a;图着色算法 背景活跃分析寄存器冲突图图着色算法溢出 背景 在编译器的中间表示中&#xff0c;一般会设定虚拟寄存器有无限多个&#xff08;方便优化&#xff09;&#xff0c;而真实的物理寄存器是有限的&#xff0c;因而编译器后端在将中间表示翻译成目…

初步了解C++模板

一、函数模板 如果我们要写一个交换两个变量值的函数Swap&#xff0c;那么我们得对每一种类型都写一个&#xff0c;以便适用不同类型的参数&#xff0c;但是有了模板之后&#xff0c;可以简化操作 template<class T> void Swap(T& x, T& y) {T tmp x;x y;y …

百题千解计划【CSDN每日一练】订班服(附解析+多种实现方法:Python、Java、C、C++、C#、Go、JavaScript)

如果决意去做一件事了,就不要再问自己和别人值不值得,心甘情愿才能理所当然,理所当然才会义无反顾。 🎯作者主页: 追光者♂🔥 🌸个人简介: 💖[1] 计算机专业硕士研究生💖 🌟[2] 2022年度博客之星人工智能领域TOP4🌟 🏅[3] 阿里云社区特邀专…

大数据处理框架-Spark DataFrame构造、join和null空值填充

1、Spark DataFrame介绍 DataFrame是Spark SQL中的一个概念&#xff0c;它是一个分布式的数据集合&#xff0c;可以看作是一张表。DataFrame与RDD的主要区别在于&#xff0c;前者带有schema元信息&#xff0c;即DataFrame所表示的二维表数据集的每一列都带有名称和类型。 2、构…

Statefulset部署应用

上一部分我们分享到了使用 RS 没有办法让自己管理的多个 pod 都有一个独立的持久化声明&#xff0c;RS 没有办法在指定模板中对不同的 pod 做差异化处理 使用多个 RS 来分别管理自己的的一个 pod&#xff0c;当我们扩缩容的时候&#xff0c;也会出现问题&#xff0c;老的 pod …

10. Mybatis 项目的创建

目录 1. Mybatis 概念 2. 第一个 Mybits 查询 2.1 创建数据库和表 2.2 添加 Mybatis 框架支持 2.3 添加配置文件 2.4 配置 MyBatis 中的 XML 路径 2.5 添加业务代码 在学习 Mybatis 之前&#xff0c;我们需要知道 Mybatis 和 Spring 没有任何的关系。如果一定要强调二者…

UniSSOView 任意命令执行复现

免责声明 技术文章仅供参考,任何个人和组织使用网络应当遵守宪法法律,遵守公共秩序,尊重社会公德,不得利用网络从事危害国家安全、荣誉和利益,未经授权请勿利用文章中的技术资料对任何计算机系统进行入侵操作。利用此文所提供的信息而造成的直接或间接后果和损失,均由使…

什么是专业级OV通配符https证书

通配符SSL证书指的是SSL数字证书中可以用一张SSL数字证书保护主域名以及主域名下所有子域名的数字证书。我们按照验证方式将通配符SSL数字证书分为DV基础型和OV企业型通配符SSL证书两种&#xff0c;专业级的OV通配符SSL证书指的是需要验证域名所有权以及申请主体真实性的OV企业…