【数据结构】红黑树的插入与验证

news2025/1/12 3:50:08

文章目录

  • 一、基本概念
    • 1.时代背景
    • 2. 基本概念
    • 3.基本性质
  • 二、实现原理
    • 1. 插入
      • 1.1变色
      • 1.2旋转+变色
        • ①左旋
        • ②右旋
        • ③右左双旋
        • ④左右双旋
    • 2.验证
  • 源码
  • 总结

一、基本概念

1.时代背景

  • 1972年鲁道夫·拜尔(Rudolf Bayer)发明了一种数据结构,这是一种特殊的B树4阶情况。这些树保持了从根到叶的所有路径,节点数量相同,创造了完美平衡的树。但是,它们不是二叉搜索树。拜耳在他的论文中称它们为“对称二元B树”。这是红黑树的起源。

在这里插入图片描述

  • 在1978年的一篇论文“平衡树的二色框架”中,列奥尼达斯·吉巴斯(Leonidas J. Guibas )和罗伯特·塞奇威克(Robert Sedgewick)从对称的二元B树中推导出了红黑树。选择“红色”是因为它是作者在施乐PARC工作时可用的彩色激光打印机产生的最好看的颜色。吉巴斯的另一个回应说,这是因为他们可以使用红色和黑色的笔来画树。
    在这里插入图片描述
    在这里插入图片描述

第一张为——列奥尼达斯·吉巴斯,第二张为——罗伯特·塞奇威克。

  • 1993年,Arne Andersson引入了右倾树的想法,以简化插入和删除操作。
    在这里插入图片描述
  • 1999年,Chris Okasaki展示了如何使插入操作纯粹功能化。它的平衡功能只需要处理 4 个不平衡情况和一个默认平衡情况。
    在这里插入图片描述

详细请看:维基百科

2. 基本概念

红黑树,是一种二叉搜索树,但在每个结点上增加一个存储位表示结点的颜色,可以是Red或
Black。 通过对任何一条从根到叶子的路径上各个结点着色方式的限制,红黑树确保没有一条路 径会比其他路径长出俩倍,因而是接近平衡的。

  • 实现平衡的关键:最长路径小于等于最短路径的两倍

3.基本性质

  1. 每个结点不是红色就是黑色
  2. 根节点是黑色的
  3. 如果一个节点是红色的,则它的两个孩子结点是黑色的(如果一个结点是黑色的,则其两个孩子可以是红的也可以是黑的。)
  4. 对于每个结点,从该结点到其所有后代叶结点的简单路径上,均包含相同数目的黑色结点
  5. 每个叶子结点都是黑色的(此处的叶子结点指的是空结点)

强调:2,3,4点是有关联的,且是最关键的3点。

  • 假设根节点如果是红的,那么插入的结点就是只能是黑的(3),那么就违背了(4)。
  • 对于3分析,孩子结点为空,但空节点也被理解成黑色(5),因此(5)是用来辅助(3)的。
  • 对于4分析,可推理出两个结论——
    1 . 插入结点必须为红色的。
    2 . 满足最长路径小于最短路径的两倍(概念)。对此点可以看做间隔问题,即n个数之间(不算头一个数),有n个间隔,即n个黑结点(不算根节点),之间最多有n个红结点。

二、实现原理

1. 插入

1.1变色

根本逻辑:基于每条路径的黑色结点不变。

第一种变色方式:
在这里插入图片描述
这样变,是不是每条路径的黑色结点数没变呢?

那这样变的前提是什么呢?

  • 黑色结点的左右孩子为红且不为空。

那什么时候发生变色呢?

  • 基于性质3,红色结点的两个孩子必须为黑,但由4我们可以推出每次插入结点必须为红,那这时候我们按照4的原则进行处理,使处理结果符合3即可,怎么处理,就是进行变色。

在这里插入图片描述
此时,parent的右边进行插入新节点,且parent在grandfather的左边。

在这里插入图片描述

此时在parent的右边进行插入,且parent为grandfather的左节点。

  • 总结
  1. 变色的前提是每条路径的黑色结点不变
  2. uncle非空且为红,且parent为红,变grandfather为红,parent与uncle为黑。

继续分析,如果grandfather为红,其父节点是否可能为红呢?

  • 答案是可能的。

因此我们需要继续往上更新:

  1. 更新cur为grandfather
  2. parent为cur的parent

接着分析,如果grandfather为根节点呢?

  • 由于性质2,我们需要再次修改根节点的颜色为黑。

1.2旋转+变色

