c++的学习之路:26、AVL树

摘要

本章主要是说一下AVL树的实现,这里说的是插入的底层原理

目录

摘要

一、原理

二、四种旋转

1、左单旋

2、右单旋

3、左右双旋

4、右左双旋

三、代码实现

1、节点创建

2、插入

3、旋转

4、判断是否平衡

5、测试

 

四、代码


一、原理

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

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

下面的图就是一颗AVL树,画的有点抽象,0、-1、1就是平衡因子

二、四种旋转

1、左单旋

如下图就是左单选的过程,就是b肯定是比30大,这时就可以把b连接到30的右边,然后把60变成根节点,30变成60的左节点,就是如下图这种变化,然后这时就需要进行更新平衡因子,就可以进行左旋了,电脑画图太麻烦了,我就放一下我的手图了。

 

2、右单旋

右单选,与做单选相反就是把右边的进行放在左边,然后原理都差不多,就是b的位置肯定是比60小然后就放到30左边,然后在进行旋转进行连接在,这样就可以右旋了,这里我就不画电脑的图了,用我的手图代替了

3、左右双旋

左右双旋就是先进行做单选在进行右单选,这种情况的形状就是<就是这种的就是一个小于号,当直接左旋或者右旋就会把树给扭曲了,就不是平衡了们就是先把他变成/这种形状然后在进行右单选就可以平衡这个二叉树了,下面就是我画的图了,就不用电脑画了。

4、右左双旋

这里就是和上面那个双旋相反,就是先把>这个形状变成\这种,然后就是先进行右单选,在进行做单选,就可以把这颗树进行平衡了,这里就不进行画图了,因为原理都差不多。

三、代码实现

1、节点创建

这个节点是利用三叉链进行维护的,数据存储也就是和搜索二叉树的原理,因为搜索二叉树会右歪脖子树,所以这里就只是平衡,但是他的原理还是差不多,所以这里也是利用键值对进行一个pair容器的创建,然后在创建一个bf进行充当平衡因子进行维护。

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;//平衡因子,用来平衡AVL树,使他相对像完全二叉树
    AVLtreeNode(const pair<K, V>& kv)
        :_left(nullptr)
        , _right(nullptr)
        , _parent(nullptr)
        , _kv(kv)
        , _bf(0)
    {}
};

2、插入

插入的代码就是和搜索二叉树原理差不多,也就是大于在右边小于在左边,这里创建也是先进行插入,没有节点的时候就插入在根,有的时候就去遍历寻找,大的在右边小的在左边,然后进行插入,在进行更新平衡因子,这里是利用一个节点记录当前的父节点时候,就可以进行循环进行更新平衡因子,等于2或者-2的时候就需要进行旋转了,这时有四种情况

1、父节点=2、当前节点=1就是进行左单旋

2、父节点=-2、当前节点=-1就是右单旋

3、父节点=2、当前节点=-1时,就是双旋了,左右双旋

4、父节点=-1、当前节点=1时,就是右左双旋

下方代码就是我写的插入,也有注释

