【C++篇】树影摇曳,旋转无声:探寻AVL树的平衡之道

文章目录

    • 从结构到操作:手撕AVL树的实现
    • 一、AVL树介绍
      • 1.1 什么是AVL树
      • 1.2 平衡因子的定义
      • 1.3 平衡的意义
      • 1.4 AVL树的操作
    • 二、AVL树的节点结构
      • 2.1 节点结构的定义:
    • 三、插入操作
      • 3.1 插入操作概述
      • 3.2 步骤1:按二叉查找树规则插入节点
      • 3.3 步骤2:更新平衡因子
      • 3.4 步骤3:旋转操作详解
        • 3.4.1 右单旋
        • 3.4.2 左单旋
        • 3.4.3 左右双旋
        • 3.4.4 右左双旋
    • 四、其他操作
      • 4.1 查找操作 (`Find`)
      • 4.2 计算树的高度 (`Height`)
      • 4.3 计算节点数量 (`Size`)
      • 4.4 树是否平衡 (`IsBalanceTree`)
    • 五、性能分析与测试
      • 5.1 性能分析
      • 5.2 测试代码
    • 六、全部代码
    • 七、总结与展望

从结构到操作:手撕AVL树的实现

💬 欢迎讨论:如果你在阅读过程中有任何疑问或想要进一步探讨的内容,欢迎在评论区留言!我们一起学习、一起成长。

👍 点赞、收藏与分享:如果你觉得这篇文章对你有帮助,记得点赞、收藏并分享给更多的朋友!

🚀 逐步实现AVL树:本篇文章将带你一步一步实现一个自平衡的二叉查找树——AVL树,从最基本的节点结构开始,逐步实现插入、查找等操作,并确保每个步骤都详细讲解。


一、AVL树介绍

1.1 什么是AVL树

AVL树是一种自平衡的二叉查找树(BST),由G.M. Adelson-VelskyE.M. Landis于1962年提出。AVL树的核心特点是它保证树的每个节点的左右子树的高度差(平衡因子)不超过1,从而保证了AVL树在插入、删除和查找操作时的时间复杂度始终为O(log N)。

1.2 平衡因子的定义

每个节点都有一个平衡因子_bf),它表示节点左子树的高度减去右子树的高度:
平衡因子 = 左子树的高度 − 右子树的高度 \text{平衡因子} = \text{左子树的高度} - \text{右子树的高度} 平衡因子=左子树的高度右子树的高度

  • 如果平衡因子为 0,表示左右子树的高度相等。
  • 如果平衡因子为 1,表示左子树比右子树高1。
  • 如果平衡因子为 -1,表示右子树比左子树高1。

1.3 平衡的意义

AVL树的自平衡特性确保了其查询、插入和删除操作的最坏时间复杂度为O(log N),避免了普通二叉查找树的退化(比如变成链表)。因此,AVL树非常适用于需要频繁插入和删除操作的场景。

1.4 AVL树的操作

AVL树的主要操作有:

  • 插入操作:在树中插入节点,并在插入后调整平衡因子。如果平衡因子为2或-2,执行旋转操作恢复平衡。
  • 删除操作:删除节点,并且在删除节点后,可能需要调整树的结构以保持平衡。由于AVL树的删除操作涉及复杂的旋转和调整,因此在本文中我们不会详细讲解该操作。如果你希望深入了解,可以参考《算法导论》中的相关章节来获取详细内容。
  • 查找操作:通过比较节点值来查找特定的元素。
  • 旋转操作:当树失衡时,使用旋转来恢复平衡,常见的旋转有左旋、右旋、左右旋和右左旋。

二、AVL树的节点结构

在实现AVL树之前,首先要定义节点结构。每个节点存储以下信息:

  • 键值对_kv):存储数据(pair<K, V>)。
  • 左右子树指针_left, _right):指向左右子树。
  • 父节点指针_parent):指向父节点。
  • 平衡因子_bf):用于标识该节点的左右子树的高度差。

2.1 节点结构的定义:

template<class K, class V>
struct AVLTreeNode
{
    pair<K, V> _kv;               // 存储键值对
    AVLTreeNode<K, V>* _left;      // 左子树指针
    AVLTreeNode<K, V>* _right;     // 右子树指针
    AVLTreeNode<K, V>* _parent;    // 父节点指针
    int _bf;                       // 平衡因子

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

三、插入操作

3.1 插入操作概述

AVL树的插入操作包括三步:

