Loading [MathJax]/jax/input/TeX/jax.js
首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >专栏 >【C++】AVL树

【C++】AVL树

作者头像
zxctscl
发布于 2024-12-04 00:13:34
发布于 2024-12-04 00:13:34
16900
代码可运行
举报
文章被收录于专栏:zxctscl个人专栏zxctscl个人专栏
运行总次数:0
代码可运行

个人主页zxctscl 如有转载请先通知

1. 底层结构

前面对map/multimap/set/multiset进行了简单的介绍: 【C++】map和set ,在其文档介绍中发现,这几个容器有个共同点是:其底层都是按照二叉搜索树来实现的,但是二叉搜索树有其自身的缺陷,假如往树中插入的元素有序或者接近有序,二叉搜索树就会退化成单支树,时间复杂度会退化成O(N),因此map、set等关联式容器的底层结构是对二叉树进行了平衡处理,即采用平衡树来实现。

2. AVL树的概念

二叉搜索树虽可以缩短查找的效率,但如果数据有序或接近有序二叉搜索树将退化为单支树,查找元素相当于在顺序表中搜索元素,效率低下。因此,两位俄罗斯的数学家G.M.Adelson-Velskii和E.M.Landis在1962年 发明了一种解决上述问题的方法:当向二叉搜索树中插入新结点后,如果能保证每个结点的左右子树高度之差的绝对值不超过1(需要对树中的结点进行调整),即可降低树的高度,从而减少平均搜索长度。

因为有些节点的数量,像2和4,做不到高度差相等,所以规则就退而求其次,左右高度差不超过1。

一棵AVL树或者是空树,或者是具有以下性质的二叉搜索树: 它的左右子树都是AVL树 左右子树高度之差(简称平衡因子)的绝对值不超过1(-1/0/1) 平衡因子并不是必须的,只是它的一种实现方式。

平衡因子=右子树高度-左子树高度

如果一棵二叉搜索树是高度平衡的,它就是AVL树。如果它有n个结点,其高度可保持在

O(log2n)

,搜索时间复杂度O(

log2n

)。

3. AVL树节点的定义

AVL树节点的定义:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
template<class T>
struct AVLTreeNode
{
	AVLTreeNode(const T& data)
		: _pLeft(nullptr), _pRight(nullptr), _pParent(nullptr)
		, _data(data), _bf(0)
	{}
	AVLTreeNode<T>* _pLeft; // 该节点的左孩子
	AVLTreeNode<T>* _pRight; // 该节点的右孩子
	AVLTreeNode<T>* _pParent; // 该节点的双亲
	T _data;
	int _bf; // 该节点的平衡因子
};

4. AVL树的插入

AVL树就是在二叉搜索树的基础上引入了平衡因子,因此AVL树也可以看成是二叉搜索树。那么 AVL树的插入过程可以分为两步:

  1. 按照二叉搜索树的方式插入新节点
  2. 调整节点的平衡因子(更新插入节点的祖先节点的平衡因子) (1)插入到父亲的左边,父亲平衡因子减减 (2)插入到父亲的右边,父亲平衡因子加加 (3)如果父亲节点的平衡因子等于0,父亲所在子树高度不变,不再继续往上更新,插入结束。 (4)如果父亲的平衡因子是1或者-1,父亲所在子树高度变了,继续往上更新 (5)如果父亲的平衡因子是2或者-2,父亲所在子树已经不平衡了,需要旋转处理更新中不可能出现其它值,插入之前树是AVL树,平衡因子是-1 0 1,加加减减最多出现(3)(4)(5)情况。

插入一个节点,先判断,如果root为空,就先插入第一个节点:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
		if (_root == nullptr)
		{
			_root = new Node(kv);
			return true;
		}

用cur指向root,插入比root大往右走,是比较pair的first,比root小往左走:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
		Node* cur = _root;
		Node* parent = nullptr;
		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);

		if (parent->_kv.first <kv.first)
		{
			parent->_right = cur;
		}
		else
		{
			parent->_left = cur;
		}

