C++ 红黑树

目录

一、红黑树的概念和性质

二、实现红黑树

1、节点定义+构造

2、插入

3、左单旋&右单旋

4、中序遍历

5、检查平衡

6、获取树的高度

7、查找

8、析构

测试

完整版


一、红黑树的概念和性质

红黑树,是一种二叉搜索树,但在每个结点上增加一个存储位表示结点的颜色,可以是Red或
Black。 通过对任何一条从根到叶子的路径上各个结点着色方式的限制,红黑树确保没有一条路
径会比其他路径长出俩倍,因而是接近平衡的。
红黑树的性质
  1. 每个结点不是红色就是黑色。
  2. 根节点是黑色的。
  3. 如果一个节点是红色的,则它的两个孩子结点是黑色的。
  4. 对于每个结点,从该结点到其所有后代叶结点的简单路径上,均包含相同数目的黑色结点 
  5. 每个叶子结点都是黑色的。(此处的叶子结点指的是空结点又称NIL节点)
  6. 满足上面的性质,红黑树就能保证:其最长路径中节点个数不会超过最短路径节点个数的两倍。
    1. 假设全部的黑色节点有N个
    2. 最短路径长度: logN
    3. 整棵树的节点数量: [N,2N]之间
    4. 最长路径长度: 2logN
    5. 假设10亿个节点,AVL: 最多查找30次左右,RB:最多查找60次左右

二、实现红黑树

1、节点定义+构造

enum Colour
{
	RED, BLACK,
};

template<class K,class V>
struct RBTreeNode
{
	RBTreeNode<K, V>* _left;
	RBTreeNode<K, V>* _right;
	RBTreeNode<K, V>* _parent;
	pair<K, V> _kv;
	Colour _col;
	RBTreeNode(const pair<K,V>& kv)
		:_left(nullptr)
		,_right(nullptr)
		,_parent(nullptr)
		,_kv(kv)
		,_col(RED)
	{}
};

首先定义了一个枚举类型Colour,其中包含两个枚举值REDBLACK,用于表示红黑树节点的颜色。

接下来定义了一个模板结构体RBTreeNode,表示红黑树的节点。该结构体包含以下成员变量:

  • _left:指向左子节点的指针。
  • _right:指向右子节点的指针。
  • _parent:指向父节点的指针。
  • _kv:键值对,使用pair<K, V>类型存储键和值。
  • _col:节点的颜色,使用Colour枚举类型表示,默认为红色(RED)。

结构体还定义了一个构造函数RBTreeNode,接受一个键值对作为参数,并初始化成员变量。在构造函数中,左子节点、右子节点和父节点的指针都被初始化为nullptr,颜色被初始化为红色。

2、插入