  1. 按二叉查找树规则插入节点
  2. 更新每个节点的平衡因子
  3. 如果需要,进行旋转操作来恢复树的平衡。
bool Insert(const pair<K, V>& kv);

3.2 步骤1:按二叉查找树规则插入节点

在插入节点时,我们根据值的大小,递归地找到插入位置,并在该位置插入新节点:

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;
}
//更新插入节点的_parent
cur->_parent = parent;

3.3 步骤2:更新平衡因子

插入节点后,我们从新插入的节点开始,逐步更新路径上每个节点的平衡因子。插入时会对父节点的平衡因子进行调整:

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 = 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);
	}
}
return true;

3.4 步骤3:旋转操作详解

如果某个节点的平衡因子为2或-2,表示该节点不平衡。我们需要通过旋转操作来恢复平衡。旋转操作有四种:

  1. 右单旋(RotateR)
  2. 左单旋(RotateL)
  3. 左右双旋(RotateLR)
  4. 右左双旋(RotateRL)
3.4.1 右单旋

右单旋适用于当左子树较高时,执行旋转操作来恢复平衡。

在这里插入图片描述
在这里插入图片描述

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

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

    subL->_right = parent;
    parent->_parent = subL;

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

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

3.4.2 左单旋

左单旋适用于当右子树较高时,执行旋转操作来恢复平衡。

在这里插入图片描述
在这里插入图片描述

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

    parent->_right = subRL;
    parent->_parent = subR;
    if (subRL) {
        subRL->_parent = parent;
    }

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

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

3.4.3 左右双旋

在这里插入图片描述
在这里插入图片描述

当左子树的右子树较高时,首先进行左旋,再进行右旋,恢复平衡。

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

    RotateL(subL);  // 左旋
    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) {
        subL->_bf = subLR->_bf = parent->_bf = 0;
    }
}

在这里插入图片描述


3.4.4 右左双旋

当右子树的左子树较高时,首先进行右旋,再进行左旋,恢复平衡。
在这里插入图片描述
在这里插入图片描述

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

    RotateR(subR);  // 右旋
    RotateL(parent);  // 左旋

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

在这里插入图片描述

总结:找失衡原因,决定如何旋转

  • 左子树的左子树高:进行R
  • 右子树的右子树高:进行L
  • 左子树的右子树高:进行LR
  • 右子树的左子树高:进行RL

四、其他操作

4.1 查找操作 (Find)

查找操作与普通的二叉查找树类似。通过不断比较当前节点的键值和目标值,沿树的路径查找目标节点。

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;  // 如果没有找到,返回空
}

该方法的时间复杂度为 O(log N),因为AVL树是平衡的,树的高度是对数级别的。


4.2 计算树的高度 (Height)

树的高度指的是从根节点到最远叶子节点的最长路径上的边数。AVL树的高度是平衡的,计算时我们需要递归地计算左右子树的高度并返回较大的一个。

int Height() {
    return _Height(_root);  // 从根节点开始计算树的高度
}

int _Height(Node* root) {
    if (root == nullptr)
        return 0;  // 空树的高度为0

    int leftHeight = _Height(root->_left);  // 递归计算左子树的高度
    int rightHeight = _Height(root->_right);  // 递归计算右子树的高度

    return leftHeight > rightHeight ? leftHeight + 1 : rightHeight + 1;  // 返回较大值,并加1
}

该方法的时间复杂度为 O(N),其中N为节点总数。虽然AVL树是平衡的,但在计算高度时需要遍历整个树。


4.3 计算节点数量 (Size)

节点数量是树中所有节点的总数。通过递归遍历树,计算左右子树的节点数量,并加上当前节点。

int Size() {
    return _Size(_root);  // 从根节点开始计算树的大小
}

int _Size(Node* root) {
    if (root == nullptr)
        return 0;  // 空树节点数量为0

    return _Size(root->_left) + _Size(root->_right) + 1;  // 左右子树的节点数量加1
}

该方法的时间复杂度为 O(N),需要遍历整个树来计算节点总数。


4.4 树是否平衡 (IsBalanceTree)

