【C++】AVL树和红黑树(插入和测试详解)

news2024/10/3 0:21:33

文章目录

    • 1、AVL树
      • 1.1 AVL树的插入
      • 1.2 总结与测试AVL树
    • 2、红黑树
      • 2.1 红黑树的插入
      • 2.2 红黑树的测试

了解AVL树是为了了解红黑树,了解红黑树是为了更好的理解set和map。

1、AVL树

AVL树是在二叉搜索树的基础上进行了严格的平衡,能做到平衡的关键是通过平衡因子以及旋转。
AVL树有以下特性:

  1. 任何根的左右子树高度之差(简称平衡因子)的绝对值不超过1(-1/0/1)。
  2. 其中平衡因子是用右子树高度减去左子树高度。
  3. 任何子树都是AVL树。

在这里插入图片描述

下面实现的AVL树还是KV结构的。

AVL树节点定义

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

template<class K, class V>
struct AVLTreeNode
{
	//三叉链结构方便访问父节点
	struct AVLTreeNode<K, V>* _left;
	struct AVLTreeNode<K, V>* _right;
	struct AVLTreeNode<K, V>* _parent;
	pair<K, V> _kv; //键值对 里面存储key和value
	int _bf; //平衡因子

	AVLTreeNode(const pair<K, V>& kv)
		:_left(nullptr)
		,_right(nullptr)
		,_parent(nullptr)
		,_kv(kv)
		,_bf(0)
};

template<class K, class V>
class AVLTree
{
	typedef AVLTreeNode<K, V> Node;
public:

private:
	Node* root = nullptr;
};

1.1 AVL树的插入

1、AVL树的插入首先一开始和二叉搜索树的插入一样,先确定插入的位置,再和父节点链接。

2、插入完后,可能会破坏AVL树结构,所以要判断平衡因子。
插入新结点后,平衡因子会出现三种情况。
在这里插入图片描述

3、当平衡因子出现了-2或2的情况,这个时候就需要对parent进行旋转。
旋转有以下情况。
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

bool insert(const pair<K, V>& kv)
	{
		if (_root == nullptr)
		{
			_root = new Node(kv);
			return true;
		}
		Node* cur = _root;
		Node* parent = _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);
		//只能用kv值来确定parent和cur的指向
		if (parent->_kv.first < kv.first)
		{
			parent->_right = cur;
			cur->_parent = parent;
		}
		else
		{
			parent->_left = cur;
			cur->_parent = parent;
		}
		
		//判断平衡因子
		while (parent)
		{
			if (parent->_left == cur)
			{
				//根节点左边插入节点,根的平衡因子-1
				parent->_bf--;
			}
			else
			{
				//根节点右边插入节点,根的平衡因子+1
				parent->_bf++;
			}

			//说明之前是-1或1,变为平衡
			if (parent->_bf == 0)
			{
				break;
			}
			else if (parent->_bf == 1 || parent->_bf == -1)
			{
				//下面子树高度差也影响了上面的根结点,所以需要向上调整
				cur = parent;
				parent = cur->_parent;
			}
			else if (parent->_bf == 2 || parent->_bf == -2)
			{
				//这个时候就需要旋转,使得树平衡
				if (parent->_bf == -2 && cur->_bf == -1)
				{
					//这是右旋转的情况
					RotateR(parent);
				}
				else if (parent->_bf == 2 && cur->_bf == 1)
				{
					RotateL(parent);
				}
				else if (parent->_bf == -2 && cur->_bf == 1)
				{
					RotateLR(parent);
				}
				else if (parent->_bf == 2 && cur->_bf == -1)
				{
					RotateRL(parent);
				}
				else
				{
					assert(false);
				}
				
				//旋转完后,结构平衡,退出
				break;

			}
			else
			{
				//如果平衡因子出现其它情况,说明错了
				assert(false);
			}
		}//while

		return true;
	}

