首页 > 编程语言 >【C++】红黑树万字详解(一文彻底搞懂红黑树的底层逻辑)

【C++】红黑树万字详解(一文彻底搞懂红黑树的底层逻辑)

时间:2024-10-24 17:49:06浏览次数:11  
标签:color 红黑树 pRoot C++ pCur pParent 搞懂 data 节点

目录

00.引入

01.红黑树的性质

02.红黑树的定义

03.红黑树的插入

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

2.检测新节点插入后,是否满足红黑树的性质

1.uncle节点存在且为红色

2.uncle节点不存在

3.uncle节点存在且为黑色

 04.验证红黑树


00.引入

和AVL树一样,红黑树也是一种自平衡的二叉搜索树。AVL树通过平衡因子调整子树的高度,确保树的高度差在 -1 到 1 之间。而红黑树通过在每个节点上增加一个存储位表示节点的颜色,可以是RED或BLACK。通过对任何一条从根到叶子的路径上各个节点着色方式的限制,红黑树确保没有任何一条路径会是其他路径的两倍以上长,因而接近平衡。

01.红黑树的性质

红黑树具有以下几种性质,这些性质确保了树的平衡性和高效性:

1.节点颜色:每个节点要么是红色,要么是黑色。

2.根节点颜色:根节点始终是黑色。

3.红色节点限制:没有两个红色节点可以相连。

4.黑色节点数量:从任何节点到其每个叶子节点的所以路径包含相同数量的黑色节点。

5.叶子节点:所有的叶子节点(此处都表示为NULL节点)都是黑色。

其实还有一条潜规则:每一个新插入的节点初始都为红色。

为什么以上性质就可以保证最长路径的节点个数不会超过最短路径节点个数的两倍

通过性质1/2/5可知,在判断性质4时(计算路径上黑色节点的数量),不需要考虑头节点以及叶子节点,只需要考虑中间节点即可,再根据性质3,可以列举以下几种情况:

1.中间没有黑色节点:

路径: ->->红->

2.中间只有1个黑色节点:

路径: ->->->红->->->->红->->红->->红->

3.中间有2个黑色节点:

路径: ->->->->红->->->->->红->->->->->红->

            。。。黑->红->->红->->红->

可以看出,最短路径就是全黑的;最长路径是黑、红相间的。因而我们可以找到规律:

假设中间有n个黑色节点:

最短路径:n+2(中间节点加上首尾)

最长路径:n+(n+1)+2=2n+3(中间有n个黑色节点最多可以有n+1个红色节点)

所以最长路径(2n+3)永远比最短路径的2倍(2n+4)小1。

02.红黑树的定义

红黑树的底层结构在代码实现中使用节点结构体和数来表示。节点结构体 保存每个节点的数据、指向左右子节点、父节点的指针、以及平衡因子;树类管理插入、删除等操作。

// 节点的颜色
enum Color { RED, BLACK };

// 红黑树节点的定义
template<class T>
struct RBTreeNode
{
	RBTreeNode(const T& data = T(), Color color = RED)
		: _pLeft(nullptr)
		, _pRight(nullptr)
		, _pParent(nullptr)
		, _data(data)
		, _color(color)
	{}

	RBTreeNode<T>* _pLeft; // 节点的左孩子
	RBTreeNode<T>* _pRight; // 节点的右孩子
	RBTreeNode<T>* _pParent; // 节点的双亲
	T _data; // 节点的值域
	Color _color; // 节点的颜色
};

在节点的定义中,我们将节点默认颜色给成红色,这是因为如果定义成黑色的话,根据性质4,每一次插入节点都需要重新调整树,而定义成红色,就只需要在父节点也为红色时进行调整,一定程度上保证了插入的效率。