为了验证AVL树的平衡性,我们需要检查每个节点的平衡因子,并确保它们的绝对值不超过1。如果树的任意节点的平衡因子大于1或小于-1,那么树就不平衡。

bool _IsBalanceTree(Node* root) {
    if (root == nullptr)
        return true;  // 空树是平衡的

    int leftHeight = _Height(root->_left);  // 左子树高度
    int rightHeight = _Height(root->_right);  // 右子树高度
    int diff = leftHeight - rightHeight;  // 计算平衡因子

    if (abs(diff) >= 2) {
        cout << root->_kv.first << "高度差异常" << endl;
        return false;  // 如果高度差大于等于2,树不平衡
    }

    if (root->_bf != diff) {
        cout << root->_kv.first << "平衡因子异常" << endl;
        return false;  // 如果平衡因子与实际高度差不符,树不平衡
    }

    // 递归检查左右子树是否平衡
    return _IsBalanceTree(root->_left) && _IsBalanceTree(root->_right);
}

该方法的时间复杂度为 O(N),需要递归遍历整个树并计算每个节点的平衡因子。


五、性能分析与测试

5.1 性能分析

AVL树通过保持平衡,保证了插入、查找和删除操作的时间复杂度都为O(logN),相比于普通的二叉查找树,AVL树避免了退化成链表的情况,极大提高了性能。

5.2 测试代码

下面是两个测试代码,用于验证AVL树的插入和查找操作。

// 测试代码
void TestAVLTree1()
{
	AVLTree<int, int> t;
	// 常规的测试用例
	//int a[] = { 16, 3, 7, 11, 9, 26, 18, 14, 15 };
	// 特殊的带有双旋场景的测试用例
	int a[] = { 4, 2, 6, 1, 3, 5, 15, 7, 16, 14 };
	for (auto e : a)
	{
		t.Insert({ e, e });
		cout << "Insert:" << e << "->";
		cout << t.IsBalanceTree() << endl;
	}

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


// 插入一堆随机值,测试平衡,顺便测试一下高度和性能等
void TestAVLTree2()
{
	const int N = 1000000;
	vector<int> v;
	v.reserve(N);
	srand((unsigned int)time(0));
	for (int i = 0; i < N; i++)
	{
		v.push_back(rand() + i);
	}

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

	cout << t.IsBalanceTree() << endl;

	cout << "Insert:" << end2 - begin2 << endl;
	cout << "Height:" << t.Height() << endl;
	cout << "Size:" << t.Size() << endl;

	size_t begin1 = clock();
	// 确定在的值
	/*for (auto e : v)
	{
		t.Find(e);
	}*/
	// 随机值
	for (int i = 0; i < N; i++)
	{
		t.Find((rand() + i));
	}

	size_t end1 = clock();
	cout << "Find:" << end1 - begin1 << endl;
}

六、全部代码

AVLTree.h

#pragma once


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

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


template<class K, class V>
class AVLTree
{
	typedef AVLTreeNode<K, V> Node;
public:
	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 = 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);
			}
		}
		return true;
	}

	void InOrder()
	{
		_InOrder(_root);
	}

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

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

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

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


