C++|红黑树(分析+模拟实现插入)

目录

一、概念

二、红黑树插入的实现

2.1红黑树节点的定义 

2.2红黑树基础架构

2.3红黑树的插入

2.3.1按照二叉搜索树的规则插入新结点

2.3.2检测新插入节点,是否破坏红黑树性质来进行调整

 2.3.2.1cur为红,p为红,g为黑,u存在且为红

 2.3.2.2cur为红,p为红,g为黑,u不存在/u存在且为黑

2.3.2.2.1 解决方式①

2.3.2.2.2 解决方式②

2.4插入整体代码实现

2.5红黑树与AVL树的比较与应用

 三、红黑树插入测试


前面已经学过了AVL树,如果说发明AVL树的人是个大佬,那么发明红黑树的人简直就是个天才,让我们一起来探索其奥秘。

一、概念

红黑树,同样是一种二叉搜索树,它是基于AVL树上,在每个节点增加了一个存储位表示节点的颜色,可以是Red或Black。通过对任何一条从根到叶子的路径上各个节点着色方式的限制,红黑树确保没有一条路径比其他路径长出两倍[1,2],因而是接近平衡的,满足该规则的同时其具有以下性质:

红黑树的性质:

        1.顾名思义,每个节点不是红色就是黑色

        2.根节点是黑色

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

        4.对于每个节点,从该节点到其所有后代叶节点(指的空节点(用NIL表示))的简单路径上,均包含相同数目的黑色节点(每条路径都包含相同数量的黑色节点)

        5.每个叶子结点都是黑色的(叶子结点代表空节点,即将空节点代表为黑色节点)

思考:满足以上性质的红黑树,为什么就能够确保没有条路径比其他路径长出两倍[1,2]?

那我们根据性质来分析,有红节点那么其子节点必是黑节点,那么如何求最短路径?因为要确保每条路径黑色节点相同,那么最短路径就是这条路径从根节点开始没有红节点,全是黑色节点

同样的如何求最长路径?那就是这条路径从根节点开始是一黑一红。

虽然路径的含义是从根节点到空节点,但是空节点默认当作为黑色节点,所以在计算路径长度时,就不包括空节点了。

我们用黑色节点代表B,用红色节点代表R,对于每条路径,B相等,因为没有连续的R,要么没有R,要么有R就必有B,所以R<=B,那么R+B <= B+B = 2 * B,所以最长路径不超过最短路径的两倍。

示意图:每条路径黑色节点相同

 对于AVL树和红黑树,他们各有各自的优势,总体上没有太大的区别,但在维护平衡性方面要求更低,实现更简单,并且在实际应用中提供了更好的性能,所以选择了红黑树来作为map和set的底层,那么接下来,根据其规则一步一步分析来实现红黑树的插入,并采用KV模型。

二、红黑树插入的实现

2.1红黑树节点的定义 

 用一个枚举来存放一个节点的颜色,该节点中,包括左指针、右指针、指向父亲的指针,前面章节也学习了map和set的用法,这里了,对于节点的实现就采用kv模型,节点的值就用pair来存放。

enum Color//对于红黑节点,用枚举结构来表示
	{
		RED,
		BLACK
	};

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

		RBTreeNode<K, V>* _left;
		RBTreeNode<K, V>* _right;
		RBTreeNode<K, V>* _parent;

		pair<K, V> _kv;

		Color _color;//默认给红色才符合规则,若默认给黑色的话,则插入的每个节点都是黑色节点,
		//那么就不能保证每条路径的黑色节点相同,违反了第4条性质。而给红色,就可以根据规则调整。
	};

2.2红黑树基础架构

	template<class K, class V>
	class RBTree
	{
		typedef RBTreeNode<K, V> Node;
		typedef Node* PNode;
	public:
		RBTree()
			:_Root(nullptr)
		{}
	private:
		PNode _Root;
	};