前面我们分析了一种简单的只需变色的情况,我们下面接着分析另外一种情况。

第二种变色需要在旋转的基础上进行分类讨论,具体情况有四种。

①左旋

在这里插入图片描述

补充:当uncle为黑结点时,parent的左子树不为空且根节点为黑色,cur的左右子树同理,这里不过多分析了,因为具体情况过多分析容易提高理解难度。

  • 开始时parent在grandfather右边,且cur在parent的右边

②右旋

在这里插入图片描述
对uncle的补充同左旋

  • 开始时parent在grandfather左边,且cur在parent的左边

③右左双旋

在这里插入图片描述
对uncle的补充同左旋

  • 开始时parent在grandfather的右边,且cur在parent的左边

④左右双旋

在这里插入图片描述
对uncle的补充同左旋

  • 开始时parent在grandfather的左边,且cur在parent的右边

  • 总结
    根据parent的位置我们可以大致分为两种情况:

  1. parent在grandfather的左边
    在这里插入图片描述

  2. parent在grand的右边
    在这里插入图片描述
    其实不难看出,AVL和红黑树都会进行旋转,只是AVL树旋转后处理的是平衡因子,红黑树旋转后处理的是变色,归根结底终究都是为了让树达到平衡。

  • 核心代码

//判断是否要进行旋转变色
//当父节点为红色说明要进行判断
while (parent && parent->_col == RED)
{
	//爷爷结点
	Node* grandfather = parent->_parent;
	if (grandfather->_left == parent)
	{
		Node* uncle = grandfather->_right;
		if (uncle && uncle->_col == RED)//如果uncle存在且为红色
		{
			//变色
			parent->_col = uncle->_col = BLACK;
			grandfather->_col = RED;

			//继续往上迭代进行分析
			cur = grandfather;
			parent = cur->_parent;
		}
		else//如果uncle不存在或者为黑色
		{

			//旋转
			if (parent->_left == cur)
			{
				RotateR(grandfather);
				parent->_col = BLACK;

				cur->_col = grandfather->_col = RED;
			}
			else
			{
				RotateL(parent);
				RotateR(grandfather);

				cur->_col = BLACK;

				parent->_col = grandfather->_col = RED;
			}

			break;
		}
	}
	else//grandfather->_right == parent
	{
		Node* uncle = grandfather->_left;

		if (uncle && uncle->_col == RED)
		{
			//变色
			grandfather->_col = RED;
			parent->_col = uncle->_col = BLACK;

			//往上更新
			cur = grandfather;
			parent = cur->_parent;
		}
		else
		{
			if (parent->_right == cur)
			{
				//旋转
				RotateL(grandfather);

				//变色
				parent->_col = BLACK;
				grandfather->_col = cur->_col = RED;
			}
			else
			{
				RotateR(parent);
				RotateL(grandfather);

				cur->_col = BLACK;
				grandfather->_col = parent->_col = RED;
			}
			break;
		}
	}
}
  • 插入代码
bool insert(const pair<Key, Val>& val)
{
	//第一步:插入操作
	//如果根节点为空
	if (_root == nullptr)
	{
		_root = new Node(val);
		_root->_col = BLACK;
		return true;
	}
	else
	{
		Node* cur = _root, * parent = _root;
		while (cur)
		{
			if (cur->_key > val.first)
			{
				parent = cur;
				cur = cur->_left;

			}
			else if (cur->_key < val.first)
			{
				parent = cur;
				cur = cur->_right;

			}
			else
			{
				return false;
			}
		}
		cur = new Node(val);
		if (parent->_key > val.first)
		{
			parent->_left = cur;
		}
		else
		{
			parent->_right = cur;
		}
		//更新新增结点的_parent
		cur->_parent = parent;

		//判断是否要进行旋转变色
		//当父节点为红色说明要进行判断
		while (parent && parent->_col == RED)
		{
			//爷爷结点
			Node* grandfather = parent->_parent;
			if (grandfather->_left == parent)
			{
				Node* uncle = grandfather->_right;
				if (uncle && uncle->_col == RED)
				//如果uncle存在且为红色
				{
					//变色
					parent->_col = uncle->_col = BLACK;
					grandfather->_col = RED;

					//继续往上迭代进行分析
					cur = grandfather;
					parent = cur->_parent;
				}
				else//如果uncle不存在或者为黑色
				{

					//旋转
					if (parent->_left == cur)
					{
						RotateR(grandfather);
						parent->_col = BLACK;

						cur->_col = grandfather->_col = RED;
					}
					else
					{
						RotateL(parent);
						RotateR(grandfather);

						cur->_col = BLACK;

						parent->_col = grandfather->_col = RED;
					}

					break;
				}
			}
			else//grandfather->_right == parent
			{
				Node* uncle = grandfather->_left;

				if (uncle && uncle->_col == RED)
				{
					//变色
					grandfather->_col = RED;
					parent->_col = uncle->_col = BLACK;

					//往上更新
					cur = grandfather;
					parent = cur->_parent;
				}
				else
				{
					if (parent->_right == cur)
					{
						//旋转
						RotateL(grandfather);

						//变色
						parent->_col = BLACK;
						grandfather->_col = cur->_col = RED;
					}
					else
					{
						RotateR(parent);
						RotateL(grandfather);

						cur->_col = BLACK;
						grandfather->_col = parent->_col = RED;
					}
					break;
				}
			}
		}
		//根节点可能为红色,不管红色还是黑色都弄成黑色
		_root->_col = BLACK;
		return true;
	}
}

