【C++入门到精通】C++入门 —— 红黑树(自平衡二叉搜索树)

news2025/2/26 1:56:52

在这里插入图片描述

阅读导航

  • 前言
  • 一、红黑树的概念
  • 二、红黑树的性质
  • 三、红黑树节点的定义
  • 四、红黑树结构(带有头结点的红黑树)
  • 五、红黑树的插入操作
    • 1. 按照二叉搜索的树规则插入新节点
    • 2. 新节点插入后,红黑树的变色以及旋转
      • 情况1: cur为红,p为红,g为黑,u存在且为红
      • 情况2:cur为红,p为红,g为黑,u不存在或者u存在且为黑(单旋)
    • ⭕插入代码
  • 六、红黑树的删除
    • 1.被删除节点无子节点
    • 2.被删除节点只有一个子节点
    • 3.被删除节点有两个子节点
  • 七、红黑树与AVL树的比较
  • 八、红黑树的应用
  • 附:详细红黑树模拟代码
  • 温馨提示

前言

前面我们讲了C语言的基础知识,也了解了一些初阶数据结构,并且讲了有关C++的命名空间的一些知识点以及关于C++的缺省参数、函数重载,引用 和 内联函数也认识了什么是类和对象以及怎么去new一个 ‘对象’ ,也了解了C++中的模版,以及学习了几个STL的结构,也了解了搜索二叉树的基本原理,以及学习了数据结构的大哥AVL树相信大家都掌握的不错,接下来博主将会带领大家继续学习有关C++比较重要的知识点—— 红黑树(自平衡二叉搜索树) 。下面话不多说坐稳扶好咱们要开车了😍

一、红黑树的概念

红黑树(Red-Black Tree)是一种自平衡的二叉查找树,红黑树之所以称为"红黑",是因为每个节点上都有一个颜色属性,可以是红色或黑色。 通过对任何一条从根到叶子的路径上各个结点着色方式的限制,红黑树确保没有一条路径会比其他路径长出俩倍,因而是接近平衡的。
在这里插入图片描述

二、红黑树的性质

  1. 每个节点都有一个颜色属性,要么是红色,要么是黑色。
  2. 根节点是黑色的。
  3. 所有叶子节点(NIL节点)都是黑色的。
  4. 如果一个节点是红色的,则它的两个子节点都是黑色的。
  5. 对于每个节点,从该节点到其所有后代叶子节点的简单路径上,均包含相同数量的黑色节点。

🚨🚨注意:NIL节点是一种特殊的节点,表示空节点或叶子节点。它被视为黑色节点,并且没有实际存储的值

三、红黑树节点的定义

// 颜色枚举类型,表示节点颜色属性
enum Colour
{
    RED, // 红色
    BLACK, // 黑色
};

// 红黑树节点结构体模板
template<class T>
struct RBTreeNode
{
    RBTreeNode<T>* _left; // 左子节点指针
    RBTreeNode<T>* _right; // 右子节点指针
    RBTreeNode<T>* _parent; // 父节点指针
    T _data; // 节点数据
    Colour _col; // 节点颜色属性,取值为 RED 或 BLACK

    // 构造函数,初始化节点的左右子节点、父节点和颜色属性
    RBTreeNode(const T& data)
        :_left(nullptr)
        , _right(nullptr)
        , _parent(nullptr)
        , _data(data)
        , _col(RED) // 新插入节点的颜色总是红色(符合红黑树性质)
    {}
};

⭕它表示红黑树中的一个节点。该结构体包含以下成员:

  • _left:指向左子节点的指针。
  • _right:指向右子节点的指针。
  • _parent:指向父节点的指针。
  • _data:存储节点的数据。
  • _col:标识节点的颜色属性,可以是 RED(红色)或 BLACK(黑色)。

结构体模板还有一个构造函数,用于初始化节点的各个成员。构造函数接受一个参数 data,表示要存储在节点中的数据。构造函数会将左右子节点指针、父节点指针和节点颜色属性初始化为默认值,其中新插入的节点颜色总是红色。

红黑树的每个节点都是通过该结构体创建的实例,通过连接不同节点的指针,可以构建出红黑树的结构。

四、红黑树结构(带有头结点的红黑树)

在这里插入图片描述