bool Insert(const pair<K, V>& kv)
    {
        if (_root == nullptr)//如果是空节点直接申请,然后返回true
        {
            _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;//找不到就返回false
            }
        }
        cur = new Node(kv);//创建新的节点
        if (parent->_kv.first > kv.first)//如果小于就插入在右边
        {
            parent->_left = cur;
        }
        else if (parent->_kv.first < kv.first)//如果大于就插入到左边
        {
            parent->_right = cur;
        }
        cur->_parent = parent;//记录一下父节点的地址
        while (parent)//平衡因子的更新,持续到根
        {
            if (cur == parent->_right)//判断新的节点在父节点的左右,如果在右边平衡因子就++,如果在左边就--
            {
                parent->_bf++;
            }
            else
            {
                parent->_bf--;
            }
            if (parent->_bf == 1 || parent->_bf == -1)//如果父节点的平衡因子等于1或者-1,接着更新
            {
                parent = parent->_parent;
                cur = cur->_parent;
            }
            else if(parent->_bf==0)//如果平衡因子等于0就退出
            {
                break;
            }
            else if (parent->_bf == 2 || parent->_bf == -2)//超长了,准备旋转了,有四种情况
            {
                if (parent->_bf == 2 && cur->_bf == 1)//当父节点的平衡因子等于2当前节点的平衡因子等于1时                
                {                                      //就是相当于右边是一条直线时,然后进行左旋
                    RotateL(parent);//调用左旋函数
                }
                else if (parent->_bf == -2 && cur->_bf == -1)//当父平衡因子等于-2当前节点平衡因子等于-1时
                {                                                //就是左边时一条直线的情况时,然后进行右旋
                    RotateR(parent);//调用右旋函数
                }
                else if (parent->_bf == -2 && cur->_bf == 1)//当父节点平衡因子等于-2时,当前节点的平衡因子等于-1时
                {                                            //就是相当于一条折现,就是<这个形状,需要旋转两次,先左旋在右旋
                    RotateLR(parent);//调用先左旋在右旋的函数
                }
                else if (parent -> _bf == 2 && cur->_bf == -1)//当父节点平衡因子等于2时,当前节点平衡因子等于-1时
                {                                            //就是相当于>这个形状,也是需要旋转两次,先进性右旋在进行左旋
                    RotateRL(parent);//调用先右旋在左旋的函数
                }
                else
                {
                    assert(false);//上述情况都没出现,就说明树已经出现问题了,这里直接利用断言直接报错,断死
                }
                break;
            }
            else
            {
                assert(false);//这里就是更新平衡因子失败,也就是说上述都没,也就是直接报错,因为树也是出现错误了
            }
        }
    return true;
    }

3、旋转

下面这段代码就是四种旋转,在左右单旋后,更新平衡因子是0,双旋的平衡因子需要根据具体条件具体实现,具体怎么是先把的下面都有注释。

void RotateL(Node* parent)//左旋
    {
        Node* subR = parent->_right;//记录父节点的右侧,用来后续旋转
        Node* subRL = subR->_left;//记录subR的左侧,这个节点比父节点大,比subR小
        parent->_right = subRL;//因为这个节点比父节点大,所以连接父节点在右边
        if (subRL)
            subRL->_parent = parent;//如果subRL节点不是空姐点就把他的父节点置为父节点
        Node* ppnode = parent->_parent;//记录父节点的父节点
        subR->_left = parent;//旋转把subR的左节点连接为父节点,因为父节点比subR节点小
        parent->_parent = subR;//把父节点的父节点置为subR
        if (ppnode == nullptr)//判断ppnode是否为空也就是之前的父节点是否为根
        {
            _root = subR;//如果是根的话,就把subR置为根,在把subR的父节点置为空
            _root->_parent = nullptr;
        }
        else
        {
            if (ppnode->_left == parent)//判断ppnode的左节点是否为之前的父节点,如果是就把位置换成subR
            {
                ppnode->_left = subR;
            }
            else
            {
                ppnode->_right = subR;//判断ppnode的右节点是否为之前的父节点,如果是就把位置换成subR
            }

            subR->_parent = ppnode;//在进行连接subR的父节点
        }
        parent->_bf = subR->_bf = 0;//最后更新平衡因子,因为旋转过后肯定是平衡的所以直接置为0
    }

    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;
        }
        subL->_bf = parent->_bf = 0;
    }

    void RotateLR(Node* parent)//双旋之先左旋在右旋
    {
        Node* subL = parent->_left;//记录父节点的左侧
        Node* subLR = subL->_right;//记录subL的右侧因为形状是<这样的,然后需要先左旋进行掰直
        int bf = subLR->_bf;//用于记录平衡因子
        RotateL(parent->_left);//利用左旋函数进行左旋,这里就是把父节点的左侧当成父节点传进去,也就是subL
        RotateR(parent);//然后在进行右旋
        if (bf == 1)//更新平衡因子,当前平衡因子如果等于1就把父节点的平衡因子置为0,subLR的为0,因为这个节点在旋转过后
        {            //就会平衡,但是在bf等于1时,左边肯定是有的,所以subL就是-1
            parent->_bf = 0;
            subLR->_bf = 0;
            subL->_bf = -1;
        }
        else if (bf == -1) //在旋转之前平衡因子是-1 的话,在旋转结束时,也就是说明父节点的位置就是需要置为1,因为之前
        {                    //在左,旋转之后就是有右,其他两个就是0
            parent->_bf = 1;
            subLR->_bf = 0;
            subL->_bf = 0;
        }
        else if (bf == 0)//如果都是0的话旋转后也是0就直接更新成0
        {
            parent->_bf = 0;
            subLR->_bf = 0;
            subL->_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)
        {
            subR->_bf = 0;
            parent->_bf = -1;
            subRL->_bf = 0;
        }
        else if (bf == -1)
        {
            subR->_bf = 1;
            parent->_bf = 0;
            subRL->_bf = 0;
        }
        else if (bf == 0)
        {
            subR->_bf = 0;
            parent->_bf = 0;
            subRL->_bf = 0;
        }
        else
        {
            assert(false);
        }
    }