2.3红黑树的插入

 对于红黑树的插入实现,可以分为两个大步骤:

1.按照二叉搜索树的规则插入新结点

2.检测新结点插入后,红黑树的性质是否被破坏来进行调整

2.3.1按照二叉搜索树的规则插入新结点

        bool Insert(const pair<K,V>& kv)
		{
			if (_Root == nullptr)
			{
				_Root = new Node(kv,BLACK);
				_Root->_parent = nullptr;
			}

			//寻找插入位置
			PNode cur = _Root;
			PNode parent = nullptr;
			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
					return false;
			}
			//插入
			cur = new Node(kv);
			if (kv.first < parent->_kv.first)
			{
				parent->_left = cur;
			}
			else if (kv.first > parent->_kv.first)
			{
				parent->_right = cur;
			}
			cur->_parent = parent;


            //调整
            //.......
        }

 2.3.2检测新插入节点,是否破坏红黑树性质来进行调整

因为新结点的默认颜色是红色,因此:

1.如果其双亲结点的颜色是黑色,没有违反红黑树任何性质,则不需要调整,若还有问题,则说明插入之前该树就有问题。如图:

2.但当新插入节点的父结点颜色为红色时,就违反了性质三不能有连续的红色节点,此时就需要进行调整,但是该调整还跟叔叔节点的情况有关,因此分了三种情况,我们一起来看。 

约定:cur为当前节点,p为父节点,g为祖父节点,u为叔叔节点

 2.3.2.1cur为红,p为红,g为黑,u存在且为红

 2.3.2.2cur为红,p为红,g为黑,u不存在/u存在且为黑

对于此种情况下,其又分为两种小情况:

2.3.2.2.1 解决方式①

我们对当u不存在和u存在且为黑的情况依次进行分析:

当u不存在:

 

 当u存在且为黑:

 

 

对于解决方式①的情况下,p为g的左孩子,cur为p的左孩子,不管u不存在还是u存在且为黑,符合情况一的先完成其对应的操作,符合情况二的,都是进行右单旋 + 变色(p变黑,g变红),调整完成后 p已经是黑色的了,也不需要继续往上调整了。若调整后还有问题,则说明插入节点之前,该树就已经不是平衡的红黑树了,而我们的操作都是在插入之前其都是平衡的情况下进行的,所以调整完成后,就没有必要继续往上调整了。

 2.3.2.2.2 解决方式②

同理我们对当u不存在和u存在且为黑的情况依次进行分析:

当u不存在:

 

 

 当u存在且为黑:

 对于解决方式②的情况下,p为g的左孩子,cur为p的右孩子,不管u不存在还是u存在且为黑,符合情况一的先完成其对应的操作,符合情况二的,都是进行左右双旋 + 变色(cur变黑,g变红),调整完成后 cur已经是黑色的了,也不需要继续往上调整了。若调整后还有问题,则说明插入节点之前,该树就已经不是平衡的红黑树了,而我们的操作都是在插入之前其都是平衡的情况下进行的,所以调整完成后,就没有必要继续往上调整了。 