带有头结点的红黑树是在普通红黑树的基础上添加了一个额外的头结点(也称为哨兵节点),用于简化红黑树的操作和边界情况的处理。**头结点通常被设置为黑色并且不存储任何实际数据。**具体来说,当红黑树为空时,根节点指针指向头结点,而不是空指针。这样,在对红黑树进行插入、删除等操作时,可以直接操作根节点,无需检查根节点是否为空。头结点的左子节点指向实际的根节点,而右子节点指向树中的最大节点(即最右侧的节点)。这样,通过头结点可以方便地访问到根节点以及整个树的边界。

总之,带有头结点的红黑树通过引入额外的头结点,简化了红黑树的操作和边界情况的处理,提高了代码的可读性和可维护性。

五、红黑树的插入操作

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

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);
    // 进行变换操作使树满足红黑树性质
    // ... 缺少变换操作的代码 ...

    // 插入成功
    return true;
}

cur为新插入的节点,接下来针对这一个节点进行讨论变换(颜色)就可以了

2. 新节点插入后,红黑树的变色以及旋转

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

🚨🚨约定:cur为新插入节点,p为父节点,g为祖父节点,u为叔叔节点

情况1: cur为红,p为红,g为黑,u存在且为红

如果g节点,调整完成后,需要将g改为黑色
在这里插入图片描述
如果g是子树,g一定有双亲,如果g的双亲是红色,需要继续向上调整
在这里插入图片描述
🍁解决方式:将p,u改为黑,g改为红,然后把g当成cur,继续向上调整

情况2:cur为红,p为红,g为黑,u不存在或者u存在且为黑(单旋)

在这里插入图片描述

  1. 如果u节点不存在,则cur一定是新插入节点,因为如果cur不是新插入节点则cur和p一定有一个节点的颜色是黑色,就不满足性质4: 每条路径黑色节点个数相同。
  2. 如果u节点存在,则其一定是黑色的,那么cur节点原来的颜色一定是黑色的现在看到其是红色的原因是因为cur的子树在调整的过程中将cur节点的颜色由黑色改成红色。

🍁解决办法

  • p为g的左孩子,cur为p的左孩子,则进行右单旋转;
  • p为g的右孩子,cur为p的右孩子,则进行左单旋转
  • p、g变色–p变黑,g变红

⭕插入代码

	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);
		//cur为新插入的节点,接下来针对这一个节点进行讨论变换(颜色)就可以了

		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 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;
		}
	}

六、红黑树的删除

1.被删除节点无子节点

  • 如果待删除节点是叶子节点,则直接删除该节点即可。
  • 如果待删除节点是根节点,则删除根节点并将根节点设置为 null。

2.被删除节点只有一个子节点

  • 如果待删除节点只有一个子节点,可以用其子节点来替代被删除的节点,并保持红黑树的性质。
  • 如果待删除节点是红色节点,则用其子节点来代替该节点,并不会影响树的性质;
  • 如果待删除节点是黑色节点,则需要根据其子节点和父节点的颜色组合,进行颜色变换并进行旋转操作,使树重新满足红黑树的性质。

3.被删除节点有两个子节点

如果待删除节点有两个子节点,需要先寻找其后继节点(即右子树中最小的节点或左子树中最大的节点),将其替换到待删除节点的位置,然后再删除该后继节点。

在实现过程中,需要注意以下几点:

  • 对于黑色节点的处理,需要进行额外的操作,以保证树的性质;
  • 在红黑树中,每个节点都带有颜色属性,因此需要在代码中对颜色进行判断和处理;
  • 删除节点或替换节点后,重新插入到树上的节点也需要进行变换操作,以保证树的性质。

下面是一个实现删除红黑树节点的代码示例:

bool Remove(const K& key)
{
    // 查找要删除的节点
    pair<Node*, bool> res = Find(key);
    if (!res.second)
    {
        return false;  // 没有找到要删除的节点
    }

    Node* cur = res.first;
    if (cur->_left && cur->_right)  // 待删除节点有两个子节点
    {
        Node* next = cur->_right;
        while (next->_left)  // 找到待删除节点的后继节点
        {
            next = next->_left;
        }
        cur->_kv = next->_kv;  // 将后继节点的值赋给待删除节点
        cur = next;  // 准备删除后继节点
    }

    Node* child = cur->_left ? cur->_left : cur->_right;  // 获取待删除节点的唯一子节点
    if (cur->_col == BLACK)  // 如果待删除节点为黑色节点,则需要进行额外的操作
    {
        cur->_col = child ? child->_col : RED;  // 将待删除节点的颜色赋给其子节点(若无子节点则为红色)
        FixUp(cur);  // 进行变换操作,维护树的性质
    }
    Replace(cur, child);  // 删除节点

    return true;
}