03.红黑树的插入

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

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

	// 在红黑树中插入值为data的节点,插入成功返回true,否则返回false
	// 注意:为了简单起见,本次实现红黑树不存储重复性元素
	bool Insert(const T& data)
	{
		if (!_pRoot) // 根节点为空直接插入
		{
			_pRoot = new Node(data);
			_pRoot->_color = BLACK; // 设置根节点为黑色
			return true;
		}
		Node* pParent = nullptr;
		Node* pCur = _pRoot;
		while (pCur)
		{
			pParent = pCur;
			if (data == pCur->_data) // 元素重复,不进行插入
				return false;
			else if (data > pCur->_data) // 大于向右走
				pCur = pCur->_pRight;
			else // 小于向左走
				pCur = pCur->_pLeft;
		}

		pCur = new Node(data);
		// 更新父节点的子节点指针
		if (data < pParent->_data)
			pParent->_pLeft = pCur;
		else
			pParent->_pRight = pCur;
		pCur->_pParent = pParent;
        return true;
    }

二叉搜索树的插入过程不在赘述,详细可跳转这篇博客【C++】二叉搜索树

2.检测新节点插入后,是否满足红黑树的性质

因为新插入节点默认为红色,因此如果双亲节点是黑色,则满足性质,不需要调整;如果双亲节点为红色,违反了性质3,需要进行调整,调整过程需要进行分类讨论:

第一种情况:

我们调整红黑树的底层逻辑是通过改变某些节点的颜色,使整棵树符合性质,看下面这棵树:

cur表示新插入节点,parent为父节点,grandpa为父节点的父节点,uncle为grandpa的另一个节点