private:
	int _Size(Node* root)
	{
		if (root == nullptr)
			return 0;

		return _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 leftHeight > rightHeight ? leftHeight + 1 : rightHeight + 1;
	}

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

		int leftHeight = _Height(root->_left);
		int rightHeight = _Height(root->_right);
		int diff = leftHeight - rightHeight;
		if (abs(diff) >= 2)
		{
			cout << root->_kv.first << "高度差异常" << endl;
			return false;
		}

		if (root->_bf != diff) {
			cout << root->_kv.first << "平衡因子异常" << endl;
			return false;
		}

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

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

		_InOrder(root->_left);
		cout << root->_kv.first << ":" << root->_kv.second << " ";
		_InOrder(root->_right);
	}


	void RotateR(Node* parent)
	{
		Node* subL = parent->_left;
		Node* subLR = subL->_right;
		Node* ppNode = parent->_parent;
		parent->_left = subLR;
		if (subLR)
		{
			subLR->_parent = parent;
		}
		subL->_right = parent;
		parent->_parent = subL;
		if (parent == _root)
		{
			subL->_parent = nullptr;
			_root = subL;
		}
		else
		{
			subL->_parent = ppNode;
			if (parent == ppNode->_right)
				ppNode->_right = subL;
			else
				ppNode->_left = subL;
		}

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

	void RotateL(Node* parent)
	{
		Node* subR = parent->_right;
		Node* ppNode = parent->_parent;
		Node* subRL = subR->_left;
		parent->_right = subRL;
		parent->_parent = subR;
		if (subRL)
		{
			subRL->_parent = parent;
		}
		subR->_left = parent;
		if (parent == _root)
		{
			_root = subR;
			subR->_parent = nullptr;
		}
		else
		{
			subR->_parent = ppNode;
			if (parent == ppNode->_right)
				ppNode->_right = subR;
			else
				ppNode->_left = subR;
		}

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

	}

	void RotateLR(Node* parent)
	{
		Node* subL = parent->_left;
		Node* subLR = subL->_right;
		int bf = subLR->_bf;
		RotateL(subL);
		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)
		{
			subL->_bf = subLR->_bf = parent->_bf = 0;
		}
		else
		{
			assert(false);
		}
	}

	void RotateRL(Node* parent)
	{
		Node* subR = parent->_right;
		Node* subRL = subR->_left;
		int bf = subRL->_bf;
		RotateR(subR);
		RotateL(parent);
		if (bf == 0)
		{
			parent->_bf = subR->_bf = subRL->_bf = 0;
		}
		else if (bf == 1)
		{
			subRL->_bf = 0;
			parent->_bf = 0;
			subR->_bf = -1;
		}
		else if (bf == -1)
		{
			subRL->_bf = 0;
			parent->_bf = 1;
			subR->_bf = 0;
		}
		else
		{
			assert(false);
		}
	}

	Node* _root = nullptr;
};