template<class K,class V>
class RBTree
{
	typedef RBTreeNode<K, V> Node;
public:
	bool Insert(const pair<K, V>& kv)
	{
		if (_root == nullptr){
			_root = new Node(kv);
			_root->_col = BLACK;
			return true;
		}

		Node* parent = nullptr;
		Node* cur = _root;
		while (cur){
			if (cur->_kv.first < kv.first){
				parent = cur;
				cur = cur->_right;
			}
			else if (cur->_kv.first > kv.first){
				parent = cur;
				cur = cur->_left;
			}
			else{
				return false;
			}
		}

		cur = new Node(kv);
		if (parent->_kv.first > kv.first){
			parent->_left = cur;
		}
		else{
			parent->_right = cur;
		}
		cur->_parent = parent;

		while (parent && parent->_col == RED){
			Node* grandfather = parent->_parent;
			if (grandfather->_left == parent){
				Node* uncle = grandfather->_right;
				// 情况1:u存在且为红,变色处理,并继续往上处理
				if (uncle && uncle->_col == RED){
					parent->_col = BLACK;
					uncle->_col = BLACK;
					grandfather->_col = RED;

					// 继续往上调整
					cur = grandfather;
					parent = cur->_parent;
				}
				else // 情况2+3:u不存在/u存在且为黑,旋转+变色
				{
					//     g
					//   p   u
					// c 
					if (cur == parent->_left){
						RotateR(grandfather);
						parent->_col = BLACK;
						grandfather->_col = RED;
					}
					else{
						//     g
						//   p   u
						//     c
						RotateL(parent);
						RotateR(grandfather);
						cur->_col = BLACK;
						//parent->_col = RED;
						grandfather->_col = RED;
					}

					break;
				}
			}
			else // (grandfather->_right == parent)
			{
				//    g
				//  u   p
				//        c
				Node* uncle = grandfather->_left;
				// 情况1:u存在且为红,变色处理,并继续往上处理
				if (uncle && uncle->_col == RED){
					parent->_col = BLACK;
					uncle->_col = BLACK;
					grandfather->_col = RED;

					// 继续往上调整
					cur = grandfather;
					parent = cur->_parent;
				}
				else // 情况2+3:u不存在/u存在且为黑,旋转+变色
				{
					//    g
					//  u   p
					//        c
					if (cur == parent->_right){
						RotateL(grandfather);
						grandfather->_col = RED;
						parent->_col = BLACK;
					}
					else{
						//    g
						//  u   p
						//    c
						RotateR(parent);
						RotateL(grandfather);
						cur->_col = BLACK;
						grandfather->_col = RED;
					}
					break;
				}
			}
		}
		_root->_col = BLACK;
		return true;
	}
private:
	Node* _root = nullptr;
};

首先,定义了一个名为Insert的函数,它接受一个键值对kv作为参数。这个函数的目的是将这个键值对插入到红黑树中。

  1. 如果根节点为空(也就是说,这是一棵空树),那么我们就创建一个新的节点作为根节点,并将其颜色设置为黑色。然后返回true表示插入成功。

  2. 如果根节点不为空,我们就需要找到新节点应该插入的位置。我们创建两个指针parentcur,并将它们初始化为根节点。然后我们进入一个循环,不断地将cur向左或向右移动,直到找到新节点应该插入的位置。在这个过程中,我们始终保持parentcur的父节点。

  3. 当我们找到新节点应该插入的位置后,我们创建一个新的节点,并将其插入到正确的位置。我们还需要设置新节点的父节点。

  4. 接下来,我们需要修复可能被新节点破坏的红黑树的性质。我们进入一个循环,只要新节点的父节点存在且颜色为红色,我们就继续循环。在循环中,我们首先找到新节点的祖父节点和叔叔节点。

  5. 如果新节点的叔叔节点存在且颜色为红色(情况1),我们就将新节点的父节点和叔叔节点的颜色都改为黑色,然后将祖父节点的颜色改为红色。然后我们将祖父节点视为新插入的节点,继续循环。

  6. 如果新节点的叔叔节点不存在或颜色为黑色(情况2&3),我们就需要进行旋转和颜色调整。具体的操作取决于新节点、其父节点和祖父节点的相对位置。这里有四种可能的情况,分别对应于代码中的四个子情况。

  7. 最后,我们需要确保根节点的颜色始终为黑色。这是因为在上面的调整过程中,根节点的颜色可能被改变。

  8. 函数最后返回true,表示插入操作成功。

3、左单旋&右单旋

private:
	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 (ppnode == nullptr) {
			_root = subR;
			_root->_parent = nullptr;
		}
		else {
			if (ppnode->_left == parent) {
				ppnode->_left = subR;
			}
			else {
				ppnode->_right = subR;
			}
			subR->_parent = ppnode;
		}
	}

  1. 首先,我们定义了三个指针变量:subR 指向父节点的右子节点,subRL 指向 subR 的左子节点,ppnode 指向父节点的父节点。

  2. 我们将父节点的右子节点指向 subRL,这样父节点和 subR 就断开了连接。如果 subRL 不为空,我们还需要将其父节点指针指向父节点。

  3. 接下来,我们将父节点的父节点指针指向 subR,将 subR 的左子节点指向父节点。

  4. 如果父节点的父节点指针 ppnode 为空,表示父节点是根节点,我们将根节点指针 _root 指向 subR,并将根节点的父节点指针置为空。

  5. 如果父节点的父节点指针 ppnode 不为空,我们需要根据父节点在其父节点的位置来更新指、针。如果父节点是其父节点的左子节点,我们将 subR 设置为其父节点的左子节点;否则,将 subR 设置为其父节点的右子节点。同时,我们还需要更新 subR 的父节点指针为 ppnode