对调整的情况分析完后,所以,我们对插入所有的分析转换成代码,如下: 

 2.4插入整体代码实现

        bool Insert(const pair<K,V>& kv)
		{
			if (_Root == nullptr)
			{
				_Root = new Node(kv,BLACK);
				_Root->_parent = nullptr;
			}

			//寻找插入位置
			PNode cur = _Root;
			PNode parent = nullptr;
			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
					return false;
			}
			//插入
			cur = new Node(kv);
			if (kv.first < parent->_kv.first)
			{
				parent->_left = cur;
			}
			else if (kv.first > parent->_kv.first)
			{
				parent->_right = cur;
			}
			cur->_parent = parent;


			//调整
			while (parent && parent->_color == RED)//只要停留在情况一就继续判断
			{
				PNode grandparent = parent->_parent;
				PNode uncle = nullptr;

				//先定好uncle的位置,不管uncle是否存在
				if (parent == grandparent->_left)
				{
					uncle = grandparent->_right;

				}
				else
				{
					uncle = grandparent->_left;
				}

				
				if (uncle && uncle->_color == RED)//p为红、u存在且为红
				{
					
					//    g
					//  p   u
					// cur
					parent->_color = BLACK;
					uncle->_color = BLACK;
					grandparent->_color = RED;

					//根节点,更新结束
					if (grandparent == _Root)
					{
						grandparent->_color = BLACK;
						break;
					}
					//往上更新
					cur = grandparent;
					parent = cur->_parent;

				}
				else if (cur == parent->_left && parent == grandparent->_left )//cur为p的左孩子,p为g的左孩子,p为红
				{
					
				
					//     g
					//  p     u
					//cur
					RotateR(grandparent);
					parent->_color = BLACK;
					grandparent->_color = RED;
					break;

				}
				else if (cur == parent->_right &&  parent == grandparent->_right )//cur为p的右孩子,p为g的右孩子,p为红
				{
			
					//     g
					// u      p
					//          cur
					RotateL(grandparent);
					parent->_color = BLACK;
					grandparent->_color = RED;
					break;
				
				}
				else if (cur == parent->_right && parent == grandparent->_left )//p为g的左孩子,cur为p的右孩子,p为红
				{
					//   g
					//p     u
					//  cur
					RotateL(parent);
					RotateR(grandparent);
					cur->_color = BLACK;
					grandparent->_color = RED;
					break;
				}
				else if (cur == parent->_left && parent == grandparent->_right)//p为g的右孩子,cur为p的左孩子,p为红
				{
					//   g
					//u     p
					//   cur

					RotateR(parent);
					RotateL(grandparent);
					cur->_color = BLACK;
					grandparent->_color = RED;
					break;
				}
				else
				{
					assert(false);
				}
			}
			
			return true;
		}


        void RotateL(PNode parent)
		{
			PNode subR = parent->_right;
			PNode subRL = subR->_left;
			PNode pparent = parent->_parent;

			if (parent == _Root)//更新根节点
			{
				_Root = subR;
				subR->_parent = nullptr;
			}
			else
			{
				//更新parent的父节点指向
				if (parent == pparent->_left)
				{
					pparent->_left = subR;
				}
				else
				{
					pparent->_right = subR;
				}
				subR->_parent = pparent;

			}

			//parent的右指针指向subRL,subRL的父节点指向parent
			parent->_right = subR->_left;
			if (subRL)//subR的左节点可能不存在
				subRL->_parent = parent;

			//subR的左指针指向parent,parent的父节点指向subR
			subR->_left = parent;
			parent->_parent = subR;

		}

		//右单旋
		void RotateR(PNode parent)
		{
			PNode subL = parent->_left;
			PNode subLR = subL->_right;
			PNode pparent = parent->_parent;

			if (_Root == parent)
			{
				_Root = subL;
				subL->_parent = nullptr;
			}
			else
			{
				//更新parent的父节点指向
				if (pparent->_left == parent)
				{
					pparent->_left = subL;
				}
				else
				{
					pparent->_right = subL;
				}
				subL->_parent = pparent;
			}
			//parent的左指针指向subLR,subLR的父节点指向parent
			parent->_left = subLR;
			if (subLR)//subR的右节点可能不存在
				subLR->_parent = parent;
			//subL的右指针指向parent,parent的父节点指向subL
			subL->_right = parent;
			parent->_parent = subL;

		}

2.5红黑树与AVL树的比较与应用

同样的,对于红黑树的删除等操作,不在进行探究,因为有了插入的了解,就已经对红黑树的性质有了一定的掌握,如果对其他操作有额外兴趣的,可参照:《算法导论》或者《STL源码剖析》。