(下面用c,p,g,u分别表示cur,parent,grandpa,uncle

此时c和p是连续的红色节点。破坏了性质3,那么我直接把p的颜色改成黑色,性质3被修复,但同时又破坏了性质4,于是我把u的颜色也改成黑色,这样以g为根节点的这棵树确实被修复了。

但要注意,如果g不是根节点呢

此时性质4还是被破坏了,为此,把g的颜色改成红色

这样就完成了修复。但如果是下面这种情况呢

g的双亲也为红色,此时需要继续向上调整。。

所以我们得到了第一种情况:

1.uncle节点存在且为红色

a.祖父节点为根 或 祖父的双亲为黑色

只需要修改u和p的颜色即可

b.祖父节点不为根 且 祖父的双亲为红色

此时需要进一步向上调整

将cur的位置换到grandpa,p、u、g一并转换,此时u一定存在,如果u不存在,则违反了性质4

第二种情况:

2.uncle节点不存在

1.单旋的情况

学习过AVL树可以知道,对于这样的一棵树我们需要通过旋转的方法调整高度,关于旋转的原理,可以调整这篇博客【C++】AVL树

旋转完成之后,我们需要调整颜色,此时p成为了这棵树的根节点,为了满足条件4,我们把p颜色改成黑,g颜色改成红:

这是左单旋的情况,右单旋同理

2.双旋的情况

双旋分为 右左 与 左右 两种情况,右左即p是g的右孩子,c是p的左孩子。右左情况下我们需要对p进行右单旋,在对g进行左单旋,最后调整c和g的颜色,过程如下:

左右的情况同理

3.uncle节点存在且为黑色

a.单旋的情况

此时cur一定不是新插入节点,这种情况就是1.b向上调整的后续了,此时依旧要用到旋转操作,和情况2同理:

b.双旋的情况

由此看来,情况2和情况3其实可以合并。

下面给出插入以及旋转的完整代码:

template<class T>
class RBTree
{
	typedef RBTreeNode<T> Node;
public:
	RBTree()
	{
		_pRoot = nullptr;
	}

	// 在红黑树中插入值为data的节点,插入成功返回true,否则返回false
	// 注意:为了简单起见,本次实现红黑树不存储重复性元素
	bool Insert(const T& data)
	{
		if (!_pRoot) // 根节点为空直接插入
		{
			_pRoot = new Node(data);
			_pRoot->_color = BLACK; // 设置根节点为黑色
			return true;
		}
		Node* pParent = nullptr;
		Node* pCur = _pRoot;
		while (pCur)
		{
			pParent = pCur;
			if (data == pCur->_data) // 元素重复,不进行插入
				return false;
			else if (data > pCur->_data) // 大于向右走
				pCur = pCur->_pRight;
			else // 小于向左走
				pCur = pCur->_pLeft;
		}

		pCur = new Node(data);
		// 更新父节点的子节点指针
		if (data < pParent->_data)
			pParent->_pLeft = pCur;
		else
			pParent->_pRight = pCur;
		pCur->_pParent = pParent;
		while (pParent && pParent->_color == RED)
		{
			Node* pGrandpa = pParent->_pParent;
			if (!pGrandpa)
				pParent->_color = BLACK;
			else {
				if (pParent == pGrandpa->_pRight) 
				{
					Node* pUncle = pGrandpa->_pLeft;
					// uncle存在且为红
					if (pUncle && pUncle->_color == RED)
					{
						pUncle->_color = pParent->_color = BLACK;
						pGrandpa->_color = RED;
						// 继续向上调整
						pCur = pGrandpa;
						pParent = pCur->_pParent;
					}
					// uncle不存在或者uncle为黑
					else
					{
						// 右右的情况
						if (pCur == pParent->_pRight)
						{
							RotateL(pGrandpa);
							pParent->_color = BLACK;
							pGrandpa->_color = RED;
						}
						// 右左的情况
						else
						{
							RotateR(pParent);
							RotateL(pGrandpa);
							pCur->_color = BLACK;
							pGrandpa->_color = RED;
						}
						break;
					}
				}
				else // pParent = pGrandpa->_pLeft
				{
					Node* pUncle = pGrandpa->_pRight;
					// uncle存在且为红
					if (pUncle && pUncle->_color == RED)
					{
						pUncle->_color = pParent->_color = BLACK;
						pGrandpa->_color = RED;
						// 继续向上调整
						pCur = pGrandpa;
						pParent = pCur->_pParent;
					}
					// uncle不存在或者uncle为黑
					else
					{
						// 左左的情况
						if (pCur == pParent->_pLeft)
						{
							RotateR(pGrandpa);
							pParent->_color = BLACK;
							pGrandpa->_color = RED;
						}
						// 左右的情况
						else
						{
							RotateL(pParent);
							RotateR(pGrandpa);
							pCur->_color = BLACK;
							pGrandpa->_color = RED;
						}
						break;
					}
				}
			}
		}
		_pRoot->_color = BLACK;
		return true;
	}

private:
	// 左单旋
	void RotateL(Node* pParent)
	{
		Node* pCur = pParent->_pRight;
		pParent->_pRight = pCur->_pLeft; // cur的左孩子作为parent的右孩子
		if (pCur->_pLeft)
			pCur->_pLeft->_pParent = pParent;
		if (pParent->_pParent) // parent不是根节点
		{
			pCur->_pParent = pParent->_pParent;
			if (pParent->_pParent->_pLeft == pParent)
				pParent->_pParent->_pLeft = pCur;
			else
				pParent->_pParent->_pRight = pCur;
		}
		else // parent是根节点
		{
			_pRoot = pCur;
			pCur->_pParent = nullptr;
		}
		pParent->_pParent = pCur;
		pCur->_pLeft = pParent;
	}

	// 右单旋
	void RotateR(Node* pParent)
	{
		Node* pCur = pParent->_pLeft;
		pParent->_pLeft = pCur->_pRight; // cur的右孩子作为parent的左孩子
		if (pCur->_pRight)
			pCur->_pRight->_pParent = pParent;
		if (pParent->_pParent) // parent不是根节点
		{
			pCur->_pParent = pParent->_pParent;
			if (pParent->_pParent->_pLeft == pParent)
				pParent->_pParent->_pLeft = pCur;
			else
				pParent->_pParent->_pRight = pCur;
		}
		else // parent是根节点
		{
			_pRoot = pCur;
			pCur->_pParent = nullptr;
		}
		pParent->_pParent = pCur;
		pCur->_pRight = pParent;
	}

private:
	Node* _pRoot;
};

 04.验证红黑树

检验上述代码,就得从红黑树的5条性质入手,性质1、5不需要验证,性质2也很容易验证,实际上我们主要验证的是性质3(没有连续的红色节点)以及性质4(每条路径上黑色节点数量一致)

对于性质4,我们可以先记录某一条路径(这里选取最左叶节点这条路径)的黑色节点数量,再遍历每一条路径,只要有一条路径上的黑色节点数量与前者不一样,就返回false,这里选择用递归的方式遍历红黑树。

对于性质3,我们需要再遍历的过程中,碰到红色节点,就验证一下双亲的颜色,如果也为红,则违反性质,返回false

完整代码如下:

	// 检测红黑树是否为有效的红黑树,注意:其内部主要依靠_IsValidRBTRee函数检测
	bool IsValidRBTRee()
	{
		if (!_pRoot)
			return true;
		if (RED == _pRoot->_color)
			return false;
		Node* pCur = _pRoot;
		size_t blackcount = 0;
		while (pCur)
		{
			if (BLACK == pCur->_color)
				++blackcount;
			pCur = pCur->_pLeft;
		}
		return _IsValidRBTRee(_pRoot, blackcount, 0);
	}

	bool _IsValidRBTRee(Node* pRoot, size_t blackCount, size_t pathBlack) {
		if (!pRoot) {
			// 如果到达叶子节点,检查黑色节点数量
			return pathBlack == blackCount;
		}
		// 检查当前节点的颜色
		if (pRoot->_color == RED) {
			// 如果是红色,检查父节点是否也是红色
			if (pRoot->_pParent && pRoot->_pParent->_color == RED) {
				return false; // 违反红黑树性质
			}
		}
		// 更新路径上的黑色节点数量
		if (pRoot->_color == BLACK) {
			pathBlack++;
		}
		// 递归检查左子树和右子树
		return _IsValidRBTRee(pRoot->_pLeft, blackCount, pathBlack) &&
			_IsValidRBTRee(pRoot->_pRight, blackCount, pathBlack);
	}

最后我们用一万组包含10个1~100随机数的数组验证红黑树:

#include<iostream>
using namespace std;
#include"My_RBTree.h"
#include<vector>


bool test1()
{
    srand(time(0));  // 初始化随机种子

// 生成10000组随机向量
    for (int i = 0; i < 10000; ++i) {
        RBTree<int> BT;
        vector<int> nums;

        // 每组随机生成10个整数(范围为 1 到 100)
        for (int j = 0; j < 10; ++j) {
            nums.push_back(rand() % 100 + 1);
        }

        // 将每个整数插入 B 树中
        for (const int& num : nums) {
            BT.Insert(num);
        }

        // 检查 B 树
        if (!BT.IsValidRBTRee()) {
            cout << "树不平衡,测试失败!\n";
            for (auto it : nums)
            {
                cout << it << ", ";
            }
            cout << endl;
            return 1;
        }
    }

    cout << "所有10000组随机树均平衡,测试通过!\n";
}

int main()
{
	test1();
	return 0;
}

运行结果:

验证通过!

附上完整代码:

//My_RBTree.h
#pragma once
// 节点的颜色
enum Color { RED, BLACK };

// 红黑树节点的定义
template<class T>
struct RBTreeNode
{
	RBTreeNode(const T& data = T(), Color color = RED)
		: _pLeft(nullptr)
		, _pRight(nullptr)
		, _pParent(nullptr)
		, _data(data)
		, _color(color)
	{}

	RBTreeNode<T>* _pLeft; // 节点的左孩子
	RBTreeNode<T>* _pRight; // 节点的右孩子
	RBTreeNode<T>* _pParent; // 节点的双亲
	T _data; // 节点的值域
	Color _color; // 节点的颜色
};

template<class T>
class RBTree
{
	typedef RBTreeNode<T> Node;
public:
	RBTree()
	{
		_pRoot = nullptr;
	}

	// 在红黑树中插入值为data的节点,插入成功返回true,否则返回false
	// 注意:为了简单起见,本次实现红黑树不存储重复性元素
	bool Insert(const T& data)
	{
		if (!_pRoot) // 根节点为空直接插入
		{
			_pRoot = new Node(data);
			_pRoot->_color = BLACK; // 设置根节点为黑色
			return true;
		}
		Node* pParent = nullptr;
		Node* pCur = _pRoot;
		while (pCur)
		{
			pParent = pCur;
			if (data == pCur->_data) // 元素重复,不进行插入
				return false;
			else if (data > pCur->_data) // 大于向右走
				pCur = pCur->_pRight;
			else // 小于向左走
				pCur = pCur->_pLeft;
		}

		pCur = new Node(data);
		// 更新父节点的子节点指针
		if (data < pParent->_data)
			pParent->_pLeft = pCur;
		else
			pParent->_pRight = pCur;
		pCur->_pParent = pParent;
		while (pParent && pParent->_color == RED)
		{
			Node* pGrandpa = pParent->_pParent;
			if (!pGrandpa)
				pParent->_color = BLACK;
			else {
				if (pParent == pGrandpa->_pRight) 
				{
					Node* pUncle = pGrandpa->_pLeft;
					// uncle存在且为红
					if (pUncle && pUncle->_color == RED)
					{
						pUncle->_color = pParent->_color = BLACK;
						pGrandpa->_color = RED;
						// 继续向上调整
						pCur = pGrandpa;
						pParent = pCur->_pParent;
					}
					// uncle不存在或者uncle为黑
					else
					{
						// 右右的情况
						if (pCur == pParent->_pRight)
						{
							RotateL(pGrandpa);
							pParent->_color = BLACK;
							pGrandpa->_color = RED;
						}
						// 右左的情况
						else
						{
							RotateR(pParent);
							RotateL(pGrandpa);
							pCur->_color = BLACK;
							pGrandpa->_color = RED;
						}
						break;
					}
				}
				else // pParent = pGrandpa->_pLeft
				{
					Node* pUncle = pGrandpa->_pRight;
					// uncle存在且为红
					if (pUncle && pUncle->_color == RED)
					{
						pUncle->_color = pParent->_color = BLACK;
						pGrandpa->_color = RED;
						// 继续向上调整
						pCur = pGrandpa;
						pParent = pCur->_pParent;
					}
					// uncle不存在或者uncle为黑
					else
					{
						// 左左的情况
						if (pCur == pParent->_pLeft)
						{
							RotateR(pGrandpa);
							pParent->_color = BLACK;
							pGrandpa->_color = RED;
						}
						// 左右的情况
						else
						{
							RotateL(pParent);
							RotateR(pGrandpa);
							pCur->_color = BLACK;
							pGrandpa->_color = RED;
						}
						break;
					}
				}
			}
		}
		_pRoot->_color = BLACK;
		return true;
	}


	// 检测红黑树中是否存在值为data的节点,存在返回该节点的地址,否则返回nullptr
	Node* Find(const T& data)
	{
		Node* pCur = _pRoot;
		while (pCur)
		{
			if (data == pCur->_data)
				return pCur;
			if (data > pCur->_data)
				pCur = pCur->_pRight;
			else
				pCur = pCur->_pLeft;
		}
		return nullptr;
	}

	// 检测红黑树是否为有效的红黑树,注意:其内部主要依靠_IsValidRBTRee函数检测
	bool IsValidRBTRee()
	{
		if (!_pRoot)
			return true;
		if (RED == _pRoot->_color)
			return false;
		Node* pCur = _pRoot;
		size_t blackcount = 0;
		while (pCur)
		{
			if (BLACK == pCur->_color)
				++blackcount;
			pCur = pCur->_pLeft;
		}
		return _IsValidRBTRee(_pRoot, blackcount, 0);
	}

private:
	bool _IsValidRBTRee(Node* pRoot, size_t blackCount, size_t pathBlack) {
		if (!pRoot) {
			// 如果到达叶子节点,检查黑色节点数量
			return pathBlack == blackCount;
		}
		// 检查当前节点的颜色
		if (pRoot->_color == RED) {
			// 如果是红色,检查父节点是否也是红色
			if (pRoot->_pParent && pRoot->_pParent->_color == RED) {
				return false; // 违反红黑树性质
			}
		}
		// 更新路径上的黑色节点数量
		if (pRoot->_color == BLACK) {
			pathBlack++;
		}
		// 递归检查左子树和右子树
		return _IsValidRBTRee(pRoot->_pLeft, blackCount, pathBlack) &&
			_IsValidRBTRee(pRoot->_pRight, blackCount, pathBlack);
	}

	// 左单旋
	void RotateL(Node* pParent)
	{
		Node* pCur = pParent->_pRight;
		pParent->_pRight = pCur->_pLeft; // cur的左孩子作为parent的右孩子
		if (pCur->_pLeft)
			pCur->_pLeft->_pParent = pParent;
		if (pParent->_pParent) // parent不是根节点
		{
			pCur->_pParent = pParent->_pParent;
			if (pParent->_pParent->_pLeft == pParent)
				pParent->_pParent->_pLeft = pCur;
			else
				pParent->_pParent->_pRight = pCur;
		}
		else // parent是根节点
		{
			_pRoot = pCur;
			pCur->_pParent = nullptr;
		}
		pParent->_pParent = pCur;
		pCur->_pLeft = pParent;
	}

	// 右单旋
	void RotateR(Node* pParent)
	{
		Node* pCur = pParent->_pLeft;
		pParent->_pLeft = pCur->_pRight; // cur的右孩子作为parent的左孩子
		if (pCur->_pRight)
			pCur->_pRight->_pParent = pParent;
		if (pParent->_pParent) // parent不是根节点
		{
			pCur->_pParent = pParent->_pParent;
			if (pParent->_pParent->_pLeft == pParent)
				pParent->_pParent->_pLeft = pCur;
			else
				pParent->_pParent->_pRight = pCur;
		}
		else // parent是根节点
		{
			_pRoot = pCur;
			pCur->_pParent = nullptr;
		}
		pParent->_pParent = pCur;
		pCur->_pRight = pParent;
	}

private:
	Node* _pRoot;
};

//test.cpp
#define _CRT_SECURE_NO_WARNINGS 1
    #include<iostream>
    using namespace std;
    #include"My_RBTree.h"
    #include<vector>


    bool test1()
    {
        srand(time(0));  // 初始化随机种子

    // 生成10000组随机向量
        for (int i = 0; i < 10000; ++i) {
            RBTree<int> BT;
            vector<int> nums;

            // 每组随机生成10个整数(范围为 1 到 100)
            for (int j = 0; j < 10; ++j) {
                nums.push_back(rand() % 100 + 1);
            }

            // 将每个整数插入 B 树中
            for (const int& num : nums) {
                BT.Insert(num);
            }

            // 检查 B 树
            if (!BT.IsValidRBTRee()) {
                cout << "树不平衡,测试失败!\n";
                for (auto it : nums)
                {
                    cout << it << ", ";
                }
                cout << endl;
                return 1;
            }
        }

        cout << "所有10000组随机树均平衡,测试通过!\n";
    }

    void test2()
    {
	    RBTree<int> BT1;
	    vector<int> v1 = { 70,25,97,18,51,85 };
	    for (auto it : v1)
	    {

		    BT1.Insert(it);
	    }
	    cout << BT1.IsValidRBTRee() << endl;
    }

    int main()
    {
	    test1();
	    return 0;
    }

以上就是红黑树的详解与模拟实现过程,欢迎在评论区留言,码文不易,觉得这篇博客对你有帮助的,可以点赞收藏关注支持一波~

标签:color,红黑树,pRoot,C++,pCur,pParent,搞懂,data,节点
From: https://blog.csdn.net/dhgiuyawhiudwqha/article/details/143210484

相关文章

  • 计算机毕业设计项目推荐,个人知识管理系统 79004(开题答辩+程序定制+全套文案 )上万套实
    摘 要尽管我们每天面临的信息越来越多,信息过载与信息噪音越来越严重,但只要我们能充分利用个人知识管理技能,借助有效的个人知识管理软件相信战胜海量信息不再是困难。本课题在分析了个人知识管理现状以及对现有的个人知识管理网站进行研究比较的基础上,针对网络交流互助的特......
  • 一文搞懂促销系统架构设计
    大家好,我是汤师爷~到了技术二面,面试官基本都会开始深挖你之前做过的项目。比如:这个项目的业务背景是什么?你们的系统架构是怎么设计的?你在这个项目里具体做了哪些工作?有没有什么特别亮眼的地方?所以,能不能把参与的项目整体弄明白、说清楚,非常重要。你对项目掌握越全面,面试官才会......
  • C++Socket通讯样例(服务端)
    1.创建Socket实例并开启。privateintOpenTcp(intport,stringip=""){//1.开启服务端try{_tcpServer=newSocket(AddressFamily.InterNetwork,SocketType.Stream,ProtocolType.Tcp);IPAddressipAddr=IPAddress.Any;......
  • C++11新特性:lambda表达式
    lambda表达式目录lambda表达式常见lambda表达式的省略式值的捕获lambda表达式的类型使用场景简述附:lambda的常量性......
  • 20个基础到进阶版的OpenCV4.9.0趣味项目(C++版)(八)——石头、剪刀、布识别手势识别(传统方
    20个基础到进阶版的OpenCV4.9.0趣味项目(C++版)(八)——石头、剪刀、布识别手势识别(传统方法)文章目录20个基础到进阶版的OpenCV4.9.0趣味项目(C++版)(八)——石头、剪刀、布识别手势识别(传统方法)一、引言二、核心知识1.YCrCb空间转换和提取1)YCrCb色彩空间:2)分割:2.凸包凸包计算......
  • 大话C++:第23篇 输入输出
    1输入输出概述C++输入输出(I/O)是C++编程语言中非常重要的一部分,它涉及到从外部设备(如键盘、文件等)读取数据以及将数据写入到这些设备中。C++提供了一套丰富的I/O库,程序员可以使用这些库来执行各种输入输出操作。C++的I/O操作主要依赖于<iostream>头文件,它定义了用于输入输出......
  • Qt/C++路径轨迹回放/回放每个点信号/回放结束信号/拿到移动的坐标点经纬度
    一、前言说明在使用百度地图的路书功能中,并没有提供移动的信号以及移动结束的信号,但是很多时候都期望拿到移动的哪里了以及移动结束的信号,以便做出对应的处理,比如结束后需要触发一些对应的操作。经过搜索发现很多人都有这个需求,需要在js文件中加上一点代码才行,也就是在start开始......
  • [C++]在windows基于C++编程署yolov11-pose的openvino姿态估计模型cmake项目部署演示源
    【算法介绍】在Windows系统上,基于C++编程部署YOLOv11-Pose的OpenVINO姿态估计模型,可以通过CMake项目来实现。以下是简要介绍:首先,需要准备开发环境,包括安装OpenVINOToolkit、CMake、OpenCV和C++编译器(如GCC或MSVC)。OpenVINO是英特尔开发的一款用于优化和部署深度学习模型的工具套件......
  • [C++]在windows基于C++编程署yolov11-cls的openvino图像分类模型cmake项目部署演示源
    【算法介绍】在Windows系统上,基于C++编程部署YOLOv11-CLS的OpenVINO图像分类模型,可以通过CMake项目来实现。以下是简要介绍:首先,需要准备开发环境,包括安装OpenVINOToolkit、CMake、OpenCV和C++编译器(如GCC或MSVC)。OpenVINO是英特尔开发的一款用于优化和部署深度学习模型的工具套件,......
  • C++11 中的内存对齐:alignas 与 alignof
    alignas和alignof是C++11引入的两个关键字,它们与内存对齐相关,帮助开发者控制和查询数据的内存对齐方式。内存对齐可以提高访问数据时的性能,特别是在处理硬件层面要求严格的场景下。1.alignasalignas是一个声明说明符,用来设置类型或对象的对齐方式。它允许开发者显式指定......