2.验证

  • 原理
  1. 根节点不能为红
  2. 每条路径的黑色结点数相同
  3. 每条路径不能出现连续的红色结点。
  • 代码
bool _IsRBTree(Node* root)
{
	if (root == nullptr)
		return true;
	//根节点是黑色的
	if (root->_col == RED)
		return false;
	//各个路径的黑色结点数是相同的,因此设立一个基准进行比较合适,
	//再对树进行遍历求每个路径的黑色结点的数量,最后比较即可。
	int benchmark = 0;
	Node* cur = root;
	while (cur)
	{
		if (cur->_col == BLACK)
			benchmark++;
		cur = cur->_left;
	}
	return Check(root);
}
bool Check(Node* root, int BCount,int benchmark)
{
	if (root == nullptr)
	{
		//验证基准值是否等于黑色结点数
		//只要有一个不是,即不是红黑树。
		if (BCount != benchmark)
			return false;

		return true;
	}

	//求每条黑色结点的个数
	if (root->_col == BLACK)
		BCount++;

	//验证性质3,即不能有连续的红色结点。
	if (root->_col == RED && root->_parent 
	&& root->_parent->_col == RED)
	{
		return false;
	}
	return Check(root->_left,BCount,benchmark) 
		&& Check(root->_right, BCount, benchmark);
}

源码

#pragma once
#include<iostream>
using namespace std;
namespace MY_STL
{
	enum Color
	{
		RED = 0,
		BLACK = 1
	};
	template<class Key,class Val>
	struct RBTreeNode
	{
		typedef RBTreeNode<Key, Val> Node;
		RBTreeNode(const pair<Key,Val>& key)
			:_key(key.first)
			,_val(key.second)
			,_right(nullptr)
			,_left(nullptr)
			,_parent(nullptr)
			,_col(RED)
		{}

		Node* _right;
		Node* _left;
		Node* _parent;

		Key _key;
		Val _val;
		Color _col;
	};

	template<class Key,class Val>
	class RBTree
	{
		typedef RBTreeNode<Key, Val> Node;

