【C++】:红黑树深度剖析 --- 手撕红黑树!

news2024/12/28 3:01:29

目录

  • 前言
  • 一,红黑树的概念
  • 二,红黑树的性质
  • 三,红黑树节点的定义
  • 四,红黑树的插入操作
    • 4.1 第一步
    • 4.2 第二步
    • 4.3 插入操作的完整代码
  • 五,红黑树的验证
  • 六,实现红黑树的完整代码
  • 五,红黑树与AVL树的比较

点击跳转至上一篇文章: 【C++】:AVL树的深度解析及其实现

点击跳转至文章:【C++】:二叉树进阶 — 搜索二叉树

前言

上一篇文章介绍了什么是AVL树和AVL树的实现,AVL树也有它的缺点:就是太过追求绝对平衡,比如在插入时要维护其绝对平衡,旋转次数太多,在删除时甚至有可能要一直旋转到根位置,使之性能低下

本篇文章介绍的红黑树也是一种平衡树,是通过改变节点颜色以及旋转操作,使之接近平衡

红黑树比AVL树的用途更加广泛,在一些方面效率甚至要优于AVL树,并且 map/set 的底层封装用的也是红黑树。

一,红黑树的概念

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

在这里插入图片描述

二,红黑树的性质

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

三,红黑树节点的定义

红黑树的节点结构与AVL树的大致相同,只是AVL树中有节点的颜色,没有平衡因子。

//枚举颜色
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(BLACK)
	{}
};

四,红黑树的插入操作

首先我们要思考一个问题,插入节点时,到底是插入红色节点还是黑色节点?为什么?

答案:插入红色节点

因为我们知道红黑树最重要的两条性质是第(3)(4)条,通过维护这两条规则使之成为红黑树,而当新插入一个节点时,必定会破坏两条规则之一。

假设插入节点为黑色节点,则所有路径的黑色节点数量均不相同,如何让它们相同将是一个巨大的难题,而插入红色节点(此时一定是作为孩子节点),就破坏规则(3),但是只要根据其父亲和叔叔节点进行适当的变色就可以继续恢复规则(3)。

显而易见,规则(3)(4)就好比"慈父严母",非要选择得罪其中一人,那当然是"慈父"了。

红黑树是在二叉搜索树的基础上加上其平衡限制条件,因此红黑树的插入可分为两步:

4.1 第一步

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

bool Insert(const pair<K, V>& kv)
{
	if (_root == nullptr)
	{
		_root = new Node(kv);
		return true;
	}
	
	Node* cur = _root;
	Node* parent = nullptr;
	
	while (cur)
	{
		if (cur->_kv.first > kv.first)
		{
			parent = cur;
			cur = cur->_left;
		}
		else if (cur->_kv.first < kv.first)
		{
			parent = cur;
			cur = cur->_right;
		}
		else
			return false;
	}
	cur = new Node(kv);

	//新增节点,颜色为红色
	cur->_col = RED;

	//链接时要判断链接在parent的左还是右
	if (parent->_kv.first > kv.first)
		parent->_left = cur;
	else
		parent->_right = cur;

	cur->_parent = parent;

	//检测新节点插入后,红黑树的性质是否造到破坏
	//……
	
}

4.2 第二步

检测新节点插入后,红黑树的性质是否造到破坏

因为新节点的默认颜色是红色,因此:如果其双亲节点的颜色是黑色,没有违反红黑树任何性质,则不需要调整;但当新插入节点的双亲节点颜色为红色时,就违反了性质三不能有连在一起的红色节点,此时需要对红黑树分情况来讨论:

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

(2) 下面的抽象图中,a/b/c/d/e 表示具有 n 个节点的红黑树,n >=0

(一) 情况一: cur为红,p为红,g为黑,u存在且为红

在这里插入图片描述

解决方式:将p,u改为黑,g改为红,然后把g当成cur,继续向上调整

(二) 情况二: cur为红,p为红,g为黑,u不存在/u存在且为黑

在这里插入图片描述

下面我们根据 u 的情况用具象图分别解释单旋与双旋操作:

(1) u 不存在,a/b/c/d/e都是空,cur 是新增

p为g的左孩子,cur为p的左孩子,则进行右单旋转,再 p 变黑,g 变红

在这里插入图片描述

相反,p为g的右孩子,cur为p的右孩子,则进行左单旋转,再 p 变黑,g 变红

在这里插入图片描述

p为g的左孩子,cur为p的右孩子,先针对p做左单旋转,再针对 g 做右单旋,cur 变黑,g 变红

在这里插入图片描述

(2) u 存在且为黑

单旋情况