4、判断是否平衡

这里还是进行一个封装,因为不太好穿私有变量,所以这里也就是利用函数进行封装了,如下方代码所示,是否出现错误就是判断高度差,也就是左右高度差不超过2,这里如下方代码所示,在代码旁边注释了了我的思路

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

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

    int Height()
    {
        return _Height(_root);
    }
private:
    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 _IsBalance(Node* root)//判断是否出现异常,高度差在2以内
    {
        if (root == NULL)
        {
            return true;
        }
        int leftH = _Height(root->_left);
        int rightH = _Height(root->_right);
        if (rightH - leftH != root->_bf)//判断平衡因子是否出错
        {
            cout << root->_kv.first << "节点平衡因子异常" << endl;
            return false;
        }
        return abs(leftH - rightH) < 2//这里是因为不知道哪个大所以直接求绝对值
            && _IsBalance(root->_left)//因为每条路都需要求一下所以直接递归去求
            && _IsBalance(root->_right);
    }

    void _InOrder(Node* root)//中序遍历
    {
        if (root == nullptr)
        {
            return;
        }
        _InOrder(root->_left);
        cout << root->_kv.first << " ";
        _InOrder(root->_right);
    }

5、测试

这里是利用随机数进行测试是否是平衡,如下方代码和测试结果。

void test()
{
    srand(time(0));
    const size_t N = 500000;
    AVLTree<int, int> t;
    for (size_t i = 0; i < N; ++i)
    {
        size_t x = rand() + i;
        t.Insert(make_pair(x, x));
        //cout << t.IsBalance() << endl;
    }

    //t.Inorder();

    cout << t.IsBalance() << endl;
    cout << t.Height() << endl;
}

 

 

四、代码

#pragma once

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;//平衡因子,用来平衡AVL树,使他相对像完全二叉树
	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)
	{
		if (_root == nullptr)//如果是空节点直接申请,然后返回true
		{
			_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;//找不到就返回false
			}
		}
		cur = new Node(kv);//创建新的节点
		if (parent->_kv.first > kv.first)//如果小于就插入在右边
		{
			parent->_left = cur;
		}
		else if (parent->_kv.first < kv.first)//如果大于就插入到左边
		{
			parent->_right = cur;
		}
		cur->_parent = parent;//记录一下父节点的地址
		while (parent)//平衡因子的更新,持续到根
		{
			if (cur == parent->_right)//判断新的节点在父节点的左右,如果在右边平衡因子就++,如果在左边就--
			{
				parent->_bf++;
			}
			else
			{
				parent->_bf--;
			}
			if (parent->_bf == 1 || parent->_bf == -1)//如果父节点的平衡因子等于1或者-1,接着更新
			{
				parent = parent->_parent;
				cur = cur->_parent;
			}
			else if(parent->_bf==0)//如果平衡因子等于0就退出
			{
				break;
			}
			else if (parent->_bf == 2 || parent->_bf == -2)//超长了,准备旋转了,有四种情况
			{
				if (parent->_bf == 2 && cur->_bf == 1)//当父节点的平衡因子等于2当前节点的平衡因子等于1时				
				{									  //就是相当于右边是一条直线时,然后进行左旋
					RotateL(parent);//调用左旋函数
				}
				else if (parent->_bf == -2 && cur->_bf == -1)//当父平衡因子等于-2当前节点平衡因子等于-1时
				{												//就是左边时一条直线的情况时,然后进行右旋
					RotateR(parent);//调用右旋函数
				}
				else if (parent->_bf == -2 && cur->_bf == 1)//当父节点平衡因子等于-2时,当前节点的平衡因子等于-1时
				{											//就是相当于一条折现,就是<这个形状,需要旋转两次,先左旋在右旋
					RotateLR(parent);//调用先左旋在右旋的函数
				}
				else if (parent -> _bf == 2 && cur->_bf == -1)//当父节点平衡因子等于2时,当前节点平衡因子等于-1时
				{											//就是相当于>这个形状,也是需要旋转两次,先进性右旋在进行左旋
					RotateRL(parent);//调用先右旋在左旋的函数
				}
				else
				{
					assert(false);//上述情况都没出现,就说明树已经出现问题了,这里直接利用断言直接报错,断死
				}
				break;
			}
			else
			{
				assert(false);//这里就是更新平衡因子失败,也就是说上述都没,也就是直接报错,因为树也是出现错误了
			}
		}
	return true;
	}

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

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

	int Height()
	{
		return _Height(_root);
	}