对比前一篇章AVL树的插入实现的理解,红黑树和AVL树都是高效的平衡二叉树,对于节点的操作的时间复杂度都是 log N,红黑树不追求绝对的平衡,其只需要保证最长路径不超过最短路径的2倍,且控制节点的颜色来控制相对平衡,只有在特定情况下,需要进行旋转,相对AVL树而言,降低了插入和旋转次数,当有了AVL树的基础,那么实现红黑树就会变得相对简单,所以在实际应用中,也会更多的采用红黑树 

例如:

1.c++STL库 -- map/set、multimap/multiset

2.java库

3.Linux库等

 三、红黑树插入测试

//RBTree.h
#pragma once
#include <iostream>
#include <assert.h>
using namespace std;


namespace bit
{
	enum Color//对于红黑节点,用枚举结构来表示
	{
		RED,
		BLACK
	};

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

		RBTreeNode<K, V>* _left;
		RBTreeNode<K, V>* _right;
		RBTreeNode<K, V>* _parent;

		pair<K, V> _kv;

		Color _color;//默认给红色才符合规则,若默认给黑色的话,则插入的每个节点都是黑色节点,
		//那么就不能保证每条路径的黑色节点相同,违反了第4条性质。而给红色,就可以根据规则调整。
	};

	template<class K, class V>
	class RBTree
	{
		typedef RBTreeNode<K, V> Node;
		typedef Node* PNode;
	public:
		RBTree()
			:_Root(nullptr)
		{}

		bool Insert(const pair<K,V>& kv)
		{
			if (_Root == nullptr)
			{
				_Root = new Node(kv,BLACK);
				_Root->_parent = nullptr;
			}

			//寻找插入位置
			PNode cur = _Root;
			PNode parent = nullptr;
			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
					return false;
			}
			//插入
			cur = new Node(kv);
			if (kv.first < parent->_kv.first)
			{
				parent->_left = cur;
			}
			else if (kv.first > parent->_kv.first)
			{
				parent->_right = cur;
			}
			cur->_parent = parent;


			//调整
			while (parent && parent->_color == RED)//只要停留在情况一就继续判断
			{
				PNode grandparent = parent->_parent;
				PNode uncle = nullptr;

				//先定好uncle的位置,不管uncle是否存在
				if (parent == grandparent->_left)
				{
					uncle = grandparent->_right;

				}
				else
				{
					uncle = grandparent->_left;
				}

				
				if (uncle && uncle->_color == RED)//p为红、u存在且为红
				{
					
					//    g
					//  p   u
					// cur
					parent->_color = BLACK;
					uncle->_color = BLACK;
					grandparent->_color = RED;

					//根节点,更新结束
					if (grandparent == _Root)
					{
						grandparent->_color = BLACK;
						break;
					}
					//往上更新
					cur = grandparent;
					parent = cur->_parent;

				}
				else if (cur == parent->_left && parent == grandparent->_left )//cur为p的左孩子,p为g的左孩子,p为红
				{
					
				
					//     g
					//  p     u
					//cur
					RotateR(grandparent);
					parent->_color = BLACK;
					grandparent->_color = RED;
					break;

				}
				else if (cur == parent->_right &&  parent == grandparent->_right )//cur为p的右孩子,p为g的右孩子,p为红
				{
			
					//     g
					// u      p
					//          cur
					RotateL(grandparent);
					parent->_color = BLACK;
					grandparent->_color = RED;
					break;
				
				}
				else if (cur == parent->_right && parent == grandparent->_left )//p为g的左孩子,cur为p的右孩子,p为红
				{
					//   g
					//p     u
					//  cur
					RotateL(parent);
					RotateR(grandparent);
					cur->_color = BLACK;
					grandparent->_color = RED;
					break;
				}
				else if (cur == parent->_left && parent == grandparent->_right)//p为g的右孩子,cur为p的左孩子,p为红
				{
					//   g
					//u     p
					//   cur

					RotateR(parent);
					RotateL(grandparent);
					cur->_color = BLACK;
					grandparent->_color = RED;
					break;
				}
				else
				{
					assert(false);
				}
			}
			
			return true;
		}

		void RotateL(PNode parent)
		{
			PNode subR = parent->_right;
			PNode subRL = subR->_left;
			PNode pparent = parent->_parent;

			if (parent == _Root)//更新根节点
			{
				_Root = subR;
				subR->_parent = nullptr;
			}
			else
			{
				//更新parent的父节点指向
				if (parent == pparent->_left)
				{
					pparent->_left = subR;
				}
				else
				{
					pparent->_right = subR;
				}
				subR->_parent = pparent;

			}

			//parent的右指针指向subRL,subRL的父节点指向parent
			parent->_right = subR->_left;
			if (subRL)//subR的左节点可能不存在
				subRL->_parent = parent;

			//subR的左指针指向parent,parent的父节点指向subR
			subR->_left = parent;
			parent->_parent = subR;

		}

		//右单旋
		void RotateR(PNode parent)
		{
			PNode subL = parent->_left;
			PNode subLR = subL->_right;
			PNode pparent = parent->_parent;

			if (_Root == parent)
			{
				_Root = subL;
				subL->_parent = nullptr;
			}
			else
			{
				//更新parent的父节点指向
				if (pparent->_left == parent)
				{
					pparent->_left = subL;
				}
				else
				{
					pparent->_right = subL;
				}
				subL->_parent = pparent;
			}
			//parent的左指针指向subLR,subLR的父节点指向parent
			parent->_left = subLR;
			if (subLR)//subR的右节点可能不存在
				subLR->_parent = parent;
			//subL的右指针指向parent,parent的父节点指向subL
			subL->_right = parent;
			parent->_parent = subL;

		}





		void InOrder()
		{
			_InOrder(_Root);
		}

		void _InOrder(PNode Root)
		{
			if (Root == nullptr)
				return;
			_InOrder(Root->_left);
			cout << Root->_kv.first << ":" << Root->_kv.second << endl;
			_InOrder(Root->_right);
			
		}

		bool IsBalance()
		{
			if (_Root->_color != BLACK)
				return false;

			int blackcount = 0;
			PNode cur = _Root;

			while (cur)
			{
				if (BLACK == cur->_color)
					blackcount++;
				cur = cur->_left;
			}

			int k = 0;


			return _IsBalance(_Root,k,blackcount);
		}

		bool _IsBalance(PNode Root, int k, int blackcount)
		{
			if (nullptr == Root)
			{
				if (k != blackcount)
				{
					cout << "违反性质四:每条路径黑色节点的个数必须相同" << endl;
					return false;
				}
				return true;
			}

			if (BLACK == Root->_color)
				k++;

			if (RED == Root->_color && RED == Root->_parent->_color)
			{
				cout << "违反性质三:不能出现连续的红色节点" << endl;
				return false;
			}

			return _IsBalance(Root->_left, k, blackcount) &&
				_IsBalance(Root->_right, k, blackcount);

		}

	private:
		PNode _Root;
	};

	void TestRBTree()
	{
		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> t;
		for (auto e : a)
		{
			t.Insert(make_pair(e, e));

		}

		t.InOrder();
		cout << t.IsBalance() << endl;
	}
}
//test.cpp
#include "RBTree.h"