在该示例中,Find 函数用于查找要删除的节点,如果未找到则返回 false。如果待删除节点有两个子节点,则需要寻找其后继节点,并将后继节点的值赋给待删除节点。然后,判断待删除节点的颜色,如果为黑色节点,则需要进行额外的操作以维护树的性质。最后,调用 Replace 函数删除节点,并返回 true 表示删除成功。

七、红黑树与AVL树的比较

  1. 平衡性:

    • 红黑树:红黑树是一种弱平衡树,它通过满足一组红黑树性质来保证整个树的大致平衡。这些性质包括节点的颜色、路径上的黑色节点数量等。
    • AVL树:AVL树是一种严格平衡树,它要求任何节点的左右子树的高度差(平衡因子)不超过1。这种严格的平衡要求导致在插入和删除节点时需要更频繁地进行旋转操作。
  2. 性能:

    • 插入和删除操作:
      • 红黑树:由于红黑树是一种弱平衡树,它的调整操作相对较少,因此插入和删除节点的操作速度通常比较快。
      • AVL树:AVL树的平衡要求非常严格,插入和删除节点时可能需要进行多次旋转操作来维持平衡,因此这些操作相对较慢。
    • 查询操作:
      • 红黑树和AVL树在查询操作上具有相似的性能,都能在平均情况下以O(log n)的时间复杂度进行查找。
  3. 存储空间:

    • 红黑树:红黑树需要一个额外的位来存储节点颜色信息,所以相对于普通的二叉搜索树会占用更多的存储空间。
    • AVL树:AVL树不需要额外的存储空间来维护平衡,所以在存储空间上相对较高效。

🚨🚨注意:红黑树适用于插入和删除操作较频繁的场景,而AVL树适用于对平衡性要求更高的场景

八、红黑树的应用

红黑树是一种高效的平衡二叉搜索树,具有较好的查找、插入和删除性能。因此,它在许多方面都有广泛的应用。

  1. C++ STL中的map和set容器:STL库中的map和set容器都是基于红黑树实现的,它们提供了快速的键值查找和排序的功能。

  2. 数据库索引结构:许多数据库系统采用B+树或B树作为索引结构,但也有一些数据库系统采用红黑树作为索引结构来加速数据的查找。

  3. 操作系统调度:操作系统CPU调度算法中也使用了红黑树。例如,在Linux内核中,用红黑树来维护进程的优先级队列。

  4. 编译器标识符表:编译器在分析代码时需要维护一个标识符表来存放变量、函数等信息。红黑树可以作为标识符表的底层数据结构,提高符号查找的效率。

  5. 计算机网络路由表:路由表是计算机网络中进行数据包转发的重要组成部分之一。为了快速查找路由路径,许多路由器会使用红黑树等数据结构来维护路由表。

附:详细红黑树模拟代码

#pragma once
enum Colour
{
	RED,
	BLACK,
};

template<class T>
struct RBTreeNode
{
	RBTreeNode<T>* _left;
	RBTreeNode<T>* _right;
	RBTreeNode<T>* _parent;
	T _data;
	Colour _col;

	RBTreeNode(const T& data)
		:_left(nullptr)
		, _right(nullptr)
		, _parent(nullptr)
		, _data(data)
		, _col(RED)
	{}
};

template<class K, class T, class KeyOfT>
class RBTree
{
	typedef RBTreeNode<T> 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);
		//cur为新插入的节点,接下来针对这一个节点进行讨论变换(颜色)就可以了

		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;
		}
	}

private:
	Node* _root = nullptr;
};

温馨提示

感谢您对博主文章的关注与支持!另外,我计划在未来的更新中持续探讨与本文相关的内容,会为您带来更多关于C++以及编程技术问题的深入解析、应用案例和趣味玩法等。请继续关注博主的更新,不要错过任何精彩内容!

再次感谢您的支持和关注。期待与您建立更紧密的互动,共同探索C++、算法和编程的奥秘。祝您生活愉快,排便顺畅!
在这里插入图片描述

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

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