	public:
		bool insert(const pair<Key, Val>& val)
		{
			//第一步:插入操作
			//如果根节点为空
			if (_root == nullptr)
			{
				_root = new Node(val);
				_root->_col = BLACK;
				return true;
			}
			else
			{
				Node* cur = _root, * parent = _root;
				while (cur)
				{
					if (cur->_key > val.first)
					{
						parent = cur;
						cur = cur->_left;

					}
					else if (cur->_key < val.first)
					{
						parent = cur;
						cur = cur->_right;

					}
					else
					{
						return false;
					}
				}
				cur = new Node(val);
				if (parent->_key > val.first)
				{
					parent->_left = cur;
				}
				else
				{
					parent->_right = cur;
				}
				//更新新增结点的_parent
				cur->_parent = parent;

				//判断是否要进行旋转变色
				//当父节点为红色说明要进行判断
				while (parent && parent->_col == RED)
				{
					//爷爷结点
					Node* grandfather = parent->_parent;
					if (grandfather->_left == parent)
					{
						Node* uncle = grandfather->_right;
						if (uncle && uncle->_col == RED)
						//如果uncle存在且为红色
						{
							//变色
							parent->_col = uncle->_col = BLACK;
							grandfather->_col = RED;

							//继续往上迭代进行分析
							cur = grandfather;
							parent = cur->_parent;
						}
						else//如果uncle不存在或者为黑色
						{

							//旋转
							if (parent->_left == cur)
							{
								RotateR(grandfather);
								RotateCount++;

								parent->_col = BLACK;
								cur->_col = grandfather->_col = RED;
							}
							else
							{
								RotateL(parent);
								RotateR(grandfather);
								RotateCount+=2;

								cur->_col = BLACK;

								parent->_col = grandfather->_col
								 = RED;
							}

							break;
						}
					}
					else//grandfather->_right == parent
					{
						Node* uncle = grandfather->_left;

						if (uncle && uncle->_col == RED)
						{
							//变色
							grandfather->_col = RED;
							parent->_col = uncle->_col = BLACK;

							//往上更新
							cur = grandfather;
							parent = cur->_parent;
						}
						else
						{
							if (parent->_right == cur)
							{
								//旋转
								RotateL(grandfather);
								RotateCount++;

								//变色
								parent->_col = BLACK;
								grandfather->_col = cur->_col = RED;
							}
							else
							{
								RotateR(parent);
								RotateL(grandfather);
								RotateCount += 2;

								cur->_col = BLACK;
								grandfather->_col = parent->_col 
								= RED;
							}
							break;
						}
					}
				}
				//根节点可能为红色,不管红色还是黑色都弄成黑色
				_root->_col = BLACK;
				return true;
			}
		}
		bool IsRBTree()
		{
			return _IsRBTree(_root);
		}
		size_t Height()
		{
			return Height(_root);
		}
	private:
		size_t Height(Node* root)
		{
			if (root == nullptr)
			{
				return 0;
			}

			int LHeight = Height(root->_left);
			int RHeight = Height(root->_right);

			return max(LHeight, RHeight) + 1;
		}
		bool _IsRBTree(Node* root)
		{
			if (root == nullptr)
				return true;

			//根节点是黑色的
			if (root->_col == RED)
				return false;
			//各个路径的黑色结点数是相同的,因此设立一个基准进行比较
			int benchmark = 0;
			Node* cur = root;
			while (cur)
			{
				if (cur->_col == BLACK)
					benchmark++;

				cur = cur->_left;
			}
			return Check(root,0,benchmark);
		}
		bool Check(Node* root, int BCount,int benchmark)
		{
			if (root == nullptr)
			{
				//验证基准值是否等于黑色结点数
				//只要有一个不是,即不是红黑树。
				if (BCount != benchmark)
					return false;

				return true;
			}

			//求每条黑色结点的个数
			if (root->_col == BLACK)
				BCount++;

			//验证性质3,即不能有连续的红色结点。
			if (root->_col == RED && root->_parent 
			&& root->_parent->_col == RED)
			{
				return false;
			}
			return Check(root->_left,BCount,benchmark) &
			& Check(root->_right, BCount, benchmark);
		}

		void RotateL(Node* parent)
		{
			//画图分析:
			//操作的结点有cur,cur_left,ppnode
			Node* cur = parent->_right;
			Node* cur_left = cur->_left;
			//将parent的右节点改为cur_left
			parent->_right = cur_left;
			//改变cur_left父节点的转向
			//cur_left可能为空
			if (cur_left != nullptr)
			{
				cur_left->_parent = parent;
			}
			//将parent链接在cur的左边
			//为了更新cur的parent需要保存parent的父节点
			Node* ppnode = parent->_parent;

			cur->_left = parent;
			parent->_parent = cur;

			//ppnode可能为空
			if (ppnode == nullptr)
			{
				//需要修改根节点
				_root = cur;
				cur->_parent = nullptr;
			}
			else
			{
				//改变ppnode的指向
				if (ppnode->_left == parent)
				{
					ppnode->_left = cur;
				}
				else
				{
					ppnode->_right = cur;
				}
				cur->_parent = ppnode;

			}

		}
		void RotateR(Node* parent)
		{
			//操作的结点
			Node* cur = parent->_left;
			Node* cur_right = cur->_right;

			//第一步:将cur_right链接到parent的left
			parent->_left = cur_right;
			//更改cur_right的父节点
			//注意:cur_right可能为空
			if (cur_right != nullptr)
			{
				cur_right->_parent = parent;
			}
			//第二步:将parent链接到cur的右结点。
			//先保存一下parent的父节点
			Node* ppnode = parent->_parent;

			cur->_right = parent;
			parent->_parent = cur;
			//ppnode为空说明需要修改根节点
			if (ppnode == nullptr)
			{
				_root = cur;
				cur->_parent = nullptr;
			}
			else
			{
				if (ppnode->_left == parent)
				{
					ppnode->_left = cur;
				}
				else
				{
					ppnode->_right = cur;
				}

				cur->_parent = ppnode;
			}
		}
		Node* _root = nullptr;
		public:
			size_t RotateCount = 0;
	};
};