int main()
{
	bit::TestRBTree();
	return 0;
}

 输出结果:

分析、代码是手敲的,若哪里有bug,或者哪里没懂的,请大家指正,谢谢~

下一篇章,将用红黑树来实现封装map/set

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

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

相关文章

好用的桌面备忘录是哪个?备忘录软件哪个更好用?

备忘录软件已成为我们日常生活和工作中不可或缺的工具&#xff0c;它能帮助我们记录重要事项、安排日程&#xff0c;从而提高工作效率&#xff0c;减少遗忘。在繁忙的工作和生活中&#xff0c;一款好用的备忘录软件往往能让我们事半功倍。 在众多的备忘录软件中&#xff0c;敬…

Jenkins 构建 Web 项目:构建服务器和部署服务器分离的情况

构建命令 #!/bin/bash node -v pnpm -v pnpm install pnpm build:prod # 将dist打包成dist.zip zip -r dist.zip dist

2024年艺术鉴赏与文化传播国际会议(AACC 2024)

2024年艺术鉴赏与文化传播国际会议&#xff08;AACC 2024&#xff09; 2024 International Conference on Art Appreciation and Cultural Communication 【重要信息】 大会地点&#xff1a;贵阳 大会官网&#xff1a;http://www.icaacc.com 投稿邮箱&#xff1a;icaaccsub-co…