相关文章

nginx-proxy反向代理缓存

介绍&#xff1a; 反向代理缓存&#xff0c;类似于动静分离&#xff0c;即通过nginx代理服务器根据客户端发送的url请求&#xff0c;去后台服务器获取数据&#xff0c;将静态数据缓存到nginx代理服务器上&#xff0c;并配置有过期时间&#xff0c;当客户端下次以相同的url请求…

【机器学习】决策树原理及scikit-learn使用

文章目录 决策树详解ID3 算法C4.5算法CART 算法 scikit-learn使用分类树剪枝参数重要属性和接口 回归树重要参数&#xff0c;属性及接口交叉验证代码示例 一维回归的图像绘制 决策树详解 决策树&#xff08;Decision Tree&#xff09;是一种非参数的有监督学习方法&#xff0c;…

blender 之视频渲染(以三维重建path为例)

blender 之视频渲染&#xff08;以三维重建path为例&#xff09; 1.新建轨迹路径2.设置相机&#xff0c;使其按照path运动3.将相机视角对准物体4.修改帧率5.设置输出路径6.设置输出格式7.渲染 1.新建轨迹路径 新建轨迹 选中新建的BezierCycle&#xff0c;按住S&#xff0c;拖…

基于支持向量机SVM和MLP多层感知神经网络的数据预测matlab仿真

目录 1.算法运行效果图预览 2.算法运行软件版本 3.部分核心程序 4.算法理论概述 一、支持向量机&#xff08;SVM&#xff09; 二、多层感知器&#xff08;MLP&#xff09; 5.算法完整程序工程 1.算法运行效果图预览 2.算法运行软件版本 matlab2022a 3.部分核心程序 .…

mysql面试题23:如果某个表有近千万数据,CRUD比较慢,如何优化?

该文章专注于面试,面试只要回答关键点即可,不需要对框架有非常深入的回答,如果你想应付面试,是足够了,抓住关键点 面试官:如果某个表有近千万数据,CRUD比较慢,如何优化? 当某个表存在近千万数据且CRUD(增删改查)操作比较慢时,可以考虑以下优化策略: 使用索引:索…

3.1.OpenCV技能树--二值图像处理--图像腐蚀与膨胀

文章目录 1.文章内容来源2.图像膨胀处理2.1.图像膨胀原理简介2.2.图像膨胀核心代码2.3.图像膨胀效果展示 3.图像腐蚀处理3.1.图像腐蚀原理简介3.2.图像腐蚀核心代码3.3.图像腐蚀效果展示 4.易错点总结与反思 1.文章内容来源 1.题目来源:https://edu.csdn.net/skill/practice/o…

LVS+Keepalived 高可用集群负载均衡

一.keepalived介绍 1.1.Keepalived实现原理 由多台路由器组成一个热备组&#xff0c;通过共用的虚拟IP地址对外提供服务。 每个热备组内同时只有一台主路由器提供服务&#xff0c;其他路由器处于冗余状态。 若当前在线的路由器失效&#xff0c;则其他路由器会根据设置…

(论文调研) Multi-task的网络结构 在图像去噪问题中的应用

1.SNIDER: Single Noisy Image Denoising and Rectification for Improving License Plate Recognition 这是一篇用于实现端到端的车牌恢复 (LPR: License Plate Recognition) 网络, 其中使用去噪和校正网络来生成清晰的恢复图像, 以实现稳健的 LPR 性能. 这个网络的名称为SN…

内网渗透笔记之内网基础知识

0x01 内网概述 内网也指局域网&#xff08;Local Area Network&#xff0c;LAN&#xff09;是指在某一区域内由多台计算机互联成的计算机组。一般是方圆几千米以内。局域网可以实现文件管理、应用软件共享、打印机共享、工作组内的历程安排、电子邮件和传真通信服务等功能。 内…

Maven(4)-利用intellij idea创建maven 多模块项目

本文通过一个例子来介绍利用maven来构建一个多模块的jave项目。开发工具&#xff1a;intellij idea。 一、项目结构 multi-module-project是主工程&#xff0c;里面包含两个模块&#xff08;Module&#xff09;&#xff1a; web-app是应用层&#xff0c;用于界面展示&#xff…