private:
	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 (parent == _root) {
			_root = subL;
			_root->_parent = nullptr;
		}
		else {
			if (ppnode->_left == parent) {
				ppnode->_left = subL;
			}
			else {
				ppnode->_right = subL;
			}
			subL->_parent = ppnode;
		}
	}

  1. 首先,我们定义了三个指针变量:subL 指向父节点的左子节点,subLR 指向 subL 的右子节点,ppnode 指向父节点的父节点。

  2. 我们将父节点的左子节点指向 subLR,这样父节点和 subL 就断开了连接。如果 subLR 不为空,我们还需要将其父节点指针指向父节点。

  3. 接下来,我们将父节点的父节点指针指向 subL,将 subL 的右子节点指向父节点,以完成右旋转操作。

  4. 如果父节点是根节点 _root,我们将根节点指针 _root 指向 subL,并将根节点的父节点指针置为空。

  5. 如果父节点的父节点指针 ppnode 不为空,我们需要根据父节点在其父节点的位置来更新指针。如果父节点是其父节点的左子节点,我们将 subL 设置为其父节点的左子节点;否则,将 subL 设置为其父节点的右子节点。同时,我们还需要更新 subL 的父节点指针为 ppnode

4、中序遍历

public:
	void InOrder()
	{
		_InOrder(_root);
	}
private:
	void _InOrder(Node* root)
	{
		if (root == nullptr)
		{
			return;
		}

		_InOrder(root->_left);
		cout << root->_kv.first << " ";
		_InOrder(root->_right);
	}

5、检查平衡

private:
	bool _Check(Node* root,int blackNum,int benchmark)
	{
		if (root == nullptr) {
			if (benchmark != blackNum) {
				cout<< "某条路径黑色节点的数量不相等" << endl;
				return false;
			}
			return true;
		}
		if (root->_col == BLACK)
			blackNum++;

		if (root->_col == RED && root->_parent && root->_parent->_col == RED) {
			cout << "存在连续的红色节点" << endl;
			return false;
		}

		return _Check(root->_left, blackNum, benchmark)
			&& _Check(root->_right, blackNum, benchmark);
	}

_Check函数是一个递归函数,用于检查从给定节点到其所有叶子节点的所有路径是否都包含相同数量的黑色节点,并且没有连续的红色节点。

  1. 如果当前节点为空,那么我们就检查到达这个叶子节点的路径上的黑色节点数量是否等于基准值。如果不等于,那么就返回false。否则,返回true

  2. 如果当前节点是黑色的,那么我们就将黑色节点的数量加一。

  3. 如果当前节点是红色的,并且它的父节点也是红色的,那么就返回false,因为这违反了红黑树的性质。

  4. 最后,我们递归地检查当前节点的左子节点和右子节点。

public:
	bool IsBalance()
	{
		if (_root && _root->_col == RED) {
			cout << "根节点的颜色是红色" << endl;
			return false;
		}
		int benchmark = 0;
		Node* cur = _root;
		while (cur) {
			if (cur->_col == BLACK)
				benchmark++;
			cur = cur->_left;
		}
		return _Check(_root, 0, benchmark);
	}
  • IsBalance函数首先检查根节点是否是黑色的。如果根节点是红色的,那么就返回false
  • 然后,我们计算从根节点到左子树的最左叶子节点的路径上的黑色节点数量,作为基准值。
  • 最后,我们调用_Check函数,检查整棵树是否满足红黑树的性质。如果满足,那么就返回true。否则,返回false