VS QT 里头文件的<>和““的区别

今天在跑项目的时候遇到这么个问题&#xff0c;在添加api宏定义的时候&#xff0c;不加显示无法识别的外部错误&#xff0c;加了显示找不到文件。反正就是怎么都是错的&#xff0c;但是我检查了CmakeLists、模块所在文件夹、项目路径都是没有问题的。非常奇怪。 然后就开始尝试…

一阶数字高通滤波器

本文的主要内容包含一阶高通滤波器公式的推导和数字算法的实现以及编程和仿真 1 计算公式推导 1.1.2 算法实现及仿真 利用python实现的代码如下&#xff1a; import numpy as np # from scipy.signal import butter, lfilter, freqz import matplotlib.pyplot as plt #2pifW…

【LeetCode 随笔】面试经典 150 题【中等+困难】持续更新中。。。

文章目录 380.【中等】O(1) 时间插入、删除和获取随机元素238.【中等】除自身以外数组的乘积134.【中等】 加油站135.【困难】分发糖果42.【困难】接雨水 &#x1f308;你好呀&#xff01;我是 山顶风景独好 &#x1f49d;欢迎来到我的博客&#xff0c;很高兴能够在这里和您见面…

matlab使用教程(80)—修改图形对象的透明度

1.更改图像、填充或曲面的透明度 此示例说明如何修改图像、填充或曲面的透明度。 1.1坐标区框中所有对象的透明度 透明度值称为 alpha 值。使用 alpha 函数设置当前坐标区范围内所有图像、填充或曲面对象的透明度。指定一个介于 0&#xff08;完全透明&#xff09;和 1&#x…

第19讲:自定义类型:结构体

目录 1.结构体类型的声明1.1 结构体回顾1.1.1 结构的声明 特殊的结构声明1.3 结构的⾃引⽤ 2. 结构体内存的对齐2.2 为什么存在内存对⻬?2.3 修改默认对⻬数 3. 结构体传参4. 结构体实现位段4.1 什么是位段4.2 位段的内存分配4.3 位段的跨平台问题4.5 位段使⽤的注意事项 正文…

目标检测——无人机垃圾数据集

引言 亲爱的读者们&#xff0c;您是否在寻找某个特定的数据集&#xff0c;用于研究或项目实践&#xff1f;欢迎您在评论区留言&#xff0c;或者通过公众号私信告诉我&#xff0c;您想要的数据集的类型主题。小编会竭尽全力为您寻找&#xff0c;并在找到后第一时间与您分享。 …

【ONE·MySQL || 事务】

总言 主要内容&#xff1a;介绍事务。理解事务概念&#xff08;为什么存在&#xff09;&#xff0c;理解事务的四种属性&#xff08;原子性、持久性、隔离性、一致性&#xff09;&#xff0c;理解事务的隔离级别&#xff08;四种隔离级别&#xff0c;读写并发说明&#xff09;。…

Java zip解压时候 malformed input off : 0, length : 1