总结

 红黑树的理解较AVL树抽象,需要画图分析,不过有了AVL树旋转的基础,这里的难度要下降不少。还是与之前一样,只要图画的好,代码跑不了,所以这里的关键就在于画图。
 总之,今天的分享到这里就结束了,如果感觉有所帮助,不妨点个赞鼓励一下吧!

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

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

相关文章

基于SSM的学生公寓管理中心系统设计与实现

末尾获取源码 开发语言&#xff1a;Java Java开发工具&#xff1a;JDK1.8 后端框架&#xff1a;SSM 前端&#xff1a;采用JSP技术开发 数据库&#xff1a;MySQL5.7和Navicat管理工具结合 服务器&#xff1a;Tomcat8.5 开发软件&#xff1a;IDEA / Eclipse 是否Maven项目&#x…

Geotif.js读取tif元信息相关问题记录

起因是使用OL加载COG时&#xff0c;出现了不指定sources的max就一片黑的情况&#xff0c;所以需要读取tif真实波段值范围而不是靠比例设置颜色了。 使用geotiff.js可以读取tif的元信息&#xff0c;但当tif没有GDAL_METADATA这个key时就读不出来 然后找到了这个 乍一看简直完美…

普中 51 单片机点亮LED灯

普中 51 单片机 &#xff08;STC89C52RC&#xff09; LED / IO 将LED1进行闪烁操作 为啥要进行延时操作&#xff1f;依据人的肉眼余晖效应&#xff0c; 延时时间不能太短&#xff0c;否则就无法观察到 LED 闪烁 #include "reg52.h" typedef unsigned int u16; //对…

【Linux】Base64编码

Mz1 对这3个字符进行一个Base64编码理解&#xff0c;把他化为2进制数据&#xff0c;在以6个位为单位分割&#xff0c;然后用这个16进制化为10进制&#xff0c;查表得出阿斯卡码对应的字符&#xff0c;那么这个字符就是base64编码&#xff0c;因为64个字符有64阿斯卡码。 相关截…

SpringBoot学习笔记(项目创建,yaml,多环境开发,整合mybatis SMM)

一、SpringBoot入门 1.1 SpringBoot概述 SpringBoot是由Pivotal团队提供的全新框架&#xff0c;其设计目的是用来简化Spring应用的初始搭建以及开发过程。 Spring程序缺点&#xff1a;配置繁琐&#xff0c;依赖设置繁琐。SpringBoot程序优点&#xff1a;自动装配&#xff0c…

列表对象复制属性到另一个列表对象 从List<Object>另一个List<Object>

目录 事件起因环境和工具解决办法结束语 事件起因 在写一个市级的项目时&#xff0c;遇到了一个问题&#xff0c;这个项目涉及的数据内容非常大&#xff0c;光是数据库文件的大小就已经达到了12G&#xff0c;数据的规模大致是在百万级的&#xff0c;光是我这次参与处理的数据就…

led灯白光和暖光哪个对眼睛好?最适合孩子开学使用的护眼台灯

什么样的光更适合阅读呢&#xff1f;从生物学的角度上讲是早上的自然光。一方面是因为早晨的光照使得人体内在的生物钟和外界24h亮暗循环同步。如果生理节律被打乱&#xff0c;早晨明亮的光照可以帮助恢复正常的生理节律。另一方面是其物理特性&#xff0c;自然光漫射效果较好&…

基于springboot+vue的加盟店管理系统(前后端分离)

博主主页&#xff1a;猫头鹰源码 博主简介&#xff1a;Java领域优质创作者、CSDN博客专家、公司架构师、全网粉丝5万、专注Java技术领域和毕业设计项目实战 主要内容&#xff1a;毕业设计(Javaweb项目|小程序等)、简历模板、学习资料、面试题库、技术咨询 文末联系获取 项目介绍…

零碎改动(ES6+)

这里写自定义目录标题 let 和 const幂运算字符串新增API let 和 const ES6建议不再使用var定义变量&#xff0c;而使用let定义变量&#xff0c;const定义常量 let a 1; // 使用 let 定义变量 a 2; // 变量的值是可修改的const b 1; // 使用 const 定义常量 b 2; // ❌ 报…

4. HBase必知必会之理论基础篇