但是这里是三叉链,这里得和父亲链接起来:cur->_parent = parent; 这里得更新平衡因子:插入到父亲的左边,父亲平衡因子减减;插入到父亲的右边,父亲平衡因子加加;平衡因子等于0,父亲所在子树高度不变,更新结束。平衡因子是1或者-1,父亲所在子树高度变了,继续往上更新,父亲的平衡因子是2或者-2,父亲所在子树已经不平衡了

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
	while (parent)
		{
			if (cur == parent->_left)
			{
				parent->_bf--;
			}
			else
			{
				parent->_bf++;
			}

			if (parent->_bf == 0)
			{
				// 更新结束
				break;
			}
			else if (parent->_bf == 1 || parent->_bf == -1)
			{
				// 继续往上更新
				cur = parent;
				parent = parent->_parent;
			}
			else if (parent->_bf == 2 || parent->_bf == -2)
			{
				// 当前子树出问题了,需要旋转平衡一下


				break;
			}
			else
			{
				// 理论而言不可能出现这个情况
				assert(false);
			}
		}

5. AVL树的旋转

这里用抽象图来代表某一类: abc代表高度为h的AVL子树:

在a这里插入新节点,a的高度变化从h变成h+1,就会把30的平衡因子更新为-1;

具象图:

  1. h==0: 30这里新增一个节点。
  1. h==1: 在20的左边或者右边新增都会引发旋转
  1. h==2 高度为2的AVL树有三种:

b和c是x/y/z中的任意一种 a只能是z这种情况,如果a是x这种情况:

那么当a插入一个节点,像下面这样,高度不变,就不会往上更新:

如果长这样:它自己就不旋转了:

所以a一定就是下面这种形状,插入一个节点才会引发旋转:

当h==2时候,a插入一个节点,就会引发旋转

b和c三种形状都可以,而插入可以选择4个节点中任何一个,所以

  1. h==3 高度为3最满的就是下面这样,4个节点可能是满的C44,四个节点中任意选三,四个节点中任意选两个,四个节点中任意选一个,这些都可能是高度为h的情况。光树就可能有15种。

a如果是下面这种情况:就有8种插入新节点的可能:

如果在一棵原本是平衡的AVL树中插入一个新节点,可能造成不平衡,此时必须调整树的结构, 使之平衡化。根据节点插入位置的不同,AVL树的旋转分为四种:

  1. 新节点插入较高左子树的左侧—左左:右单旋(左边高,往右边压)

b变成60的左边,30<b子树<60<c子树

所以当h==1时候,也是同样的右边压

旋转后的a c位置没变,把60变到30右边,b变到60左边:

此时代码就是:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
	void RotateR(Node* parent)
	{
		Node* subL = parent->_left;
		Node* subLR = subL->_right;

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

		subL->_right = parent;

		Node* ppNode = parent->_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;
		}

		parent->_bf = subL->_bf = 0;
	}
  1. 新节点插入较高右子树的右侧—右右:左单旋

现在右边高,让subRL变成30的右边,30<b<60<c:

右边高,左边旋:

代码和右单旋类似:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
	void RotateL(Node* parent)
	{
		Node* subR = parent->_right;
		Node* subRL = subR->_left;

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

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

		parent->_parent = subR;

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

		parent->_bf = subR->_bf = 0;
	}

像下面这样的情况,发现右边高,就左单旋,而出来结果导致左边高,再右单旋,发现结果和刚开始一样。

上面这个图并不是纯粹的右边高,它是右边高,左边高,不像纯粹的右边高(下面图这样):

这不是单纯的右边高,右边高,左边高

以8为旋转点,进行右边单旋啊,经过这个单旋,变成单纯的右边高。在意parent为旋转点进行左单选:

  1. 新节点插入较高左子树的右侧—左右:先左单旋再右单旋

将双旋变成单旋后再旋转,即:先对30进行左单旋,然后再对90进行右单旋,旋转完成后再 考虑平衡因子的更新。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
	void RotateLR(Node* parent)
	{
		Node* subL = parent->_left;
		Node* subLR = subL->_right;

		int bf = subLR->_bf;

		RotateL(parent->_left);
		RotateR(parent);

		if (bf == -1)
		{
			subLR->_bf = 0;
			subL->_bf = 0;
			parent->_bf = 1;
		}
		else if (bf == 1)
		{
			subLR->_bf = 0;
			subL->_bf = -1;
			parent->_bf = 0;
		}
		else if (bf == 0)
		{
			subLR->_bf = 0;
			subL->_bf = 0;
			parent->_bf = 0;
		}
		else
		{
			assert(false);
		}

	}
  1. 新节点插入较高右子树的左侧—右左:先右单旋再左单旋