// 测试代码
void TestAVLTree1()
{
	AVLTree<int, int> t;
	// 常规的测试用例
	//int a[] = { 16, 3, 7, 11, 9, 26, 18, 14, 15 };
	// 特殊的带有双旋场景的测试用例
	int a[] = { 4, 2, 6, 1, 3, 5, 15, 7, 16, 14 };
	for (auto e : a)
	{
		/*if (e == 14)
		{
			int x = 0;
		}*/

		t.Insert({ e, e });
		cout << "Insert:" << e << "->";
		cout << t.IsBalanceTree() << endl;
	}

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





// 插入一堆随机值,测试平衡,顺便测试一下高度和性能等
void TestAVLTree2()
{
	const int N = 1000000;
	vector<int> v;
	v.reserve(N);
	srand((unsigned int)time(0));
	for (int i = 0; i < N; i++)
	{
		v.push_back(rand() + i);
	}

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

	cout << t.IsBalanceTree() << endl;

	cout << "Insert:" << end2 - begin2 << endl;
	cout << "Height:" << t.Height() << endl;
	cout << "Size:" << t.Size() << endl;

	size_t begin1 = clock();
	// 确定在的值
	/*for (auto e : v)
	{
		t.Find(e);
	}*/
	// 随机值
	for (int i = 0; i < N; i++)
	{
		t.Find((rand() + i));
	}

	size_t end1 = clock();
	cout << "Find:" << end1 - begin1 << endl;
}

Test.cpp

#define _CRT_SECURE_NO_WARNINGS 1

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

int main()
{
	TestAVLTree1();
	TestAVLTree2();

	return 0;
}

七、总结与展望

随着数据量的不断增长,如何保持高效的查找与插入操作成为了现代计算机科学的核心问题之一。AVL树作为一种自平衡二叉查找树,凭借其稳定的时间复杂度和高效的性能,已广泛应用于各类需要动态数据结构支持的场景。未来,随着算法和硬件的进一步发展,AVL树的实现和优化将迎来更多挑战与机遇。我们期待看到更高效的树形结构和操作算法,不断推动计算机科学的前沿发展,同时也期待你在实现这些算法时能够继续探索与创新,为我们带来更多启发和思考。

以上就是关于【C++篇】树影摇曳,旋转无声:探寻AVL树的平衡之道的内容啦,各位大佬有什么问题欢迎在评论区指正,或者私信我也是可以的啦,您的支持是我创作的最大动力!❤️
在这里插入图片描述

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

DeepSeek、微信、硅基流动、纳米搜索、秘塔搜索……十种不同方法实现DeepSeek使用自由

为了让大家实现 DeepSeek 使用自由&#xff0c;今天分享 10 个畅用 DeepSeek 的平台。 一、官方满血版&#xff1a;DeepSeek官网与APP 首推&#xff0c;肯定是 DeepSeek 的官网和 APP&#xff0c;可以使用满血版 R1 和 V3 模型&#xff0c;以及联网功能。 网址&#xff1a; htt…

推荐几款SpringBoot项目手脚架

作为程序员、一般需要搭建项目手脚架时、都会去Gitee或Github上去找、但是由于Github在国内并不稳定、所以就只能去Gitee去上查找。 不同语言检索方式不一样、但是也类似。 Gitee WEB应用开发 / 后台管理框架 芋道源码 ELADMIN 后台管理系统 一个基于 Spring Boot 2.7.1…

【VSCode】MicroPython环境配置

【VSCode】MicroPython环境配置 RT-Thread MicroPython 插件安装MicroPython 库文件配置结束语 RT-Thread MicroPython 插件安装 在 VSCode 拓展中搜索 “RT-Thread MicroPython” 并安装&#xff0c;详细配置步骤&#xff08;修改 VSCode 默认终端、MicroPython 代码补全&…

Moonshot AI 新突破:MoBA 为大语言模型长文本处理提效论文速读

前言 在自然语言处理领域&#xff0c;随着大语言模型&#xff08;LLMs&#xff09;不断拓展其阅读、理解和生成文本的能力&#xff0c;如何高效处理长文本成为一项关键挑战。近日&#xff0c;Moonshot AI Research 联合清华大学、浙江大学的研究人员提出了一种创新方法 —— 混…

大语言模型推理能力从何而来?

前言 DeepSeek R1采用强化学习进行后训练&#xff0c;通过奖励机制和规则引导模型生成结构化思维链&#xff08;CoT&#xff09;&#xff0c;从而显著提升了推理能力。这一创新方法使得DeepSeek R1能够在无需大量监督数据的情况下&#xff0c;通过自我进化发展出强大的推理能力…

最新本地部署 DeepSeekR1 蒸馏\满血量化版 + WebOpenUI 完整教程(Ubuntu\Linux系统\Ollama)

测试机为6133CPU(40Cores)256G D44*4090D 24G 一种方法是部署蒸馏版Distill模型。一种是部署Huggingface上unsloth的量化版模型 Ollama及模型安装 1.下载并安装ollama curl -fsSL https://ollama.com/install.sh | sh如果下载不动可以试试挂梯子或者再试几次 挂代理代码&…

PySide6学习专栏(四):用多线程完成复杂计算任务

如果计程序中要处理一个非常庞大的数据集中的数据&#xff0c;且数据处理计算很复杂&#xff0c;造成数据处理占用大量时间和CPU资源&#xff0c;如果不用多线程&#xff0c;仅在主进程中来处理数据&#xff0c;将会使整个程序卡死&#xff0c;必须采用多线程来处理这些数据是唯…

路由基本配置

学习目标 • 根据拓扑图进行网络布线。 • 清除启动配置并将路由器重新加载为默认状态。 • 在路由器上执行基本配置任务。 • 配置并激活以太网接口。 • 测试并检验配置。 • 思考网络实施方案并整理成文档。 任务 1&#xff1a;网络布线 使用适当的电缆类型连接网络设备。…

STM32MP157A单片机移植Linux驱动深入版

需求整理 在Linux设备树中新增leds节点&#xff0c;其有3个gpio属性&#xff0c;分别表示PE10对应led1&#xff0c;PF10对应led2&#xff0c;PE8对应led3&#xff0c;设备树键值对如下&#xff1a; leds { led1-gpio <&gpioe 10 0>; led2-gpio &l…

瑞芯微RV1126部署YOLOv8全流程:环境搭建、pt-onnx-rknn模型转换、C++推理代码、错误解决、优化、交叉编译第三方库

目录 1 环境搭建 2 交叉编译opencv 3 模型训练 4 模型转换 4.1 pt模型转onnx模型 4.2 onnx模型转rknn模型 4.2.1 安装rknn-toolkit 4.2.2 onn转成rknn模型 5 升级npu驱动 6 C++推理源码demo 6.1 原版demo 6.2 增加opencv读取图片的代码 7 交叉编译x264 ffmepg和op…

如何为自己的 PDF 文件添加密码?在线加密 PDF 文件其实更简单

随着信息泄露和数据安全问题的日益突出&#xff0c;保护敏感信息变得尤为重要。加密 PDF 文件是一种有效的手段&#xff0c;可以确保只有授权用户才能访问或修改文档内容。本文将详细介绍如何使用 CleverPDF 在线工具为你的 PDF 文件添加密码保护&#xff0c;确保其安全性。 为…

蓝桥杯核心内容

核心内容 数学 质数与筛质数&#xff0c;分解质因数 分解质因数 所有的数都可以写成有限个数相乘质数&#xff1a;可以写成1✖本身&#xff08;如131✖13&#xff09;合数&#xff1a;ab1✖...✖bn-》把乘数里面是合数的再分&#xff08;如b3是合数-》b3c1✖c2&#xff09;进…

七星棋牌源码高阶技术指南:6端互通、200+子游戏玩法深度剖析与企业级搭建实战(完全开源)

在棋牌游戏行业高速发展的今天&#xff0c;如何构建一个具备高并发、强稳定性与多功能支持的棋牌游戏系统成为众多开发者和运营团队关注的焦点。七星棋牌全开源修复版源码 凭借其 六端互通、200子游戏玩法、多省区本地化支持&#xff0c;以及 乐豆系统、防沉迷、比赛场、AI智能…

【学习笔记】【SpringCloud】MybatisPlus 基础使用

目录 一、使用 MybatisPlus 基本步骤 1. 引入 MybatisPlus 依赖 2. 定义Mapper接口并继承BaseMapper 二、MybatisPlus 常用配置 三、自定义SQL 四、IService 接口 1. 批量新增的效率问题 2. 配置方式 五、插件功能 1. 分页插件 一、使用 MybatisPlus 基本步骤 1. 引…

QT 引入Quazip和Zlib源码工程到项目中,无需编译成库,跨平台,压缩进度

前言 最近在做项目时遇到一个需求&#xff0c;需要将升级的文件压缩成zip&#xff0c;再进行传输&#xff1b; 通过网络调研&#xff0c;有许多方式可以实现&#xff0c;例如QT私有模块的ZipReader、QZipWriter&#xff1b;或者第三方库zlib或者libzip或者quazip等&#xff1…

在高流量下保持WordPress网站的稳定和高效运行

随着流量的不断增加&#xff0c;网站的稳定和高效运行变得越来越重要&#xff0c;特别是使用WordPress搭建的网站。流量过高时&#xff0c;网站加载可能会变慢&#xff0c;甚至崩溃&#xff0c;直接影响用户体验和网站正常运营。因此&#xff0c;我们需要采取一些有效的措施&am…

linux 安装启动zookeeper全过程及遇到的坑

1、下载安装zookeeper 参考文章&#xff1a;https://blog.csdn.net/weixin_48887095/article/details/132397448 2、启动失败 1、启动失败JAVA_HOME is not set and java could not be found in PATH 已安装 JAVA 配置了JAVA_HOME,还是报错解决方法&#xff1a;参考&#xf…

投资组合风险管理

投资组合风险管理 市场风险 信用风险流动性风险风险指标收益率波动率最大回撤 α \alpha α&#xff08;詹森指数&#xff09;&#xff0c; β \beta β卡玛比率月胜率上/下行捕获比夏普比率索提诺比率经风险调整的收益率&#xff08;&#x1d440;2&#xff09;特雷诺比率信息…

MySQL八股学习笔记

文章目录 一、MySQL结构1.宏观结构1.1.Server层1.2.存储引擎层 2.建立链接-连接器3.查询缓存4.解析SQL-解析器&#xff08;1&#xff09;词法分析&#xff08;2&#xff09;语法分析 5.执行SQL5.1.预处理器 prepare5.2.优化器 optimize5.3.执行器 execute&#xff08;1&#xf…

在windows下安装windows+Ubuntu16.04双系统(下)

这篇文章的内容主要来源于这篇文章&#xff0c;为正式安装windowsUbuntu16.04双系统部分。在正式安装前&#xff0c;若还没有进行前期准备工作&#xff08;1.分区2.制作启动u盘&#xff09;&#xff0c;见《在windows下安装windowsUbuntu16.04双系统(上)》 二、正式安装Ubuntu …