6、获取树的高度

public:
	int Height()
    {
		return _Height(_root);
	}
private:
	int _Height(Node* root)
    {
		if (root == nullptr)
			return 0;
		int leftH = _Height(root->_left);
		int rightH = _Height(root->_right);
		return leftH > rightH ? leftH + 1 : rightH + 1;
	}
  • Height 函数:这是一个获取树的高度的函数,用于计算树的高度。它调用了私有函数 _Height 来进行递归计算。

  • _Height 函数:这是一个私有的获取节点高度的函数,用于递归地计算节点的高度。如果当前节点为空,返回 0;否则,计算左子树和右子树的高度,返回较大的高度加 1。

7、查找

Node* Find(const K& key)
{
	Node* cur = _root;
	while (cur){
		if (cur->_kv.first < key){
			cur = cur->_right;
		}
		else if (cur->_kv.first > key){
			cur = cur->_left;
		}
		else{
			return cur;
		}
	}

	return nullptr;
}

8、析构

public:
	~RBTree()
	{
		_Destroy(_root);
		_root = nullptr;
	}
private:
	void _Destroy(Node* root)
	{
		if (root == nullptr)
		{
			return;
		}

		_Destroy(root->_left);
		_Destroy(root->_right);
		delete root;
	}

测试

void Test_RBTree1()
{
	//int a[] = { 16, 3, 7, 11, 9, 26, 18, 14, 15 };
	int a[] = { 4, 2, 6, 1, 3, 5, 15, 7, 16, 14,  };
	RBTree<int, int> t1;
	for (auto e : a)
	{
		t1.Insert(make_pair(e, e));
		cout << e << "插入:" << t1.IsBalance() << endl;
	}

	t1.InOrder();
	cout << t1.IsBalance() << endl;
}

void Test_RBTree2()
{
	srand(time(0));
	const size_t N = 5000000;
	RBTree<int, int> t;
	for (size_t i = 0; i < N; ++i)
	{
		size_t x = rand() + i;
		t.Insert(make_pair(x, x));
		//cout << t.IsBalance() << endl;
	}

	cout << t.IsBalance() << endl;
	cout << t.Height() << endl;
}

完整版

#pragma once

enum Colour
{
	RED, BLACK,
};

template<class K,class V>
struct RBTreeNode
{
	RBTreeNode<K, V>* _left;
	RBTreeNode<K, V>* _right;
	RBTreeNode<K, V>* _parent;
	pair<K, V> _kv;
	Colour _col;
	RBTreeNode(const pair<K,V>& kv)
		:_left(nullptr)
		,_right(nullptr)
		,_parent(nullptr)
		,_kv(kv)
		,_col(RED)
	{}
};

template<class K,class V>
class RBTree
{
	typedef RBTreeNode<K, V> Node;
public:
	~RBTree()
	{
		_Destroy(_root);
		_root = nullptr;
	}

	Node* Find(const K& key)
	{
		Node* cur = _root;
		while (cur) {
			if (cur->_kv.first < key) {
				cur = cur->_right;
			}
			else if (cur->_kv.first > key) {
				cur = cur->_left;
			}
			else {
				return cur;
			}
		}
		return nullptr;
	}