private:
	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 _IsBalance(Node* root)//判断是否出现异常,高度差在2以内
	{
		if (root == NULL)
		{
			return true;
		}
		int leftH = _Height(root->_left);
		int rightH = _Height(root->_right);
		if (rightH - leftH != root->_bf)//判断平衡因子是否出错
		{
			cout << root->_kv.first << "节点平衡因子异常" << endl;
			return false;
		}
		return abs(leftH - rightH) < 2//这里是因为不知道哪个大所以直接求绝对值
			&& _IsBalance(root->_left)//因为每条路都需要求一下所以直接递归去求
			&& _IsBalance(root->_right);
	}

	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;//记录subR的左侧,这个节点比父节点大,比subR小
		parent->_right = subRL;//因为这个节点比父节点大,所以连接父节点在右边
		if (subRL)
			subRL->_parent = parent;//如果subRL节点不是空姐点就把他的父节点置为父节点
		Node* ppnode = parent->_parent;//记录父节点的父节点
		subR->_left = parent;//旋转把subR的左节点连接为父节点,因为父节点比subR节点小
		parent->_parent = subR;//把父节点的父节点置为subR
		if (ppnode == nullptr)//判断ppnode是否为空也就是之前的父节点是否为根
		{
			_root = subR;//如果是根的话,就把subR置为根,在把subR的父节点置为空
			_root->_parent = nullptr;
		}
		else
		{
			if (ppnode->_left == parent)//判断ppnode的左节点是否为之前的父节点,如果是就把位置换成subR
			{
				ppnode->_left = subR;
			}
			else
			{
				ppnode->_right = subR;//判断ppnode的右节点是否为之前的父节点,如果是就把位置换成subR
			}

			subR->_parent = ppnode;//在进行连接subR的父节点
		}
		parent->_bf = subR->_bf = 0;//最后更新平衡因子,因为旋转过后肯定是平衡的所以直接置为0
	}

	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;
		}
		subL->_bf = parent->_bf = 0;
	}

	void RotateLR(Node* parent)//双旋之先左旋在右旋
	{
		Node* subL = parent->_left;//记录父节点的左侧
		Node* subLR = subL->_right;//记录subL的右侧因为形状是<这样的,然后需要先左旋进行掰直
		int bf = subLR->_bf;//用于记录平衡因子
		RotateL(parent->_left);//利用左旋函数进行左旋,这里就是把父节点的左侧当成父节点传进去,也就是subL
		RotateR(parent);//然后在进行右旋
		if (bf == 1)//更新平衡因子,当前平衡因子如果等于1就把父节点的平衡因子置为0,subLR的为0,因为这个节点在旋转过后
		{			//就会平衡,但是在bf等于1时,左边肯定是有的,所以subL就是-1
			parent->_bf = 0;
			subLR->_bf = 0;
			subL->_bf = -1;
		}
		else if (bf == -1) //在旋转之前平衡因子是-1 的话,在旋转结束时,也就是说明父节点的位置就是需要置为1,因为之前
		{					//在左,旋转之后就是有右,其他两个就是0
			parent->_bf = 1;
			subLR->_bf = 0;
			subL->_bf = 0;
		}
		else if (bf == 0)//如果都是0的话旋转后也是0就直接更新成0
		{
			parent->_bf = 0;
			subLR->_bf = 0;
			subL->_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)
		{
			subR->_bf = 0;
			parent->_bf = -1;
			subRL->_bf = 0;
		}
		else if (bf == -1)
		{
			subR->_bf = 1;
			parent->_bf = 0;
			subRL->_bf = 0;
		}
		else if (bf == 0)
		{
			subR->_bf = 0;
			parent->_bf = 0;
			subRL->_bf = 0;
		}
		else
		{
			assert(false);
		}
	}
	Node* _root = nullptr;
};