旋转:

	//右旋转
	void RotateR(Node* parent)
	{
		//从下到上依次修改
		Node* sub = parent->_left;
		Node* subR = sub->_right;
		
		//先改变最下面的subR结点
		parent->_left = subR;
		if (subR)
		{
			subR->_parent = parent;
		}
		
		//再改变parent结点
		sub->_right = parent;
		Node* ppnode = parent->_parent;
		parent->_parent = sub;
		
		//最后改变sub结点
		if (ppnode == nullptr)
		{
			_root = sub;
			_root->_parent = nullptr;
		}
		else
		{
			if (ppnode->_left == parent)
			{
				ppnode->_left = sub;
			}
			else
			{
				ppnode->_right = sub;
			}
			sub->_parent = ppnode;
		}

		parent->_bf = sub->_bf = 0;

	}
	
	//左旋和右旋类似
	void RotateL(Node* parent)
	{
		Node* sub = parent->_right;
		Node* subL = sub->_left;

		parent->_right = subL;
		if (subL)
		{
			subL->_parent = parent;
		}
		sub->_left = parent;
		Node* ppnode = parent->_parent;
		parent->_parent = sub;

		if (ppnode == nullptr)
		{
			_root = sub;
			_root->_parent = nullptr;
		}
		else
		{
			if (ppnode->_left == parent)
			{
				ppnode->_left = sub;
			}
			else
			{
				ppnode->_right = sub;
			}
			sub->_parent = ppnode;
		}

		parent->_bf = sub->_bf = 0;

	}

	void RotateLR(Node* parent)
	{
		Node* subL = parent->_left;
		Node* subLR = subL->_right;
		//保存subLR的平衡因子,为了知道从subLR哪边插入
		int bf = subLR->_bf;

		RotateL(parent->_left);
		RotateR(parent);
		if (bf == -1) // subLR左子树新增
		{
			subL->_bf = 0;
			parent->_bf = 1;
			subLR->_bf = 0;
		}
		else if (bf == 1) // subLR右子树新增
		{
			parent->_bf = 0;
			subL->_bf = -1;
			subLR->_bf = 0;
		}
		else if (bf == 0) // subLR自己就是新增
		{
			parent->_bf = 0;
			subL->_bf = 0;
			subLR->_bf = 0;
		}
		else
		{
			assert(false);
		}
	}
	
	//和上面类似
	void RotateRL(Node* parent)
	{
		Node* subR = parent->_right;
		Node* subRL = subR->_left;
		int bf = subRL->_bf;

		RotateR(parent->_right);
		RotateL(parent);
		if (bf == -1) // subRL左子树新增
		{
			subR->_bf = 1;
			parent->_bf = 0;
			subRL->_bf = 0;
		}
		else if (bf == 1) // subRL右子树新增
		{
			parent->_bf = -1;
			subR->_bf = 0;
			subRL->_bf = 0;
		}
		else if (bf == 0) // subRL自己就是新增
		{
			parent->_bf = 0;
			subR->_bf = 0;
			subRL->_bf = 0;
		}
		else
		{
			assert(false);
		}
	}

可能会有的问题解释(以下是自己的理解):

1、如何想到旋转的情况?
其实从所有情况看就这两个情况以及他们的翻转,记住它们两个就好了。
在这里插入图片描述

2、如何看左右旋?
拿上图举例,左边是一个右旋能平衡的场景,只需要将最高的结点往右放就行。
右边是一个双选的场景,先将中间结点左旋,就成了图左边的场景,再右旋就行。

1.2 总结与测试AVL树

AVL树重点关注的是其平衡因子和选择如何使得AVL树平衡,通过插入了解就足够了。
下面是如何测试结果是AVL树:
1、通过每个结点的左右子树的高度判断平衡因子是否符合要求。
2、通过小和大的测试用例测试是不是AVL树

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

template<class K, class V>
struct AVLTreeNode
{
	struct AVLTreeNode<K, V>* _left;
	struct AVLTreeNode<K, V>* _right;
	struct AVLTreeNode<K, V>* _parent;
	pair<K, V> _kv;
	int _bf;

	AVLTreeNode(const pair<K, V>& kv)
		:_left(nullptr)
		,_right(nullptr)
		,_parent(nullptr)
		,_kv(kv)
		,_bf(0)
	{}
};

template<class K, class V>
class AVLTree
{
	typedef AVLTreeNode<K, V> Node;
public:
	bool insert(const pair<K, V>& kv){}