	bool Insert(const pair<K, V>& kv)
	{
		if (_root == nullptr){
			_root = new Node(kv);
			_root->_col = BLACK;
			return true;
		}

		Node* parent = nullptr;
		Node* cur = _root;
		while (cur){
			if (cur->_kv.first < kv.first){
				parent = cur;
				cur = cur->_right;
			}
			else if (cur->_kv.first > kv.first){
				parent = cur;
				cur = cur->_left;
			}
			else{
				return false;
			}
		}

		cur = new Node(kv);
		if (parent->_kv.first > kv.first){
			parent->_left = cur;
		}
		else{
			parent->_right = cur;
		}
		cur->_parent = parent;

		while (parent && parent->_col == RED){
			Node* grandfather = parent->_parent;
			if (grandfather->_left == parent){
				Node* uncle = grandfather->_right;
				// 情况1:u存在且为红,变色处理,并继续往上处理
				if (uncle && uncle->_col == RED){
					parent->_col = BLACK;
					uncle->_col = BLACK;
					grandfather->_col = RED;

					// 继续往上调整
					cur = grandfather;
					parent = cur->_parent;
				}
				else // 情况2+3:u不存在/u存在且为黑,旋转+变色
				{
					//     g
					//   p   u
					// c 
					if (cur == parent->_left){
						RotateR(grandfather);
						parent->_col = BLACK;
						grandfather->_col = RED;
					}
					else{
						//     g
						//   p   u
						//     c
						RotateL(parent);
						RotateR(grandfather);
						cur->_col = BLACK;
						//parent->_col = RED;
						grandfather->_col = RED;
					}

					break;
				}
			}
			else // (grandfather->_right == parent)
			{
				//    g
				//  u   p
				//        c
				Node* uncle = grandfather->_left;
				// 情况1:u存在且为红,变色处理,并继续往上处理
				if (uncle && uncle->_col == RED){
					parent->_col = BLACK;
					uncle->_col = BLACK;
					grandfather->_col = RED;

					// 继续往上调整
					cur = grandfather;
					parent = cur->_parent;
				}
				else // 情况2+3:u不存在/u存在且为黑,旋转+变色
				{
					//    g
					//  u   p
					//        c
					if (cur == parent->_right){
						RotateL(grandfather);
						grandfather->_col = RED;
						parent->_col = BLACK;
					}
					else{
						//    g
						//  u   p
						//    c
						RotateR(parent);
						RotateL(grandfather);
						cur->_col = BLACK;
						grandfather->_col = RED;
					}
					break;
				}
			}
		}
		_root->_col = BLACK;
		return true;
	}

	void InOrder()
	{
		_InOrder(_root);
	}

	//根据规则检查
	bool IsBalance()
	{
		if (_root && _root->_col == RED) {
			cout << "根节点的颜色是红色" << endl;
			return false;
		}
		int benchmark = 0;
		Node* cur = _root;
		while (cur) {
			if (cur->_col == BLACK)
				benchmark++;
			cur = cur->_left;
		}
		return _Check(_root, 0, benchmark);
	}

	int Height()
	{
		return _Height(_root);
	}

private:
	void _Destroy(Node* root)
	{
		if (root == nullptr)
		{
			return;
		}

		_Destroy(root->_left);
		_Destroy(root->_right);
		delete root;
	}

	int _Height(Node* root)
	{
		if (root == NULL)
			return 0;

		int leftH = _Height(root->_left);
		int rightH = _Height(root->_right);

		return leftH > rightH ? leftH + 1 : rightH + 1;
	}

	bool _Check(Node* root,int blackNum,int benchmark)
	{
		if (root == nullptr) {
			if (benchmark != blackNum) {
				cout<< "某条路径黑色节点的数量不相等" << endl;
				return false;
			}
			return true;
		}
		if (root->_col == BLACK)
			blackNum++;

		if (root->_col == RED && root->_parent && root->_parent->_col == RED) {
			cout << "存在连续的红色节点" << endl;
			return false;
		}

		return _Check(root->_left, blackNum, benchmark)
			&& _Check(root->_right, blackNum, benchmark);
	}