HBase必知必会理论基础篇 1.1 HBase简介1.2 HBase 数据模型1.3 HBase整体架构1.4 HBase 读写流程1.4.1 客户端读取流程1.4.2 客户端写入流程 1.5 HBase 客户端常用的方法1.5.1 scan查询1.5.2 get查询1.5.3 put查询1.5.4 delete 查询1.5.5 append 查询1.5.6 increment查询 1.6 H…

SB树,看这一篇就够了

算法拾遗三十九SB树及跳表 SB树SB树四种违规类型总结 SB树Code 跳表 SB树 SB树是基于搜索二叉树来的&#xff0c;也有左旋和右旋的操作&#xff0c;只是不同于AVL树&#xff0c;它也有它自己的一套平衡性方法。 任何以叔叔节点为头的子树的节点个数不小于自己任何一个侄子树的…

【PAT】攀拓(PAT)- 程序设计(甲级)2023年夏季考试自测

个人学习记录&#xff0c;代码难免不尽人意。 今天又斥资巨款买了PAT甲级2023年夏季的考试真题做了做&#xff0c;得分 95&#xff0c;还买了时光机&#xff0c;在当时排名42名。总的来说还是比较满意的&#xff01;有些地方当时做的时候卡住了但是后面回过头来重新想的时候还是…

小程序环境搭建

第一种&#xff1a;微信开发者工具 1. 微信公众平台注册小程序 注册类型选择‘个人’即可&#xff0c;‘企业’需要公司相关信息&#xff08;企业信用代码、法人信息等&#xff09; 注册成功后&#xff0c;在‘开发’-‘开发管理’-‘开发设置’中找到AppID 并纪录。 2. …

关于前端就业前景的一点看法

一、前言 最近&#xff0c;很多人在知乎上鼓吹前端未来会没掉的概念。在此我想说这个说法并不真实&#xff0c;而是一种极端的观点。 事实上&#xff0c;前端开发在当今的互联网行业中扮演着至关重要的角色&#xff0c;它是构建 Web 应用程序所必需的一部分&#xff0c;能够实现…

高速电路设计-----第二章

本章主要讲解的是电阻、电容、电感的选型。 一、电阻&#xff1a;关键还是限流。 1、通常在电源滤波时除了LC外&#xff0c;还会串接一个R。目的是为了降低信号的Q值&#xff0c;防止信号失真。常用于失真电源滤波。 2、选型的电阻的封装太小&#xff0c;电路的电流超过电阻能…

让你不再惧怕内存优化

原文链接 让你不再惧怕内存优化 之前曾经写过一篇关于如何做性能优化的文章&#xff0c;现在针对内存这一专项再做精细化的讨论。对于安卓应用开发来说&#xff0c;内存究竟会遇到什么样的问题&#xff0c;有什么方法可以用来测试和分析&#xff0c;以及有什么样的策略可以去实…

【LeetCode-中等题】904. 水果成篮

文章目录 题目方法一&#xff1a;滑动窗口方法二&#xff1a; 题目 题目的意思就是&#xff1a;找至多包含两种元素的最长子串&#xff0c;返回其长度 方法一&#xff1a;滑动窗口 class Solution { // 滑动窗口 找至多包含两种元素的最长子串&#xff0c;返回其长度public …

红队打靶:ConnectTheDots打靶思路详解(vulnhub)

目录 写在开头 第一步&#xff1a;主机发现和端口扫描 第二步&#xff1a;FTP和NFS渗透&#xff08;失败&#xff09; 第三步&#xff1a;web渗透 第四步&#xff1a;jsfuck解码 第五步&#xff1a;再次FTP渗透与莫尔斯电码解码 第六步&#xff1a;vim读取断电swp文件…

数据科学家必备的20个Python库

公众号&#xff1a;尤而小屋作者&#xff1a;Peter编辑&#xff1a;Peter 大家好&#xff0c;我是Peter~ 小屋里面一直在输出关于数据科学领域的文章&#xff0c;绝大部分都是基于Python&#xff0c;少量的MySQL&#xff08;MySQL存储数据用&#xff09;。本文重点给大家介绍P…

保姆级教程 --redis启动命令

1、在redis目录 打开命令 windowr 输入cmd 2、输入 redis-server.exe redis.windows.conf 启动redis命令&#xff0c;看是否成功 3、可能会启动失败&#xff0c;报28 Nov 09:30:50.919 # Creating Server TCP listening socket 127.0.0.1:6379: bind: No error 4、报错后&am…