总结: 假如以pParent为根的子树不平衡,即pParent的平衡因子为2或者-2,分以下情况考虑

  1. pParent的平衡因子为2,说明pParent的右子树高,设pParent的右子树的根为pSubR 当pSubR的平衡因子为1时,执行左单旋 当pSubR的平衡因子为-1时,执行右左双旋
  2. pParent的平衡因子为-2,说明pParent的左子树高,设pParent的左子树的根为pSubL 当pSubL的平衡因子为-1是,执行右单旋 当pSubL的平衡因子为1时,执行左右双旋 旋转完成后,原pParent为根的子树个高度降低,已经平衡,不需要再向上更新
代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
	void RotateRL(Node* parent)
	{
		Node* subR = parent->_right;
		Node* subRL = subR->_left;
		int bf = subRL->_bf;

		RotateR(subR);
		RotateL(parent);

		subRL->_bf = 0;
		if (bf == 1)
		{
			subR->_bf = 0;
			parent->_bf = -1;
		}
		else if (bf == -1)
		{
			parent->_bf = 0;
			subR->_bf = 1;
		}
		else
		{
			parent->_bf = 0;
			subR->_bf = 0;
		}
	}

6. AVL树的验证

AVL树是在二叉搜索树的基础上加入了平衡性的限制,因此要验证AVL树,可以分两步:

  1. 验证其为二叉搜索树 如果中序遍历可得到一个有序的序列,就说明为二叉搜索树
  2. 验证其为平衡树 (1)每个节点子树高度差的绝对值不超过1(注意节点中如果没有平衡因子) (2)节点的平衡因子是否计算正确
代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
	  int _Size(Node* root)
	{
		return root == nullptr ? 0 : _Size(root->_left) + _Size(root->_right) + 1;
	}


		int _Height(Node* root)
		{
			if (root == nullptr)
				return 0;
			/*int leftHeight= _Height(root->_left);
			int rightHeight = _Height(root->_right);*/

			return max(_Height(root->_left), _Height(root->_right)) + 1;
		}

		bool _IsBalance(Node* root)
		{
			if (root == nullptr)
				return true;

			int leftHeight = _Height(root->_left);
			int rightHeight = _Height(root->_right);

			if (abs(leftHeight - rightHeight) >= 2)//不平衡
			{
				cout << root->_kv.first << endl;
				return false;
			}
			
			// 顺便检查一下平衡因子是否正确
			if (rightHeight - leftHeight != root->_bf)
			{
				cout << root->_kv.first << endl;
				return false;
			}

			return _IsBalance(root->_left)
				&& _IsBalance(root->_right);
		}

测试用例:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
void TestAVLTree2()
{
	const int N = 100000000;
	vector<int> v;
	v.reserve(N);
	srand(time(0));

	for (size_t i = 0; i < N; i++)
	{
		v.push_back(rand() + i);
		//cout << v.back() << endl;
	}

	size_t begin2 = clock();
	AVLTree<int, int> t;
	for (auto e : v)
	{
		t.Insert(make_pair(e, e));
		//cout << "Insert:" << e << "->" << t.IsBalance() << endl;
	}
	size_t end2 = clock();

	cout << "Insert:" << end2 - begin2 << endl;
	//cout << t.IsBalance() << endl;

	cout << "Height:" << t.Height() << endl;
	cout << "Size:" << t.Size() << endl;

	size_t begin1 = clock();
	// 确定在的值
	for (auto e : v)
	{
		t.Find(e);
	}

	// 随机值
	/*for (size_t i = 0; i < N; i++)
	{
		t.Find((rand() + i));
	}*/

	size_t end1 = clock();

	cout << "Find:" << end1 - begin1 << endl;
}

AVL树的性能: AVL树是一棵绝对平衡的二叉搜索树,其要求每个节点的左右子树高度差的绝对值都不超过1,这样可以保证查询时高效的时间复杂度,即

log2(N)

。但是如果要对AVL树做一些结构修改的操作,性能非常低下,比如:插入时要维护其绝对平衡,旋转的次数比较多,更差的是在删除时,有可能一直要让旋转持续到根的位置。因此:如果需要一种查询高效且有序的数据结构,而且数据的个数为静态的(即不会改变),可以考虑AVL树,但一个结构经常修改,就不太适合。