	void _InOrder(Node* root)
	{
		if (root == nullptr)
		{
			return;
		}

		_InOrder(root->_left);
		cout << root->_kv.first << " ";
		_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 (ppnode == nullptr) {
			_root = subR;
			_root->_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 (parent == _root) {
			_root = subL;
			_root->_parent = nullptr;
		}
		else {
			if (ppnode->_left == parent) {
				ppnode->_left = subL;
			}
			else {
				ppnode->_right = subL;
			}
			subL->_parent = ppnode;
		}
	}
	Node* _root = nullptr;
};

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

数据恢复与硬盘修理

第1篇 数据恢复与硬盘修理基础 本篇包括第1章&#xff0c;主要介绍数据恢复的发展现状、硬盘维修的基本知识以及一些基本工具的使用。 第1章 基础知识 1.1 数据恢复技术的发展和研究现状 目前国内的数据恢复服务市场处于一个极不规范的状态。虽然大部分数据恢复服务提供商是…

Linux CentOs7 安装Mysql(5.7和8.0版本)密码修改 超详细教程

CSDN 成就一亿技术人&#xff01; 今天出一期Centos下安装Mysql&#xff08;详细教程&#xff09;包括数据库密码跳过修改 CSDN 成就一亿技术人&#xff01; 目录 1.获取安装包 2.安装程序 安装下载的rpm包 查看安装包 修改5.7版本&#xff08;重要&#xff09; 安装M…

如何生成漂亮的静态文档说明页

分享&#xff1a;如何生成漂亮的静态文档说明页 最近经常被问 https://t.itmuch.com/doc.html 文档页是怎么制作的&#xff0c;考虑到步骤略复杂&#xff0c;写篇手记总结下吧。 TIPS https://t.itmuch.com/doc.html 是个人在慕课网视频《 面向未来微服务:Spring Cloud Alibab…

二叉树堆的应用实例分析:堆排序 | TOP-K问题

&#x1f4f7; 江池俊&#xff1a; 个人主页 &#x1f525;个人专栏&#xff1a; ✅数据结构冒险记 ✅C语言进阶之路 &#x1f305; 有航道的人&#xff0c;再渺小也不会迷途。 文章目录 前言一、堆排序1.1 排序思想1.2 堆排序过程&#xff08;图解&#xff09;1.3 堆排序代…

C++ 数论相关题目(欧拉函数、筛法求欧拉函数)

1、欧拉函数 给定 n 个正整数 ai &#xff0c;请你求出每个数的欧拉函数。 欧拉函数的定义 1∼N 中与 N 互质的数的个数被称为欧拉函数&#xff0c;记为 ϕ(N) 。 若在算数基本定理中&#xff0c;Npa11pa22…pamm &#xff0c;则&#xff1a; ϕ(N) Np1−1p1p2−1p2…pm−1p…

【数学建模】插值与拟合

文章目录 插值插值方法用Python解决插值问题 拟合最小二乘拟合数据拟合的Python实现 适用情况 处理由试验、测量得到的大量数据或一些过于复杂而不便于计算的函数表达式时&#xff0c;构造一个简单函数作为要考察数据或复杂函数的近似 定义 给定一组数据&#xff0c;需要确定满…

b+树的理解

二叉树&#xff1a; 每个节点支持两个分支的树结构&#xff0c;相比于单向链表&#xff0c;多了一个分支。 二叉查找树&#xff1a; 在二叉树的基础上增加了一个规则&#xff0c;左子树的所有节点都小于它的根节点&#xff0c;右子树的所有节点都大于他的根节点。 二叉查找树…

Flutter中实现中国省份地图

效果展示(这里只展示局部&#xff0c;完全展示违规)&#xff1a; 可以点击省份改变颜色&#xff0c;更多功能可以自行拓展。 注&#xff1a;非完整中国地图&#xff01;&#xff01;&#xff01; 本文用于记录在Flutter项目中安卓端实现中国地图&#xff0c;因为实现过程是通过…

分类预测 | Matlab实现GRU-Attention-Adaboost基于门控循环单元融合注意力机制的Adaboost数据分类预测/故障识别

分类预测 | Matlab实现GRU-Attention-Adaboost基于门控循环单元融合注意力机制的Adaboost数据分类预测/故障识别 目录 分类预测 | Matlab实现GRU-Attention-Adaboost基于门控循环单元融合注意力机制的Adaboost数据分类预测/故障识别分类效果基本描述程序设计参考资料 分类效果 …

【Linux C | 进程】Linux 进程间通信的10种方式(2)

&#x1f601;博客主页&#x1f601;&#xff1a;&#x1f680;https://blog.csdn.net/wkd_007&#x1f680; &#x1f911;博客内容&#x1f911;&#xff1a;&#x1f36d;嵌入式开发、Linux、C语言、C、数据结构、音视频&#x1f36d; &#x1f923;本文内容&#x1f923;&a…

手写一个图形验证码

文章目录 需求分析 需求 使用 JS 写一个验证码&#xff0c;并在前端进行校验 分析 新建文件 VueImageVerify.vue <template><div class"img-verify"><canvas ref"verify" :width"state.width" :height"state.height&qu…

OpenCV-Python(51):基于Haar特征分类器的面部检测

目标 学习了解Haar 特征分类器为基础的面部检测技术将面部检测扩展到眼部检测等。 基础 以Haar 特征分类器为基础的对象检测技术是一种非常有效的对象检测技术(2001 年Paul_Viola 和Michael_Jones 提出)。它是基于机器学习的,通过使用大量的正负样本图像训练得到一个cascade_…

socket以及字节序

1. socket 介绍&#xff1a; 简介&#xff1a; 所谓 socket&#xff08; 套接字&#xff09;&#xff0c;就是对网络中不同主机上的应用进程之间进行双向通信的 端点的抽象。 一个套接字就是网络上进程通信的一端&#xff0c;提供了应用层进程利用网络协议交换数据的机制。从所…

推荐一个还可以的windows ssh工具

1.下载 https://github.com/kingToolbox/WindTerm/releases 2.解压 3.使用 上传 下载都很快 比cmd窗口好用 当然和finalshell有点像

Linux编辑器vim(含vim的配置)

文章目录 前言vim的基本概念vim基本操作进入vim模式切换退出vim vim指令vim命令模式指令vim底行模式命令 简单vim配置 前言 本篇文章&#xff0c;小编将介绍Linux编辑器–>vim以及vim的配置。 vim的基本概念 正常/普通/命令模式(Normal mode) 控制屏幕光标的移动&#xf…

云贝教育 |【分享课】1月25日Oracle分享主题:Oracle 单实例DG

分享主题&#xff1a;Oracle 19c 单实例DG-1 讲师&#xff1a;刘峰 直播时间&#xff1a;1月25日周四19:30 直播平台&#xff1a;微信视频号 云贝学院

(更新)“高铁开通”地级市-多期DID工具变量(2000-2022年)

参照卞元超&#xff08;2019&#xff09;、邓慧慧&#xff08;2020&#xff09;、汪克亮&#xff08;2021&#xff09;等人做法&#xff0c;将开通高铁的城市作为处理组&#xff0c;未开通高铁的城市作为对照组。地级市开通高铁之后的DID赋值为1&#xff0c;未开通则赋值为0 一…

云计算中的出口数据是指什么?

谷歌云&#xff08;Google Cloud&#xff09;近日宣布了一项重大政策变动&#xff0c;决定免除那些选择终止使用其服务并将数据迁移到其他云服务商或本地环境的客户的出口数据费用&#xff08;数据导出费用&#xff09;。 这一举措由谷歌云平台负责人阿米特扎维里&#xff08;A…

docker 基础手册

文章目录 docker 基础手册docker 容器技术镜像与容器容器与虚拟机docker 引擎docker 架构docker 底层技术docker 二进制安装docker 镜像加速docker 相关链接docker 生态 docker 基础手册 docker 容器技术 开源的容器项目&#xff0c;使用 Go 语言开发原意“码头工人”&#x…

SpringBoot责任链与自定义注解:优雅解耦复杂业务

引言 责任链模式是一种行为设计模式&#xff0c;它允许你将请求沿着处理者链进行传递&#xff0c;直到有一个处理者处理请求。在实际应用中&#xff0c;责任链模式常用于解耦发送者和接收者&#xff0c;使得请求可以按照一定的规则被多个处理者依次处理。 首先&#xff0c;本…