在这里插入图片描述
双旋情况
在这里插入图片描述

4.3 插入操作的完整代码

bool Insert(const pair<K, V>& kv)
{
	if (_root == nullptr)
	{
		_root = new Node(kv);
		return true;
	}
	Node* cur = _root;
	Node* parent = nullptr;
	while (cur)
	{
		if (cur->_kv.first > kv.first)
		{
			parent = cur;
			cur = cur->_left;
		}
		else if (cur->_kv.first < kv.first)
		{
			parent = cur;
			cur = cur->_right;
		}
		else
			return false;
	}
	cur = new Node(kv);

	//新增节点,颜色为红色
	cur->_col = RED;

	//链接时要判断链接在parent的左还是右
	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;
		//    g
		// p     u
		if (parent == grandfather->_left)
		{
			//找到u的位置
			Node* uncle = grandfather->_right;
			
			if (uncle && uncle->_col == RED)
			{
				//u存在且为红,把p/u变黑,g变红,继续向上调整
				parent->_col = uncle->_col = BLACK;
				grandfather->_col = RED;

				cur = grandfather;
				parent = cur->_parent;
			}
			else
			{
				//u不存在或存在且为黑
				if (cur == parent->_left)
				{
					//     g
					//  p     u    
					//c
					//单旋
					RotateR(grandfather);
					parent->_col = BLACK;
					grandfather->_col = RED;
				}
				else
				{
					//     g
					//  p     u    
					//	   c
					//双旋
					RotateL(parent);
					RotateR(grandfather);
					
					cur->_col = BLACK;
					grandfather->_col = RED;
				}

				break;
			}
		}
		else
		{
			//    g
			// u     p
			Node* uncle = grandfather->_left;
			
			//u存在且为红,把p/u变黑,g变红,继续向上调整
			if (uncle && uncle->_col == RED)
			{
				parent->_col = uncle->_col = BLACK;
				grandfather->_col = RED;

				cur = grandfather;
				parent = cur->_parent;
			}
			else
			{
				//u不存在或存在且为黑
				if (cur == parent->_right)
				{
					//    g
					// u      p
					//            c
					//单旋
					RotateL(grandfather);
					parent->_col = BLACK;
					grandfather->_col = RED;
				}
				else
				{
					//    g
					// u     p
					//    c
					//双旋
					RotateR(parent);
					RotateL(grandfather);
					
					cur->_col = BLACK;
					grandfather->_col = RED;
				}
				break;
			}
		}
	}

	_root->_col = BLACK;

	return true;
}

五,红黑树的验证

红黑树的检测分为两步:

(1) 检测其是否满足二叉搜索树(中序遍历是否为有序序列)
(2) 检测其是否满足红黑树的性质

这里重点检测的是性质(3)与性质(4)。

检测性质(3):只要判断当前节点与其父亲节点是否为连续的红色节点

检测性质(4):先计算出任意一条路径上的黑色节点作为参考值,再用这个参考值与其他路径上的黑色节点数量比较

private:
	//中序遍历
	void InOrder()
	{
		_Inorder(_root);
		cout << endl;
	}

	//判断是否平衡
	bool IsBalance()
	{
		if (_root == nullptr)
			return true;

		//检查根节点
		if (_root->_col == RED)
			return false;
		
		// 参考值
		int refNum = 0;
		Node* cur = _root;
		while (cur)
		{
			if (cur->_col == BLACK)
				++refNum;
			
			cur = cur->_left;
		}

		return Check(_root, 0, refNum);
	}

private:
	bool Check(Node* root, int blackNum, const int refNum)
	{
		//每条路径走到空后与参考值进行比较
		if (root == nullptr)
		{
			//cout << blackNum << endl;
			if (refNum != blackNum)
			{
				cout << "存在黑色节点的数量不相等的路径" << endl;
				return false;
			}

			return true;
		}

		//检查是否存在连续的红色节点
		if (root->_col == RED && root->_parent->_col == RED)
		{
			cout << root->_kv.first << "存在连续的红色节点" << endl;
			return false;
		}
		
		//blackNum表示:根节点到当前节点的路径上黑色节点的数量
		if (root->_col == BLACK)
			blackNum++;
		
		return Check(root->_left, blackNum, refNum)
			&& Check(root->_right, blackNum, refNum);
	}

六,实现红黑树的完整代码

RBTree.h

#pragma once

#include <iostream>
#include <assert.h>
using namespace std;

//枚举颜色
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(BLACK)
	{}
};