7. AVLTree.h

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
#pragma once
#include<assert.h>
#include<vector>


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

	int _bf;  // balance factor

	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:
	// logN
	bool Insert(const pair<K, V>& kv)
	{
		if (_root == nullptr)
		{
			_root = new Node(kv);
			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);
		if (parent->_kv.first < kv.first)
		{
			parent->_right = cur;
		}
		else
		{
			parent->_left = cur;
		}
		cur->_parent = parent;

		//...
		// 更新平衡因子
		while (parent)
		{
			if (cur == parent->_left)
			{
				parent->_bf--;
			}
			else
			{
				parent->_bf++;
			}

			if (parent->_bf == 0)
			{
				// 更新结束
				break;
			}
			else if (parent->_bf == 1 || parent->_bf == -1)
			{
				// 继续往上更新
				cur = parent;
				parent = parent->_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)
				{
					RotateRL(parent);
				}
				else if(parent->_bf == -2 && cur->_bf == 1)
				{
					RotateLR(parent);
				}

				break;
			}
			else
			{
				// 理论而言不可能出现这个情况
				assert(false);
			}
		}


		return true;
	}



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


	void InOrder()
	{
		_InOrder(_root);
		cout << endl;
	}


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

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

		subL->_right = parent;

		Node* ppNode = parent->_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;
		}

		parent->_bf = subL->_bf = 0;
	}


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

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

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

		parent->_parent = subR;

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

		parent->_bf = subR->_bf = 0;
	}

	void RotateRL(Node* parent)
	{
		Node* subR = parent->_right;
		Node* subRL = subR->_left;
		int bf = subRL->_bf;

		RotateR(subR);
		RotateL(parent);

		subRL->_bf = 0;
		if (bf == 1)
		{
			subR->_bf = 0;
			parent->_bf = -1;
		}
		else if (bf == -1)
		{
			parent->_bf = 0;
			subR->_bf = 1;
		}
		else
		{
			parent->_bf = 0;
			subR->_bf = 0;
		}
	}

	void RotateLR(Node* parent)
	{
		Node* subL = parent->_left;
		Node* subLR = subL->_right;

		int bf = subLR->_bf;

		RotateL(parent->_left);
		RotateR(parent);

		if (bf == -1)
		{
			subLR->_bf = 0;
			subL->_bf = 0;
			parent->_bf = 1;
		}
		else if (bf == 1)
		{
			subLR->_bf = 0;
			subL->_bf = -1;
			parent->_bf = 0;
		}
		else if (bf == 0)
		{
			subLR->_bf = 0;
			subL->_bf = 0;
			parent->_bf = 0;
		}
		else
		{
			assert(false);
		}
	}

	bool IsBalance()
	{
		return _IsBalance(_root);
	}

	int Height()
	{
		return _Height(_root);
	}

	int Size()
	{
		return _Size(_root);
	}