	void RotateR(Node* parent){}

	void RotateL(Node* parent){}

	void RotateLR(Node* parent){}

	void RotateRL(Node* parent){}

	int Height(Node* root)
	{
		if (root == nullptr)
		{
			return 0;
		}

		int leftHight = Height(root->_left);
		int rightHight = Height(root->_right);

		return leftHight > rightHight ? leftHight + 1 : rightHight + 1;
	}

	bool IsBalanceTree()
	{
		return  _IsBalanceTree(_root);
	}

	bool _IsBalanceTree(Node* parent)
	{
		if (parent == nullptr)
		{
			return true;
		}

		int leftHight = Height(parent->_left);
		int rightHight = Height(parent->_right);

		int diff = rightHight - leftHight;
		if (diff != parent->_bf || (diff > 1 || diff < -1))
		{
			cout << "有错" << endl;
			return false;
		}

		return _IsBalanceTree(parent->_left) && _IsBalanceTree(parent->_right);

	}

	void Inorder()
	{
		_Inorder(_root);
	}

	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 arr[] = { 4, 2, 6, 1, 3, 5, 15, 7, 16, 14 };
	int arr2[] = { 16, 3, 7, 11, 9, 26, 18, 14, 15 };
	AVLTree<int, int> t;
	for (auto& e : arr)
	{
		if(e == 18)
		{	
			//调试断点
			int a = 0;
		}
		t.insert(make_pair(e, e));
		t.IsBalanceTree();
	}
	t.Inorder();
}

//没错用多数据看看能不能过
#include <cstdlib>
void test2()
{
	srand(time(NULL));
	AVLTree<int, int> t;
	for (int i = 0; i < 100000; ++i)
	{
		int num = rand() % 10000;
		t.insert(make_pair(num, num));
	}
	t.IsBalanceTree();
}

2、红黑树

AVL树因为其严格的平衡导致它因为大量的旋转导致效率相较红黑树低。

红黑树不要求严格平衡,它为每个结点加上颜色区分,使得它趋向于平衡。它有着以下规定。

  1. 根节点必须是黑色。
  2. 根节点颜色要么是红色,要么是黑色。
  3. 红色结点不能连续。(也就是如果一个结点是红的,其两个子结点都是黑的)
  4. 每条路径下的黑色结点树要一样。
  5. 叶子结点都是黑色结点(这里叶子结点代表NULL结点)

在这里插入图片描述

可能概念理解起来很抽象,我们通过代码一步步来。

首先搭建红黑树的框架。
大致和AVL树一样,只不过没有平衡因子,换成了颜色。

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

enum Color { RED, BLACK };

template<class K, class V>
struct RBTreeNode
{
	struct RBTreeNode<K, V>* _left;
	struct RBTreeNode<K, V>* _right;
	struct RBTreeNode<K, V>* _parent;
	pair<K, V> _kv;
	Color _col;

	RBTreeNode(const pair<K, V>& kv)
		:_kv(kv)
		, _left(nullptr)
		, _right(nullptr)
		, _parent(nullptr)
		, _col(BLACK)
	{}
};

template<class K, class V>
class RBTree
{
	typedef RBTreeNode<K, V> Node;
public:
private:
	Node* _root = nullptr;
};

2.1 红黑树的插入

1、首先如果根是空,新建的结点一定是黑色结点。这很好理解。
2、那么如果是后面创建的结点,是黑色还是红色呢?
 2.1 如果是黑色结点,那么想象一下,给某个路径添加一个黑色结点,使得这个路径的黑结点数量和其他路径不同,直接导致整个树不满足红黑树条件,直接破坏整个红黑树。
 2.2 如果是红色结点,最差只会出现两个红色结点相连的情况,只影响这个子树。

 所以综上选择影响最少的,选择创建红色结点。