template <class K, class V>
class RBTree
{
	typedef RBTreeNode<K, V> Node;
public:
	Node* Find(const pair<K, V>& kv)
	{
		Node* cur = _root;
		while (cur)
		{
			if (cur->_kv.first > kv.first)
				cur = cur->_left;
			else if (cur->_kv.first < kv.first)
				cur = cur->_right;
			else
				return cur;
		}
		return nullptr;
	}

	bool Insert(const pair<K, V>& kv)
	{
		if (_root == nullptr)
		{
			_root = new Node(kv);
			return true;
		}
		Node* cur = _root;
		Node* parent = nullptr;
		while (cur)
		{
			if (cur->_kv.first > kv.first)
			{
				parent = cur;
				cur = cur->_left;
			}
			else if (cur->_kv.first < kv.first)
			{
				parent = cur;
				cur = cur->_right;
			}
			else
				return false;
		}
		cur = new Node(kv);

		//新增节点,颜色为红色
		cur->_col = RED;

		//链接时要判断链接在parent的左还是右
		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;
			//    g
			// p     u
			if (parent == grandfather->_left)
			{
				//找到u的位置
				Node* uncle = grandfather->_right;
				
				if (uncle && uncle->_col == RED)
				{
					//u存在且为红,把p/u变黑,g变红,继续向上调整
					parent->_col = uncle->_col = BLACK;
					grandfather->_col = RED;

					cur = grandfather;
					parent = cur->_parent;
				}
				else
				{
					//u不存在或存在且为黑
					if (cur == parent->_left)
					{
						//     g
						//  p     u    
						//c
						//单旋
						RotateR(grandfather);
						parent->_col = BLACK;
						grandfather->_col = RED;
					}
					else
					{
						//     g
						//  p     u    
						//	   c
						//双旋
						RotateL(parent);
						RotateR(grandfather);
						
						cur->_col = BLACK;
						grandfather->_col = RED;
					}

					break;
				}
			}
			else
			{
				//    g
				// u     p
				Node* uncle = grandfather->_left;
				
				//u存在且为红,把p/u变黑,g变红,继续向上调整
				if (uncle && uncle->_col == RED)
				{
					parent->_col = uncle->_col = BLACK;
					grandfather->_col = RED;

					cur = grandfather;
					parent = cur->_parent;
				}
				else
				{
					//u不存在或存在且为黑
					if (cur == parent->_right)
					{
						//    g
						// u      p
						//            c
						//单旋
						RotateL(grandfather);
						parent->_col = BLACK;
						grandfather->_col = RED;
					}
					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);
		cout << endl;
	}

	//判断是否平衡
	bool IsBalance()
	{
		if (_root == nullptr)
			return true;

		//检查根节点
		if (_root->_col == RED)
			return false;
		
		// 参考值
		int refNum = 0;
		Node* cur = _root;
		while (cur)
		{
			if (cur->_col == BLACK)
				++refNum;
			
			cur = cur->_left;
		}

		return Check(_root, 0, refNum);
	}

private:
	//每个节点的位置记录一个值blackNum
	//blackNum表示:根节点到当前节点的路径上黑色节点的数量
	bool Check(Node* root, int blackNum, const int refNum)
	{
		//每条路径走到空后与参考值进行比较
		if (root == nullptr)
		{
			//cout << blackNum << endl;
			if (refNum != blackNum)
			{
				cout << "存在黑色节点的数量不相等的路径" << endl;
				return false;
			}

			return true;
		}

		//检查是否存在连续的红色节点
		if (root->_col == RED && root->_parent->_col == RED)
		{
			cout << root->_kv.first << "存在连续的红色节点" << endl;
			return false;
		}

		if (root->_col == BLACK)
			blackNum++;
		
		return Check(root->_left, blackNum, refNum)
			&& Check(root->_right, blackNum, refNum);
	}

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

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

		subL->_right = parent;

		//改变之前记录
		Node* ppNode = parent->_parent;
		parent->_parent = subL;

		//parent为根
		if (parent == _root)
		{
			_root = subL;
			_root->_parent = nullptr;
		}
		else
		{
			//parent为一颗子树
			if (ppNode->_left == parent)
				ppNode->_left = subL;
			else
				ppNode->_right = subL;

			subL->_parent = ppNode;
		}
	}

	//左单旋
	void RotateL(Node* parent)
	{
		Node* subR = parent->_right;
		Node* subRL = subR->_left;

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

		subR->_left = parent;

		Node* ppNode = parent->_parent;
		parent->_parent = subR;

		//parent为根
		if (parent == _root)
		{
			_root = subR;
			_root->_parent = nullptr;
		}
		else
		{
			//parent为一颗子树
			if (ppNode->_left == parent)
				ppNode->_left = subR;
			else
				ppNode->_right = subR;

			subR->_parent = ppNode;
		}
	}

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