private:
  	  int _Size(Node* root)
	{
		return root == nullptr ? 0 : _Size(root->_left) + _Size(root->_right) + 1;
	}

		int _Height(Node* root)
		{
			if (root == nullptr)
				return 0;
			/*int leftHeight= _Height(root->_left);
			int rightHeight = _Height(root->_right);*/

			return max(_Height(root->_left), _Height(root->_right)) + 1;
		}

		bool _IsBalance(Node* root)
		{
			if (root == nullptr)
				return true;

			int leftHeight = _Height(root->_left);
			int rightHeight = _Height(root->_right);

			if (abs(leftHeight - rightHeight) >= 2)//不平衡
			{
				cout << root->_kv.first << endl;
				return false;
			}
			
			// 顺便检查一下平衡因子是否正确
			if (rightHeight - leftHeight != root->_bf)
			{
				cout << root->_kv.first << endl;
				return false;
			}

			return _IsBalance(root->_left)
				&& _IsBalance(root->_right);
		}


		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 TestAVLTree1()
{
	int a[] = { 8, 3, 1, 10, 6, 4, 7, 14, 13 };
	AVLTree<int, int> t1;
	for (auto e : a)
	{
		t1.Insert({ e,e });
		/*cout <<"Insert"<<e<<"->" << t1.IsBalance() << endl;*/
	}

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

void TestAVLTree2()
{
	const int N = 100000000;
	vector<int> v;
	v.reserve(N);
	srand(time(0));

	for (size_t i = 0; i < N; i++)
	{
		v.push_back(rand() + i);
		//cout << v.back() << endl;
	}

	size_t begin2 = clock();
	AVLTree<int, int> t;
	for (auto e : v)
	{
		t.Insert(make_pair(e, e));
		//cout << "Insert:" << e << "->" << t.IsBalance() << endl;
	}
	size_t end2 = clock();

	cout << "Insert:" << end2 - begin2 << endl;
	//cout << t.IsBalance() << endl;

	cout << "Height:" << t.Height() << endl;
	cout << "Size:" << t.Size() << endl;

	size_t begin1 = clock();
	// 确定在的值
	for (auto e : v)
	{
		t.Find(e);
	}

	// 随机值
	/*for (size_t i = 0; i < N; i++)
	{
		t.Find((rand() + i));
	}*/

	size_t end1 = clock();

	cout << "Find:" << end1 - begin1 << endl;
}
本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2024-12-03,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
暂无评论
推荐阅读
编辑精选文章
换一批
Mac笔记本电脑如何双开微信(亲测有效且实用)!
现在一个人有两三个微信很正常,但是正常情况下 mac 只能登录一个微信,用其他微信只能在手机上操作比较麻烦(划水也容易被发现)。
一个程序猿的异常
2023/07/24
13.9K1
Mac笔记本电脑如何双开微信(亲测有效且实用)!
好用!目前用下来最溜的MacOS微信多开工具!
作者 | TJ君 来源 | https://mp.weixin.qq.com/s/1xaPLYCJcGT-6VYYliINHg‍ 一个生活微信,一个工作微信是很多上班族的基本配置。 但由于微信客户端在PC端上只能打开一个,这使得在上班时候就非常不便,一个号在PC端上登录,一个在手机上使用,但是上班时候又不能一直看手机,不然老板还以为你在玩呢。 所以,对于打开多个微信客户端的需求就来了! 查了一下百度,有几个基本的多开方法,简单总结下大致有以下三类: 第一类:创建微信应用的副本,通过复刻多个微信应用来实现。
程序猿DD
2023/04/04
1.3K0
好用!目前用下来最溜的MacOS微信多开工具!
如何让 Mac 版微信客户端防撤回
其实这件事情技术含量不大,而且网上之前就有过其他教程,稍微看得懂一点汇编的都可以改。但是先前的文章对于一些细节都没有讲得很清楚,因此我这篇其实是补完。 由于文内有较多链接,微信内看的话请点击右上角 在浏览器打开。 工具准备 不得不说 Hopper Disassembler 是个好东西,就是下载起来麻烦了些,昨晚搞了半天,在猫猫的帮助下搞到了这工具,还是把它放在自己的服务器上比较好 >_< 点击下载 Hipper Disassembler http://diy.ourocg.cn/downlo
用户1907613
2018/07/20
2K0
微信电脑版重大更新,可以上班刷朋友圈摸鱼了
然后是可以刷朋友圈了,这是最重磅的一大更新了,上班再也不用担心刷手机摸鱼被发现了!
godweiyang
2021/03/16
6670
微信电脑版重大更新,可以上班刷朋友圈摸鱼了
分分钟让你在 微信运动 霸占榜首
提示: 这仅仅只是逆向的一次尝试,如果你仅仅只是想单纯的修改步数,建议使用Healthkit的API修改步数,将会更简单。
疯狂的技术宅
2019/03/28
2.1K0
分分钟让你在 微信运动 霸占榜首
制作macOS安装盘
好久没用macOS了,发现系统已经从10.14更新到12.1了,趁此机会制作了个macOS 12.1安装盘(苹果系统越来越大,老系统8G优盘就够了,新系统得准备16G优盘),制作办法在文档末尾。把制作的安装盘通过vmware菜单:虚拟机 → 可移动设备 → 找到后,断开与电脑主机的连接就自动连给当前运行中的虚拟机了,重启虚拟机,在虚拟机开机的时候就会识别到安装盘来安装macOS系统了。注意:vmware默认不支持创建macOS虚机,但安装个这个软件后就可以支持了,这个软件的使用说明参考:https://github.com/BDisp/unlocker ,说白了就是以管理员身份运行win-install.cmd
Windows技术交流
2022/01/18
2.2K0
macOS系统下如何优雅的使用Burp Suite
众所周知国内我们使用的Burp Suite大多数是大佬们分享出来的专业破解版的Burp Suite,每次启动的时候都得通过加载器来启动Burp Suite,那有没有更加优雅的方式呢?下面就开始水这一篇文章了,告诉大家如何在macOS下配置基本的渗透测试环境。(我也是刚换macOS)
王先森sec
2023/04/24
3.6K0
macOS系统下如何优雅的使用Burp Suite
Mac微信小工具使用体验
这个一个功能强大的 macOS 版微信小助手,上一张作者写的功能介绍的的截图,具体实现后面讲。
分享者
2022/05/17
9140
Mac微信小工具使用体验
Mac如何实现微信多开操作
最近用新手机号码注册了一个微信账号,自己便有两个微信账号。但是Mac电脑上面只能登录一个,这就很尴尬了。
兔云小新LM
2019/07/24
12K0
Mac如何实现微信多开操作
MacOS下SVN迁移Git踩坑记
1. First Blood 之前在Windows环境下进行svn到git的迁移是很简单的,参考官方文档。 可是在macOS环境下(macOS High Sierra 10.13.2),输入: $ git svn 神奇的事情发生了,报了以下错误: can't locate SVN/Core.pm in @INC (you may need to install the SVN::Core module) (@INC contains: /usr/local/git/lib/perl5/site_perl/
mantou
2018/04/16
3.4K0
macOS 下载编译 aosp 源码
最近在读《Android 进阶指北》,开篇就是介绍如何在安装到 VirtualBox 的 Ubuntu 上进行下载和编译 aosp(Android Open Source Project)。由于我的电脑是 macOS,所以首先尝试了在 macOS 上进行下载和编译 aosp,其中碰到了很多问题,所以整理此文出来,给后来人借鉴。
huofo
2022/03/17
3.6K0
macOS 下载编译 aosp 源码
【双开微信】可同时打开多个微信小技巧,实测有效!~
很多小伙伴有两个微信或者多个微信于是想要在电脑上同时登录多个微信,那么应该怎么操作呢,网上的方法很多都是不可以实现的, 我来给大家讲一个比较实际的方法,亲测有效哦。
程序员洲洲
2024/06/07
3.8K0
【双开微信】可同时打开多个微信小技巧,实测有效!~
LLDB实战之导出Mac微信备份聊天记录的SQLite密码(SQLCipher加密)
于是查看SQLCipher的API,看到用的是sqlite3_key()和sqlite3_key_v2()这2个函数,在源码里搜索,找到调用,一共有两处,在WCTDatabase+Database.mm文件里
xferris
2019/12/30
6.6K1
【踩坑】最新亲测能用!修复MacOS安装软件时提示“应该移到废纸篓”并且无法打开软件
1、首先下载这个脚本:macOS-GateKeeper-Helper: Simple macOS GateKeeper script.
小锋学长生活大爆炸
2023/08/14
8830
【踩坑】最新亲测能用!修复MacOS安装软件时提示“应该移到废纸篓”并且无法打开软件
程序员必备小技能:mac文件备份和清理、常用工具的安装和配置
一年一度的iOS 系统 API适配来了,9 月 14 日起 App Store Connect 已经开放 iOS 15 和 iPadOS 15 App 的提交,同时苹果宣布自 2022 年 4 月起,所有提交至 App Store 的 iOS 和 iPadOS app 都必须使用 Xcode 13 和 iOS 15 SDK 构建。
公众号iOS逆向
2022/12/19
1.3K0
程序员必备小技能:mac文件备份和清理、常用工具的安装和配置
5种方法实现电脑微信多开
小焱
2025/05/20
5.6K0
5种方法实现电脑微信多开
偷偷告诉你如何一台电脑开多个微信!
手机端多开微信估计很多人都知道,像华为、小米等手机系统都对此做了支持,不过在运行Windows系统的电脑上怎么启动两个微信呢?
轩辕之风
2024/05/06
3730
偷偷告诉你如何一台电脑开多个微信!
解决mac升级ventura系统后parallels无法运行问题
记一下 mac 升级 ventura 系统后 parallels desktop 虚拟机无法使用问题
薛定喵君
2022/11/12
8.2K0
在 macOS 上安装 JDK 17
JDK 支持基于 Intel (x64) 和 Apple Silicon (AArch64) 的 Mac 电脑。
猫头虎
2024/04/07
5.9K0
制作MacOs Mojave U盘USB启动安装系统盘方法教程 (全新安装 MacOs系统)
制作MacOs Mojave正式版USB启动盘的方法有很多,用户可以选择使用命令行来创建,也可以选择第三方U盘制作工具来制作,大家可以根据自己的喜好选择。
骤雨重山
2022/01/17
6.9K0
制作MacOs Mojave U盘USB启动安装系统盘方法教程 (全新安装 MacOs系统)
推荐阅读
相关推荐
Mac笔记本电脑如何双开微信(亲测有效且实用)!
更多 >
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档