基于瞬时无功功率ip-iq的谐波信号检测Simulink仿真

&#x1f4a5;&#x1f4a5;&#x1f49e;&#x1f49e;欢迎来到本博客❤️❤️&#x1f4a5;&#x1f4a5; &#x1f3c6;博主优势&#xff1a;&#x1f31e;&#x1f31e;&#x1f31e;博客内容尽量做到思维缜密&#xff0c;逻辑清晰&#xff0c;为了方便读者。 ⛳️座右铭&a…

nodejs+vue快递管理服务系统elementui

电子商务改变了人们的传统经济活动中的交易方式和流通技术&#xff0c; 复杂的物流快递信息需要有效的进行处理&#xff0c;构建一个快递管理服务系统可以确保物流信息的一致性、员工登录&#xff1a;通过用户名和密码登录。这也间接带动了物流快递行业的高速发展。 &#xff0…

flutter开发实战-inappwebview实现flutter与Javascript方法调用

flutter开发实战-inappwebview实现flutter与Javascript方法调用 在使用inappwebview时候&#xff0c;需要flutter端与JS进行交互&#xff0c;调用相应的方法&#xff0c;在inappwebview中的JavaScript Handlers。 一、JavaScript Handlers 要添加JavaScript Handlers&#…

基于FPGA的图像形态学腐蚀算法实现,包括tb测试文件和MATLAB辅助验证

目录 1.算法运行效果图预览 2.算法运行软件版本 3.部分核心程序 4.算法理论概述 5.算法完整程序工程 1.算法运行效果图预览 将FPGA的仿真结果导入到MATLAB,结果如下所示&#xff1a; 2.算法运行软件版本 vivado2019.2 matlab2022a 3.部分核心程序 timescale 1ns / 1ps…

界面组件DevExpress WinForms v23.1 - TreeList、UI模板全新升级

DevExpress WinForms拥有180组件和UI库&#xff0c;能为Windows Forms平台创建具有影响力的业务解决方案。DevExpress WinForms能完美构建流畅、美观且易于使用的应用程序&#xff0c;无论是Office风格的界面&#xff0c;还是分析处理大批量的业务数据&#xff0c;它都能轻松胜…

cpu的指令重排,禁用办法

上一篇博客 volatile关键字总结-CSDN博客&#xff0c;我们讲了两个volatile变量可以限制编译器对指令的重排&#xff0c;但是限制不了cpu层面的指令重排 这篇博客开始&#xff0c;我们演示下cpu层面的指令重排&#xff0c;以及如何禁止cpu层面的指令重排 实验一&#xff1a;复…

SpringBoot的error用全局异常去处理

记录一下使用SpringBoot2.0.5的error用全局异常去处理 在使用springboot时&#xff0c;当访问的http地址或者说是请求地址输错后&#xff0c;会返回一个页面&#xff0c;如下&#xff1a; 这是因为请求的地址不存在&#xff0c;默认会显示error页面 但我们实际需要一个接口&a…

lua 中文字符的判断简介

一般在工作中会遇到中文字符的判断、截断、打码等需求&#xff0c;之前一直没有总结&#xff0c;虽然网上资料也多&#xff0c;今天在这里简单的总结一下。 1 .UTF-8简单描述 UTF-8 是 Unicode 的实现方式之一&#xff0c;其对应关系&#xff08;编码规则&#xff09;如下表所…

计算机算法分析与设计(6)---最长公共子序列(含C++代码)

文章目录 一、知识概述1.1 最长公共子序列1.2 进行动态规划 二、例题分析2.1 例题12.2 例题2 三、代码3.1 完整代码3.2 代码探讨 一、知识概述 1.1 最长公共子序列 1. 若给定序列X{ x 1 x_1 x1​&#xff0c; x 2 x_2 x2​&#xff0c;…&#xff0c; x m x_m xm​}&#xff0…

三相PWM整流器滞环电流控制Simulink仿真模型

&#x1f4a5;&#x1f4a5;&#x1f49e;&#x1f49e;欢迎来到本博客❤️❤️&#x1f4a5;&#x1f4a5; &#x1f3c6;博主优势&#xff1a;&#x1f31e;&#x1f31e;&#x1f31e;博客内容尽量做到思维缜密&#xff0c;逻辑清晰&#xff0c;为了方便读者。 ⛳️座右铭&a…