		_Inorder(root->_left);
		cout << root->_kv.first << ":" << root->_kv.second << endl;
		_Inorder(root->_right);
	}
private:
	Node* _root = nullptr;
};

//测试代码
void Test1()
{
	//int a[] = { 8, 3, 1, 10, 6, 4, 7, 14, 13 };
	int a[] = { 16,3,7,11,9,26,18,14,15 };
	RBTree<int, int> t;
	for (auto e : a)
		t.Insert({ e ,e });

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

Test.cpp

#include "RBTree.h"

int main()
{
	Test1();

	return 0;
}

运行结果如下

中序遍历是有序的,说明是搜索二叉树,返回1,说明满足红黑树的性质,是平衡树

在这里插入图片描述

五,红黑树与AVL树的比较

红黑树和AVL树都是高效的平衡二叉树,增删改查的时间复杂度都是O( l o g 2 N log_2 N log2N),红黑树不追求绝对平衡,其只需保证最长路径不超过最短路径的2倍,相对而言,降低了插入和旋转的次数,所以在经常进行增删的结构中性能比AVL树更优,而且红黑树实现比较简单,所以实际运用中红黑树更多。

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

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

相关文章

从 NextJS SSRF 漏洞看 Host 头滥用所带来的危害

前言 本篇博文主要内容是通过代码审计以及场景复现一个 NextJS 的安全漏洞&#xff08;CVE-2024-34351&#xff09;来讲述滥用 Host 头的危害。 严正声明&#xff1a;本博文所讨论的技术仅用于研究学习&#xff0c;旨在增强读者的信息安全意识&#xff0c;提高信息安全防护技能…

Java Linux操作系统

1、操作系统是协助用户调度硬件工作&#xff0c;充当用户和计算机硬件之间的桥梁 2、Linux内核 提供了linux系统的主要功能 3、发行版Centos&#xff1a;内核应用程序 4、快照&#xff1a;保存虚拟机的状态&#xff0c;当虚拟机出现问题的时候&#xff0c;可以恢复原始的状态…

Hospital 14.6.0全开源医院管理预约系统源码

InfyHMS 具有 60 种功能和 9 种不同类型的用户类型&#xff0c; 他们可以登录系统并根据他们的角色访问他们的数据。 源码下载&#xff1a;https://download.csdn.net/download/m0_66047725/89580674 更多资源下载&#xff1a;关注我。

MyBatis--11-- 判断 Integer类型,值为0动态SQL不生效

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 文章目录 MyBatis标签&#xff1a; Integer类型&#xff0c;值为0动态SQL不生效1.现象2.分析原因3.解决办法去掉判断条件 dto.status ! "即可 MyBatis标签&#xff1a; …

CSS相关记录

文章目录 backgroundposition文字displayflexjustify-contentalign-itemsflex-directionflex-wrap gridimportant transformtranslate&#xff08;位移&#xff09;scale&#xff08;缩放&#xff09;rotate&#xff08;旋转&#xff09;origin (旋转中心点)skew (倾斜 ) borde…

kafka架构+原理+源码

1.安装jdk17 sudo yum -y update sudo wget https://download.oracle.com/java/17/latest/jdk-17_linux-x64_bin.rpm sudo yum -y install ./jdk-17_linux-x64_bin.rpm、 sudo java -version 2.安装kafka How to easily install kafka without zookeeper | Aditya’s Blog …

C++——保持原有库头文件不变的情况下,成功编译运行工程

问&#xff1a;想要保持原来库方式&#xff0c;应该怎么操作呢&#xff1f; 答&#xff1a;如果想保持原来的方式&#xff0c;则只需要将 库所在路径 tracker/detector/rknn_model_zoo/utils 加入到 工程库包含中即可。

DVWA的安装和使用

背景介绍 DVWA是Damn Vulnerable Web Application的缩写&#xff0c;是一个用于安全脆弱性检测的开源Web应用。它旨在为安全专业人员提供一个合法的测试环境&#xff0c;帮助他们测试自己的专业技能和工具&#xff0c;同时也帮助web开发者更好地理解web应用安全防范的过程。DV…

FastAPI(七十)实战开发《在线课程学习系统》接口开发--留言功能开发

源码见&#xff1a;"fastapi_study_road-learning_system_online_courses: fastapi框架实战之--在线课程学习系统" 在之前的文章&#xff1a;FastAPI&#xff08;六十九)实战开发《在线课程学习系统》接口开发--修改密码&#xff0c;这次分享留言功能开发 我们梳理…

gstreamer使用cairo实现视频OSD叠加

前言 gstreamer中视频叠加OSD有很多种方式,比如textoverlay添加文字,gdkpixbufoverlay添加图片,clockoverlay或timeoverlay插件显示时间,pango插件进行复杂文本渲染,使用cairo插件绘制图形或者文字。 今天使用最后一种:cairo 项目源码: 如果是QT,pro文件需要导入: …

Tensorflow中高维矩阵的乘法运算tf.matmul(tf.linalg.matmul)详悉

1.问题由来 在tensorflow框架下&#xff0c;经常会用到矩阵的乘法运算&#xff0c;特别是高&#xff08;多&#xff09;维的矩阵运算&#xff0c;在这些矩阵运算时&#xff0c;经常使用到其中的tf.matmul或tf.linalg.matmul等函数。但高维矩阵在内部怎么运算的&#xff1f;其内…

跟代码执行流程,读Megatron源码(四)megatron初始化脚本initialize.py之initialize_megatron()分布式环境初始化

在前文中&#xff0c;我们讲述了pretrain函数的执行流程&#xff0c;其首要步骤是megatron分组的初始化与环境的配置。本文将深入initialize_megatron函数源码&#xff0c;剖析其初始化分布式训练环境的内部机制。 注&#xff1a;在此假设读者具备3D并行相关知识 一. initiali…

Zabbix自定义监控内容部署+邮件报警+Zabbix自愈+Zabbix批量添加主机

一、自定义监控项 1.1自定义监控项原理 1&#xff09;先明确获取监控指标数据的命令或脚本; 2&#xff09;在被监控主机配置文件子目录&#xff08;/etc/zabbix/zabbix_agent2.d/)中创建以.conf后缀的监控项配置文件&#xff0c;自定义获取监控指标数据的键值&#xff1b; …

Windows:批处理脚本学习

目录 一、第一个批处理文件 1. &&和 | | 2. | 和 & 二、变量 1.传参变量%name 2.初始化变量set命令 3.变量的使用 4.局部变量与全局变量 5.使用环境变量 6.扩充变量语法 三、注释REM和 &#xff1a;&#xff1a; 四&#xff1a;函数 1.定义函数 2.…

鸿蒙 Navigation VS Router 对比

当前HarmonyOS支持两套路由机制&#xff08;Navigation和Router&#xff09;&#xff0c;Navigation作为后续长期演进及推荐的路由选择方案&#xff0c;其与Router比较的优势如下&#xff1a; 易用性层面&#xff1a; Navigation天然具备标题、内容、回退按钮的功能联动&…

看 Unity 组件的源码 —— ILSpy

ILSpy 是开源的 .NET 程序集浏览器和解编译器。 下载 ILSpy ILSpy Github 地址&#xff1a;icsharpcode/ILSpy: .NET Decompiler with support for PDB generation, ReadyToRun, Metadata (&more) - cross-platform! (github.com) 它有 Release 包可以下载 也提供 IDE 的…

Cadence23学习笔记(十四)

ARC就是圆弧走线的意思&#xff1a; 仅打开网络的话可以只针对net进行修改走线的属性&#xff1a; 然后现在鼠标左键点那个走线&#xff0c;那个走线就会变为弧形&#xff1a; 添加差分对&#xff1a; 之后&#xff0c;分别点击两条线即可分配差分对&#xff1a; 选完差分对之后…

解锁创新:AI如何推动低代码应用的智能化

在当今快速变化的商业环境中&#xff0c;企业面临着前所未有的挑战和机遇。数字化转型已成为各行各业的必然趋势&#xff0c;企业需要迅速适应市场变化&#xff0c;提升客户体验&#xff0c;并降低开发成本。 这一背景下&#xff0c;低代码开发平台的崛起为企业提供了一种高效…

ICIP-2020-A Non-local Mean Temporal Filter for VideoCompression

在 libvpx、VP8、VP9 和 HEVC 等各种编码器实现中&#xff0c;早就发现在预处理阶段过程中从源视频信号去除噪声对客观压缩效率的提升存在好处。通常使用常规的块匹配运动搜索来构建运动轨迹&#xff0c;并沿着轨迹比较每对像素&#xff0c;根据像素间的差异确定时域滤波器系数…

SpringSecurity如何整合JWT

整合JWT 我们前几个小节&#xff0c;实现的是非前后端分离情况下的认证与授权的处理&#xff0c;目前大部分项目&#xff0c;都是使用前后端分离的模式。那么前后端分离的情况下&#xff0c;我们如何使用SpringSecurity来解决权限问题呢&#xff1f;最常见的方案就是SpringSe…