3、插入前面和搜索树一样,得先确认插入的位置,以及和父节点链接。
4、调整红黑树在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
除此以外当然还有翻转的另一类情况。

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

		Node* cur = _root;
		Node* parent = _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->_col = RED;
		if (parent->_kv.first < cur->_kv.first)
		{
			parent->_right = cur;
			cur->_parent = parent;
		}
		else
		{
			parent->_left = cur;
			cur->_parent = parent;
		}
		
		//调整
		//parent为红,代表插入的子节点也为红,需要调整。
		while (parent && parent->_col == RED)
		{
			Node* grandparent = parent->_parent;
			//首先是第一类父亲结点在祖父结点左边,叔叔结点在右边的一类情况。
			if (parent == grandparent->_left)
			{
				Node* uncle = grandparent->_right;
				if (uncle && uncle->_col == RED)
				{
					//第一种情况 叔叔结点存在且为红色
					parent->_col = BLACK;
					uncle->_col = BLACK;
					grandparent->_col = RED;
					
					//向上调整,根节点的情况可以跑完整个调整,再设置_root->_col = BLACK;
					cur = grandparent;
					parent = cur->_parent;
				}
				else
				{
					//这一类是叔叔结点不存在以及存在为黑色
					//因为处理方法都是一样的,所以只要再区分直线型和折线型。
					if (parent->_right == cur)
					{
						//折线的情况
						RotateL(parent);
						RotateR(grandparent);
						cur->_col = BLACK;
						grandparent->_col = RED;
					}
					else
					{
						//直线的情况
						RotateR(grandparent);
						parent->_col = BLACK;
						grandparent->_col = RED;
					}
					break;
				}
			}
			else
			{
				//这一类是上面翻转,一样的处理,但注意方向
				Node* uncle = grandparent->_left;
				if (uncle && uncle->_col == RED)
				{
					parent->_col = BLACK;
					uncle->_col = BLACK;
					grandparent->_col = RED;

					cur = grandparent;
					parent = cur->_parent;
				}
				else
				{
					if (parent->_right == cur)
					{
						RotateL(grandparent);
						parent->_col = BLACK;
						grandparent->_col = RED;
					}
					else
					{
						RotateR(parent);
						RotateL(grandparent);
						cur->_col = BLACK;
						grandparent->_col = RED;
					}
					break;
				}

			}
		}
		
		//最后确保根节点为黑。
		_root->_col = BLACK;
		return true;
	}

//剩下左右旋转的代码和AVL中的一样。

如果记忆红黑树的情况?(个人方式)
首先要记得红黑树的特性,根一定是黑结点,想清楚为什么插入要插红结点,这样能更情况红黑树的特性。

像情况一的推理一样,先插入黑色的根节点,再插入红结点,层序插入直到出现问题,此时面对第一个情况,叔叔结点存在并且为红色。

然后考虑叔叔结点为黑和不存在的情况,因为要旋转,再根据AVL树中记忆的两个情况,推出除了直线型情况,还有折线型情况。

2.2 红黑树的测试

在测试中,需要判断的

1. 根要为黑结点。
2.判断父子结点不能都为红
3.确保每条路径的黑结点数相同(这里通过先计算一条路径的黑结点数,再和每一条路径比对)

bool Check(Node* proot, int count, int ref)
	{
		if (proot == nullptr)
		{
			//检查黑结点
			if (count != ref)
			{
				cout << "出现路径黑结点树不同" << endl;
				return false;
			}
			return true;
		}

		Node* parent = proot->_parent;
		if (parent && (parent->_col == RED && proot->_col == RED))
		{
			cout << "出现了连续的红结点" << endl;
			return false;
		}

		if (proot->_col == BLACK)
		{
			count += 1;
		}

		return Check(proot->_left, count, ref) && Check(proot->_right, count, ref);

	}

	bool IsRBTree()
	{
		if (_root == nullptr)
		{
			return true;
		}

		if (RED == _root->_col)
		{
			cout << "根节点不能为红色" << endl;
			return false;
		}

		int ref = 0;
		Node* checkblack = _root;
		while (checkblack)
		{
			if (BLACK == checkblack->_col)
			{
				ref++;
			}
			checkblack = checkblack->_left;
		}

		return Check(_root, 0, ref);
	}

本节完~

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

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

相关文章

【LVGL笔记】-- 贝塞尔曲线绘制