void test()
{
	srand(time(0));
	const size_t N = 500000;
	AVLTree<int, int> t;
	for (size_t i = 0; i < N; ++i)
	{
		size_t x = rand() + i;
		t.Insert(make_pair(x, x));
		//cout << t.IsBalance() << endl;
	}

	//t.Inorder();

	cout << t.IsBalance() << endl;
	cout << t.Height() << endl;
}

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

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

相关文章

刷代码随想录有感(38):N叉树的层序遍历

题干&#xff1a; 代码&#xff1a; // Definition for a Node. class Node { public:int val;vector<Node*> children;Node(int _val, vector<Node*> _children) {val _val;children _children;} };class Solution { public:vector<vector<int>> l…

Github Coplit的认证及其在JetBrains中的使用

原文地址&#xff1a;Github Coplit的认证及其在JetBrains中的使用 - Pleasure的博客 下面是正文内容&#xff1a; 前言 今天分享一个可有可无的小技巧&#xff0c;水一篇文。 如标题所述&#xff0c;Github Coplit的认证及其在JetBrains中的使用 正文 介绍JetBrains JetBrain…

了解MySQL InnoDB多版本MVCC(Multi-Version Concurrency Control)

了解MySQL InnoDB多版本MVCC&#xff08;Multi-Version Concurrency Control&#xff09; 在数据库管理系统中&#xff0c;多版本并发控制&#xff08;MVCC&#xff09;是一种用于实现高并发和事务隔离的技术。MySQL的InnoDB存储引擎支持MVCC&#xff0c;这使得它可以在提供高…

Elasticsearch 开放 inference API 增加了对 OpenAI chat completions 的支持

作者&#xff1a;Tim Grein 我们很高兴地宣布在 Elasticsearch 中推出的最新创新&#xff1a;在 Elastic 的 inference API 中集成了 OpenAI Chat Completions 功能。这一新特性标志着我们在整合尖端人工智能能力至 Elasticsearch 的旅程中又迈出了一步&#xff0c;提供了生成类…

前端css中border-radius的简单使用

前端css中border-radius的使用 一、前言二、border-radius语法三、border-radius的模型例子1.源码12.源码1效果截图 四、border-radius的动画效果&#xff08;动态交互&#xff09;1.源码22.源码2显示效果 五、结语六、定位日期 一、前言 在CSS中&#xff0c;我们常用border-r…

Mac多媒体播放器 Movist Pro v2.11.4中文激活版下载

Movist Pro for Mac是一款专业的媒体播放器&#xff0c;特别为Mac用户设计。它不仅界面简洁美观&#xff0c;而且功能强大&#xff0c;能满足用户各种播放需求。 Movist Pro v2.11.4中文激活版下载 首先&#xff0c;Movist Pro for Mac支持多种媒体文件的播放&#xff0c;包括视…

Ubuntu系统安装Anaconda

1. 下载Anconda安装包 1.1 wget命令下载 当然还可以去清华大学开源软件镜像站&#xff1a;Index of /anaconda/archive/ | 清华大学开源软件镜像站 | Tsinghua Open Source Mirror&#xff0c;下载各种版本的Anaconda。 wget下载命令如下&#xff1a; 我这里下载的是2024.02…

UART串口通信

基本原理与概念 串口通信在不同的硬件上的具体表现不同&#xff0c;但基本原理与操作流程都差不多&#xff1a; UART是一种串行、异步、全双工的通信协议&#xff0c;将所需传输的数据一位接一位地传输&#xff0c;在UART 通讯协议中信号线上的状态位高电平代表’1’&#x…

c++——类和对象(1)构造,析构函数

类的六个默认函数 如果一个类当中没有成员的话&#xff0c;那叫空类&#xff0c;实际上空类有6个编译器默认生成的函数成员 默认成员函数&#xff1a;没有显示实现&#xff0c;编译器生成的成员函数称为默认成员函数 1&#xff0c;构造函数与构析函数 1.1构造函数的概念 构造…

c语言之字符串的集合存放形式