public static void unzip(String zipFilePath, String destDirectory) {File dir new File(destDirectory);// 如果目标文件夹不存在&#xff0c;则创建if (!dir.exists()) {dir.mkdirs();}byte[] buffer new byte[1024];try (ZipInputStream zis new ZipInputStream(new F…

C++小病毒

C小病毒&#xff08;注&#xff1a;对电脑无过大伤害&#xff09; 短短行&#xff0c;创造奇迹&#xff01; 把这个文件命名为virus.exe就可以使用了。 #include<bits/stdc.h> #include<windows.h> using namespace std; int main() {HWND hwnd GetForegroundW…

梳理 JavaScript 中空数组调用 every方法返回true 带来惊讶的问题

前言 人生总是在意外之中. 情况大概是这样的. 前两天版本上线以后, 无意中发现了一个bug, 虽然不是很大, 为了不让用户使用时感觉到问题. 还是对着一个小小的bug进行了修复, 并重新在上线一次, 虽然问题不大, 但带来的时间成本还是存在的. 以及上线后用户体验并不是很好. 问题…

OpenFeign微服务调用组件使用

前言&#xff1a;OpenFeign是可以跨服务、跨进程的调用方式。 什么是Feign Feign是Netflix开发的声明式、模版化的HTTP客户端。 优势: Feign可以做到使用 HTTP 请求远程服务时就像调用本地方法一样的体验&#xff0c;开发者完全感知不到这是远程方法&#xff0c;更感知不到这…

一个典型的分布式缓存系统是什么样的?no.32

分布式 Redis 服务 由于本课程聚焦于缓存&#xff0c;接下来&#xff0c;我将以微博内的 分布式 Redis 服务系统为例&#xff0c;介绍一个典型的分布式缓存系统的组成。 微博的 Redis 服务内部也称为 RedisService。RedisService 的整体架构如图所示。主要分为Proxy、存储、集…

使用第三方的PyCharm开发工具

目录 PyCharm下载 PyCharm安装 运行PyCharm 创建工程目录 编写“hello world”程序 在同一个工程下创建多个程序文件 运行程序的多种方法 保存程序 关闭程序或工程 删除程序 打开最近的工程 调试断点 熟悉PyCharm开发环境 设置Python解析器 输出彩色控制台文字及…

简易Docker磁盘使用面板Doku

这个项目似乎有 1 年多没更新了&#xff0c;最后发布版本的问题也没人修复&#xff0c;所以看看就行&#xff0c;不建议安装 什么是 Doku &#xff1f; Doku 是一个简单、轻量级的基于 Web 的应用程序&#xff0c;允许您以用户友好的方式监控 Docker 磁盘使用情况。Doku 显示 D…

一文读懂数电票,理解数电票与版式文件的关系

发票总的趋势是无纸化&#xff08;电子发票&#xff09;&#xff0c;方便计算机处理&#xff1b;最终达到节省人力物力的目的。国内在这方面进行了多年的探索&#xff0c;主要经历了以下几个阶段。 pdf格式的电子发票。 ofd格式电子发票&#xff0c;采用国密算法加密。 采用xml…

前端 CSS 经典:元素倒影

前言&#xff1a;好看的元素倒影&#xff0c;可以通过-webkit-box-reflect 实现。但有兼容问题&#xff0c;必须是 webkit 内核的浏览器&#xff0c;不然没效果。但是好看啊。 效果图&#xff1a; 代码实现&#xff1a; <!DOCTYPE html> <html lang"en"&g…

如何快速申请免费单域名SSL证书

申请免费的单域名SSL证书通常涉及以下几个步骤&#xff0c;虽然具体细节可能会根据不同的证书颁发机构(CA)有所差异。以下是通用的申请流程&#xff1a; 1.选择证书颁发机构&#xff1a; 访问提供免费单域名SSL证书的证书颁发机构网站&#xff0c;例如JoySSL等。 2.注册账号…