什么是贝塞尔曲线 贝塞尔曲线&#xff08;Bzier Curve&#xff0c;也被称为贝塞尔多项式&#xff08;Bzier Polynomial&#xff09;&#xff0c;是由一系列控制点&#xff08;Control Point&#xff09;所定义的一条平滑曲线。Pierre Bzier于1960年开始利用该曲线设计雷诺的车…

静态代码审计插件 snyk 使用教程

目录 1、vscode 插件安装 2、手动生成 token 3、自动分析 1、vscode 插件安装 2、手动生成 token 点击登录链接:

网站如何锁定用户,超级浏览器有办法解决吗?

随着全球开放&#xff0c;跨境电商人纷纷开启了2023年的搞钱之旅&#xff0c;很多期待着在新的一年大干一场。但前事不忘后事之师&#xff0c;2022年跨境生意全面沦陷&#xff0c;其实除了大环境的因素之外&#xff0c;还有一个很重要的原因是&#xff0c;各个平台都开始实行非…

自己实现strcpy和strlen函数

大家可能会遇到这样的题目&#xff0c;不使用C语言库函数来实现拷贝字符串和求字符串长度的功能。本文就是来详细地说明如何模拟实现这两个函数的功能。 strcpy部分 函数的参数形式char* strcpy(char*destination,const char*source)&#xff1b; 该参数说明了strcpy返回类型…

雅利安人覆灭了世界三大文明,为何单单在商朝被斩首两万?

转自&#xff1a;雅利安人覆灭了世界三大文明&#xff0c;为何单单在商朝被斩首两万&#xff1f; (baidu.com)在公元前3000年至1000年的广大时间内&#xff0c;是世界四大文明古国大放光彩的时候&#xff0c;古印度文明、古巴比伦文明、古埃及文明以及我们的古华夏&#xff0c;…

如何入侵服务器

根据中华人民共和国刑法&#xff1a; 第二百八十六条违反国家规定&#xff0c;对计算机信息系统功能进行删除、修改、增加、干扰&#xff0c;造成计算机信息系统不能正常运行&#xff0c;后果严重的&#xff0c;处五年以下有期徒刑或者拘役&#xff1b;后果特别严重的&#xff…

荧光染料Cyanine5 carboxylic acid,1032678-07-1,花青素Cy5-羧酸

Cyanine5 carboxylic acid&#xff0c;Cy5 COOH&#xff0c;Cyanine5 COOH| 花青素Cy5-羧酸&#xff0c;花青素Cyanine5羧酸 | CAS&#xff1a;1032678-07-1 | 纯度&#xff1a;95%试剂信息&#xff1a;CAS&#xff1a;1032678-07-1外观&#xff1a;深蓝粉末分子量&#xff1a;…

LeetCode 周赛 332,在套路里摸爬滚打~

本文已收录到 AndroidFamily&#xff0c;技术和职场问题&#xff0c;请关注公众号 [彭旭锐] 提问。 大家好&#xff0c;今天是 3T 选手小彭。 上周是 LeetCode 第 332 场周赛&#xff0c;你参加了吗&#xff1f;算法解题思维需要长时间锻炼&#xff0c;加入我们一起刷题吧~ 小…

2023,再转行去做软件测试还有前途吗?

近年来&#xff0c;以云计算、移动互联网、物联网、工业互联网、人工智能、大数据及区块链等新一代信息技术构建的智能化应用和产品出现爆发式增长&#xff0c;突破了传统对于软件形态的认知&#xff0c;软件形态正以各种展现方式诠释着对新型智能软件的定义。这也使得软件的质…

Java提供了哪些IO方式? NIO如何实现多路复用?

第11讲 | Java提供了哪些IO方式&#xff1f; NIO如何实现多路复用&#xff1f; IO 一直是软件开发中的核心部分之一&#xff0c;伴随着海量数据增长和分布式系统的发展&#xff0c;IO 扩展能力愈发重要。幸运的是&#xff0c;Java 平台 IO 机制经过不断完善&#xff0c;虽然在某…

项目管理工具dhtmlxGantt甘特图入门教程(十):服务器端数据集成(上)