采用指针分配的二维数组与直接定义的二维数组&#xff0c;sizeof的不同 采用指针分配的二维数组&#xff1a; 它的遍历方式是&#xff1a; 上面这个是分配二级指针的地址&#xff0c;二级指针就是一片可以用来分配一级指针空间的地址&#xff0c;然后指针寻址本来就可以当成数组…

java算法day3

移除链表元素设计链表翻转链表两两交换链表中的结点 移除链表元素 ps&#xff1a;有时候感觉到底要不要写特判&#xff0c;你想到了就写&#xff01;因为一般特判有一劳永逸的作用。 解法有两种&#xff0c;一种是不用虚拟头结点&#xff0c;另一种就是用虚拟头结点。 这里我…

python安装的详细步骤

下载 1.打开Python官网.我们建议工具类的测试软件还是官网下载比较靠谱. https://www.python.org/getit/ 2.在下图界面中选择需要的方式进行点击 3.直接点击下载.可以进入保存界面,进行保存即可下载,后续安装 4.鼠标放在Downloads显示平台和版本选择界面,点击Windows,进入wi…

Spring-dataSource事务案例分析-使用事务嵌套时,一个我们容易忽略的地方

场景如下&#xff1a; A_Bean 中的方法a()中调用B_Bean的b();方法都开启了事务&#xff0c;使用的默认的事务传递机制&#xff08;即&#xff1a;属于同一事务&#xff09;&#xff1b; 如下两种场景会存在较大的差异&#xff1a; 在b()方法中出现了异常&#xff0c;在b()中进…

Unity射击游戏开发教程:(2)实例化和销毁游戏对象

现在我们有了“飞船”,我们可以在屏幕上移动它,现在我们需要发射一些激光!与宇宙飞船一样,我们将让事情变得简单并使用 Unity 自己的基本形状。舱体的效果很好,所以我们来创建一个。 我们保存了有关位置、旋转和缩放的信息。我们想要缩小这个对象,假设每个轴上缩小到 0.2…

【树莓派学习】hello,world!

系统安装及环境配置详见【树莓派学习】系统烧录及VNC连接、文件传输-CSDN博客 树莓派内置python3&#xff0c;可以直接利用python输出。

Jetson nx 外接OLED屏幕

40 针 GPIO 引脚 GPIO引脚可以用作输入或输出端口&#xff0c;它们提供了一个数字电平以使用户在外界设备上进行控制或读取。Jetson TX2 NX共有198个GPIO引脚&#xff0c;分为三个不同的管脚组&#xff1a;J1、J21和J22。每个管脚组都具有数字输入/输出和PWM功能。 以下是 TX2…

语音转换中的扩散模型——DDDM-VC

DDDM-VC: Decoupled Denoising Diffusion Models with Disentangled Representation and Prior Mixup for Verifed Robust Voice Conversion https://ojs.aaai.org/index.php/AAAI/article/view/29740https://ojs.aaai.org/index.php/AAAI/article/view/29740 1.概述 首先,语…

谷歌Gemini 1.5 Pro国内怎么用?国内镜像来了

长期以来&#xff0c;许多人向我咨询是否存在一个稳定而高效的全球AI大模型测试平台&#xff0c;这个平台需要不仅真实可靠&#xff0c;而且能够提供稳定和快速的服务&#xff0c;不会频繁出现故障或响应缓慢的问题。然而&#xff0c;当我发现了AskManyAI时&#xff0c;我被其所…

ModuleNotFoundError: No module named ‘scripts.animatediff_mm‘ 解决方案

本文收录于《AI绘画从入门到精通》专栏,专栏总目录:点这里,订阅后可阅读专栏内所有文章。 大家好,我是水滴~~ 本文主要介绍在使用 Stable Diffusion WebUI 安装 AnimateDiff 插件后出现的ModuleNotFoundError: No module named scripts.animatediff_mm异常的解决方案,希望…

初识ansible变量及实例配置

目录 1、为什么要使用变量 2、变量分类 3、 变量详解 3.1 vars,vars_files , group_vars 3.1 .1 vars 剧本中定义变量 3.1.2 vars_file 将变量存放到一个文件中&#xff0c;并在剧本中引用 3.1.3 group_vars 创建一个变量文件给某个组使用 实例1-根据不同的主机…