这篇文章给大家讲解如何利用dhtmlxGantt在服务器端集成数据。 dhtmlxGantt是用于跨浏览器和跨平台应用程序的功能齐全的Gantt图表&#xff0c;可满足应用程序的所有需求&#xff0c;是完善的甘特图图表库 DhtmlxGantt正版试用下载&#xff08;qun&#xff1a;764148812&#…

考前梳理:PMP®备考之敏捷实践中的五大事件

今天为大家总结了PMP敏捷实践中的五大事件&#xff0c;帮助大家回顾考试重点&#xff0c;大家可以看着下方敏捷实践流程图进行个人构思&#xff0c;后文也会为大家一一剖析其中的重要环节。完整的Scrum敏捷实践框架流程图&#xff1a;一、冲刺计划会议 Sprint Planning1.为即将…

SQP求解器推导与matlab命令(JacobianHessian矩阵)

缩写&#xff1a; SQP(Sequential Quadratic Programming)序列二次规划 NLP 非线性规划问题 matlab代码 matlab中求解器 SQP的认识 《最优化方法及其Matlab程序设计》 书 马昌凤 SQP 知乎 基础知识点 I.Jacobian矩阵 Def: 一阶偏导 II.Hessian矩阵 Def: 二阶偏导 图 三阶…

【SpringCloud+Vue】生成微信二维码及扫码登录--OAuth2

OAuth2 微信登录流程 前端代码实现 后端代码实现 导入依赖 yml 实体类以及返回结果 工具类 微信配置信息 HTTP客户端连接池 JWT 控制层 业务层 持久层 OAuth2 OAuth2是OAuth&#xff08;Open Authorization&#xff0c;开放授权&#xff09;协议的延续版本。用来授…

Hinge Loss 和 Zero-One Loss

文章目录Hinge Loss 和 Zero-One LossHinge LossZero-One LossHinge Loss 和 Zero-One Loss 维基百科&#xff1a;https://en.wikipedia.org/wiki/Hinge_loss 图表说明&#xff1a; 纵轴表示固定 t1t1t1 的 Hinge loss&#xff08;蓝色&#xff09;和 Zero-One Loss&#xff…

字节5年测试经验,12月无情被辞,划水的兄弟别再这样了····

前言 先简单交代一下背景吧&#xff0c;某不知名 985 的本硕&#xff0c;17 年毕业加入字节&#xff0c;以“人员优化”的名义无情被裁员&#xff0c;之后跳槽到了有赞&#xff0c;一直从事软件测试的工作。之前没有实习经历&#xff0c;算是5年的工作经验吧。 这5年之间完成…

实现一个小程序分享图 wxml2canvas

我们经常会遇上动态生成海报的需求&#xff0c;而在小程序中&#xff0c;生成图片非Canvas莫属。但是在实际工作当中&#xff0c;为了追求效率&#xff0c;我们会不可避免地去使用一些JS插件&#xff0c;而 wxml-to-canvas 就是一款官方推荐且非常优秀的插件&#xff0c;它可以…

图文详解Ansible中的变量及加密

文章目录一、变量命名二、变量级别三、.变量设定和使用方式1.在playbook中直接定义变量2.在文件中定义变量3.使用变量4.设定主机变量和清单变量5.目录设定变量6.用命令覆盖变量7.使用数组设定变量8.注册变量9.事实变量10.魔法变量四、JINJA2模板五、 Ansible的加密控制练习1.用…

I2C总线应用测试程序

参考链接&#xff1a;I2c协议 Linux I2C应用编程开发 问题背景 在工作中需要测试I2C总线的传输稳定性&#xff0c;需写一个测试程序通过读写从设备寄存器的值来验证数据传输稳定性。 站在cpu的角度来看&#xff0c;操作I2C外设实际上就是通过控制cpu中挂载该I2C外设的I2C控制…

yunUI组件库解析:图片上传与排序组件yImgPro

yunUI是笔者开源的微信小程序功能库。目前其中包含了一些复杂的功能组件。方便使用。未来它将分为组件、样式、js三者合为一体&#xff0c;但分别提供。 本文所用代码皆来源于组件库中的yImgPro组件。详细代码可至github查看。地址&#xff1a; yunUI 。 npm地址&#xff1a;yu…