[数据结构 - C++] 红黑树RBTree

在这里插入图片描述

文章目录

  • 1、前言
  • 2、红黑树的概念
  • 3、红黑树的性质
  • 4、红黑树节点的定义
  • 5、红黑树的插入Insert
  • 6、红黑树的验证
  • 7、红黑树与AVL树的比较
  • 附录:

1、前言

我们在学习了二叉搜索树后,在它的基础上又学习了AVL树,知道了AVL树是靠平衡因子来调节左右高度差,从而让树变得平衡的。本篇我们再来学习一个依靠另一种平衡规则来控制的二叉搜索树——红黑树。

2、红黑树的概念

红黑树,是一种二叉搜索树但在每个结点上增加一个存储位表示结点的颜色,可以是Red或Black。 通过对任何一条从根到叶子的路径上各个结点着色方式的限制,红黑树确保没有一条路径会比其他路径长出俩倍,因而是接近平衡的。
在这里插入图片描述

3、红黑树的性质

1. 每个结点不是红色就是黑色
2. 根节点是黑色的
3. 如果一个节点是红色的,则它的两个孩子结点是黑色的
红色不能连续,黑色可以连续
4. 对于每个结点,从该结点到其所有后代叶结点的简单路径上,均 包含相同数目的黑色结点
5. 每个叶子结点都是黑色的(此处的叶子结点指的是空结点)

概念中,说到没有一条路径会比其他路径长出俩倍,性质3与性质4相互牵制就可以保证这一点。

4、红黑树节点的定义

我们定义节点依然是三叉链,与AVL树不同的是红黑树没有平衡因子,而是保存一个代表节点颜色的属性。

enum Color
{
	RED,
	BLACK
};

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

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

这里我们定义的红黑树节点,颜色默认给的红色,但是这里是给红色还是黑色合适呢?
当插入的时候,我们新插入的节点颜色是黑色时,就会破坏性质3,新插入节点的这条路径的黑色节点数一定会比其他路径的黑色节点多一个,影响整棵树。
如果是红色,那插入的时候她的父节点可能是黑色,没有影响,可能是红色,那么就会出现连续的红色节点,但是它只会影响这一条路径。
这两种颜色插入,黑色是一定会影响,红色是可能会影响的,且黑色影响整棵树,红色影响它这一条路径,两害取其轻,我们选择红色,调整的话也比较容易调整。下面我们就来尝试看插入怎么写:

5、红黑树的插入Insert

红黑树的插入是在二叉搜索树插入基础上来修改的,因此大的方向分两步走:
1、找到插入的位置;
2、插入节点后,根据性质来调节平衡。

bool Insert(const pair<K, V>& kv)
{
    if (nullptr == _root)
    {
        _root = new Node(data);
        _root->_col = BLACK; // 性质2:根节点是黑色
        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;
        }
    }
    // 新增插入节点是红色只会影响父节点,如果是黑色影响所有路径
    // 所以 new 的节点为红色
    cur = new Node(kv);
    cur->_col = RED;
    if (parent->_kv.first < kv.first)
    {
        parent->_right = cur;
        cur->_parent = parent;
    }
    else
    {
        parent->_left = cur;
        cur->_parent = parent;
    }
	// 维护处理 ...
}

当来到这块时,已经插入了,要做的就是按照性质来检查和维护这棵树了。
1、当父亲是黑色,那么就不用维护,就结束了;
2、当父亲是红色,那么就违反了性质3(不能存在连续的红色节点),这时就需要调整了,调整也是要分情况讨论(约定:cur为当前节点,p为父节点,g为祖父节点,u为叔叔节点)。
情况一:cur是红色,p为红色,g为黑色,u存在且为红色
这里看到的这棵树可能是完整的,也可能是子树:
在这里插入图片描述

如果是完整的树,那么改完之后,需要将g的颜色改为黑色。
如果g是子树,那么g就有父节点,这时g的颜色改为红色,父节点颜色可能也是红色,这时急需要继续向上调整了。
将p,u改为黑,g改为红,然后把g当作cur,继续向上调整。
情况二:cur是红色,p为红色,g为黑色,u不存在/u存在且为黑色
在这里插入图片描述

这里u有两种颜色,我们分开讨论:

  • 如果u不存在,那么cur一定是新增。因为u不存在g也是有一条右边路径的,这条路径就两个黑色节点空结点也是黑色,那么c就不存在,a、b都不存在,如果存在就是黑色节点,那么就打破了性质3。
  • 如果存在且为黑,那么这个抽象图就不全。因为父节是红色,叔叔为黑色,每条路径的黑色节点个数要相同,因此推测出cur之前应该是黑色,那么a、b就应该是红色,新增节点在a/b的孩子位置。
    整体就为下图:
    在这里插入图片描述

它会先经过情况一的调整方式,调整完变为情况二这样,然后再继续调整:
在这里插入图片描述

此时,就 以g为基点先右旋,然后将父节点颜色变为黑色,祖父节点颜色变为红色。(旋转不清楚的同学可以看看AVL树的)
情况三:cur是红色,p为红色,g为黑色,u不存在/u存在且为黑色
在这里插入图片描述

  • u不存在,cur是新增。因为每条路径的黑色节点个数相同,u不存在,u这条路径上两个黑色节点空结点也是黑色),推测出a、b、c都是不存在的,那么cur就是新增,如果存在只能是黑色节点,那么就打破了性质3。
  • 如果存在且为黑,这个抽象图依然是不完整的。因为父节点是红色,叔叔为黑色,每条路径的黑色节点相同,因此推测出a为黑色,cur之前也应该为黑色,那么b、c就应该是红色,新增节点在b/c的孩子位置。

图跟情况二中,u存在且为黑差不多:
在这里插入图片描述

会先经过情况一调整,变为情况三这样,然后进行调整:
在这里插入图片描述

此时,先以p为基点左旋,再以g为基点右旋,然后将cur节点变为黑色,祖父节点变为红色。
如果新增的父节点在右,叔叔节点在左,那么也是分以上三种情况,调整方式也是对应三种方式差不多,这里就不过多赘述,直接上代码:

bool Insert(const pair<K, V>& kv)
{
    if (nullptr == _root)
    {
        _root = new Node(data);
        _root->_col = BLACK; // 性质2:根节点是黑色
        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;
        }
    }
    
    // 新增插入节点是红色只会影响父节点,如果是黑色影响所有路径
    // 所以 new 的节点为红色
    cur = new Node(kv);
    cur->_col = RED;
    if (parent->_kv.first < kv.first)
    {
        parent->_right = cur;
        cur->_parent = parent;
    }
    else
    {
        parent->_left = cur;
        cur->_parent = parent;
    }

    // 不断向上调整的,所以得用while
    while (parent && parent->_col == RED)
    {
        // 父节点是祖父节点的左
        //      g
        //    p   u
        //  c 
        Node* grandfather = parent->_parent;
        // 1、父节点在祖父的左,即叔叔在右
        if (parent == grandfather->_left)
        {
            Node* uncle = grandfather->_right;
            // 1.1 叔叔存在并且为红
            if (uncle && uncle->_col == RED)
            {
                // 变色
                parent->_col = uncle->_col = BLACK;
                grandfather->_col = RED;

                // 向上调整
                cur = grandfather;
                parent = cur->_parent;
            }
            // 1.2 叔叔不存在 / 叔叔存在且为黑,处理方法一样
            else
            {
                if (cur == parent->_left) // 左边高的情况
                {
                    // 右单旋
                    //     g
                    //   p   u
                    // c
                    RotateR(grandfather);
                    parent->_col = BLACK;
                    grandfather->_col = RED;
                }
                else // 左边高右边高的情况
                {
                    // 双旋
                    //     g
                    //  p     u
                    //    c
                    RotateL(parent);
                    RotateR(grandfather);
                    cur->_col = BLACK;
                    grandfather->_col = RED;
                }
                // 此时不用祖父位置为黑色不用在网上调整了
                break;
            }
        }
        // 2、父节点在祖父的右,即叔叔在祖父的左
        else // parent == grandfather->_right
        {
            //     g
            //   u   p
            //         c
            Node* uncle = grandfather->_left;
            // 2.1 叔叔存在且叔叔为红色
            if (uncle && uncle->_col == RED)
            {
                // 变色
                parent->_col = uncle->_col = BLACK;
                grandfather->_col = RED;

                // 向上调整
                cur = grandfather;
                parent = cur->_parent;
            }
            // 2.2 叔叔不存在 / 叔叔存在且颜色为黑,处理方法一样
            else
            {
                if (cur == parent->_right) // 右边高的情况
                {
                    //     g
                    //   u   p
                    //         c
                    RotateL(grandfather);
                    parent->_col = BLACK;
                    grandfather->_col = RED;
                }
                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;
    Node* parentParent = parent->_parent;

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

    if (_root == parent) // 父节点就是根节点
    {
        _root = subR;
        subR->_parent = nullptr;
    }
    else // 子树情况
    {
        if (parentParent->_left == parent)
        {
            parentParent->_left = subR;
        }
        else
        {
            parentParent->_right = subR;
        }
        subR->_parent = parentParent;
    }
}

// 右单旋
void RotateR(Node* parent)
{
    Node* parentParent = parent->_parent;
    Node* subL = parent->_left;
    Node* subLR = subL->_right;

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

    if (_root == parent) // 父节点是根节点
    {
        _root = subL;
        subL->_parent = nullptr;
    }
    else // 子树情况
    {
        if (parentParent->_left == parent)
        {
            parentParent->_left = subL;
        }
        else
        {
            parentParent->_right = subL;
        }
        subL->_parent = parentParent;
    }
}

6、红黑树的验证

红色树的验证本质就是验证两方面:
1、是否为二叉搜索树(中序遍历是否有序);
2、是否满足5条性质。

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

    _InOrder(pRoot->_left);
    cout << pRoot->_data << " ";
    _InOrder(pRoot->_right);
}

bool IsBalance()
{
    if (_root == nullptr) return true;

    if (_root->_col == RED) return false;

    // 参考值
    int refValue = 0;
    Node* cur = _root;
    while (cur)
    {
        if (cur->_col == BLACK) refValue++;

        cur = cur->_left;
    }
    // 检查每条路径黑色节点个数
    // 思路:以上面参考值为主,对比每条路径的黑色节点个数
    // 当走到空就说明该路径走完了,那么这个过程中记录下黑色节点个数,到空时与refValue对比
    // 这里传进去blacknum只能是传值,这样就不会影响上一层的blacknum了
    int blacknum = 0;
    // 检查连续红色节点与每条路径黑色节点个数
    return Check(_root, blacknum, refValue);
}
bool Check(Node* root, int blacknum, const int& refValue)
{
    if (root == nullptr)
    {
        if (blacknum != refValue)
        {
            cout << "存在黑色节点不相等的路径" << endl;
            return false;
        }

        return true;
    }

    // 反向检查,查看当前与父结点为红色(当前节点为红色就说明不是根节点,即存在父节点)
    if (root->_col == RED && root->_parent->_col == RED)
    {
        cout << "有连续的红色节点" << endl;
        return false;
    }

    if (root->_col == BLACK) blacknum++;

    return Check(root->_left, blacknum, refValue)
        && Check(root->_right, blacknum, refValue);
}

7、红黑树与AVL树的比较

红黑树和AVL树都是高效的平衡二叉树,增删改查的时间复杂度都是O(log_2 N),红黑树不追求绝对平衡,其只需保证最长路径不超过最短路径的2倍,相对而言,降低了插入和旋转的次数,所以在经常进行增删的结构中性能比AVL树更优,而且红黑树实现比较简单,所以实际运用中红黑树更多。

附录:

enum Color
{
	RED,
	BLACK
};

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

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

template <class K, class V>
class RBTree
{
	typedef RBTreeNode<K,V> Node;
public:
	bool Insert(const pair<K, V>& kv)
    {
        if (nullptr == _root)
        {
            _root = new Node(data);
            _root->_col = BLACK; // 性质2:根节点是黑色
            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;
            }
        }
        
        // 新增插入节点是红色只会影响父节点,如果是黑色影响所有路径
        // 所以 new 的节点为红色
        cur = new Node(kv);
        cur->_col = RED;
        if (parent->_kv.first < kv.first)
        {
            parent->_right = cur;
            cur->_parent = parent;
        }
        else
        {
            parent->_left = cur;
            cur->_parent = parent;
        }
    
        // 不断向上调整的,所以得用while
        while (parent && parent->_col == RED)
        {
            // 父节点是祖父节点的左
            //      g
            //    p   u
            //  c 
            Node* grandfather = parent->_parent;
            // 1、父节点在祖父的左,即叔叔在右
            if (parent == grandfather->_left)
            {
                Node* uncle = grandfather->_right;
                // 1.1 叔叔存在并且为红
                if (uncle && uncle->_col == RED)
                {
                    // 变色
                    parent->_col = uncle->_col = BLACK;
                    grandfather->_col = RED;
    
                    // 向上调整
                    cur = grandfather;
                    parent = cur->_parent;
                }
                // 1.2 叔叔不存在 / 叔叔存在且为黑,处理方法一样
                else
                {
                    if (cur == parent->_left) // 左边高的情况
                    {
                        // 右单旋
                        //     g
                        //   p   u
                        // c
                        RotateR(grandfather);
                        parent->_col = BLACK;
                        grandfather->_col = RED;
                    }
                    else // 左边高右边高的情况
                    {
                        // 双旋
                        //     g
                        //  p     u
                        //    c
                        RotateL(parent);
                        RotateR(grandfather);
                        cur->_col = BLACK;
                        grandfather->_col = RED;
                    }
                    // 此时不用祖父位置为黑色不用在网上调整了
                    break;
                }
            }
            // 2、父节点在祖父的右,即叔叔在祖父的左
            else // parent == grandfather->_right
            {
                //     g
                //   u   p
                //         c
                Node* uncle = grandfather->_left;
                // 2.1 叔叔存在且叔叔为红色
                if (uncle && uncle->_col == RED)
                {
                    // 变色
                    parent->_col = uncle->_col = BLACK;
                    grandfather->_col = RED;
    
                    // 向上调整
                    cur = grandfather;
                    parent = cur->_parent;
                }
                // 2.2 叔叔不存在 / 叔叔存在且颜色为黑,处理方法一样
                else
                {
                    if (cur == parent->_right) // 右边高的情况
                    {
                        //     g
                        //   u   p
                        //         c
                        RotateL(grandfather);
                        parent->_col = BLACK;
                        grandfather->_col = RED;
                    }
                    else // 右边高,左边高
                    {
                        //     g
                        //  u     p
                        //      c
                        RotateR(parent);
                        RotateL(grandfather);
                        cur->_col = BLACK;
                        grandfather->_col = RED;
                    }
                    // 此时不用祖父位置为黑色不用在网上调整了
                    break;
                }
            }
        }
        
        // 最后将根节点变为黑色
        _root->_col = BLACK;
        return true;
    }
    
	bool Find(const K& key)
	{
		Node* cur = _root;
		while (cur)
		{
			if (cur->_key < key)
			{
				cur = cur->_right;
			}
			else if (cur->_key > key)
			{
				cur = cur->_left;
			}
			else
			{
				return true;
			}
		}
		return true;
	}

	bool IsBalance()
	{
		if (_root == nullptr) return true;

		if (_root->_col == RED) return false;

		// 参考值
		int refValue = 0;
		Node* cur = _root;
		while (cur)
		{
			if (cur->_col == BLACK) refValue++;

			cur = cur->_left;
		}
		// 检查每条路径黑色节点个数
		// 思路:以上面参考值为主,对比每条路径的黑色节点个数
		// 当走到空就说明该路径走完了,那么这个过程中记录下黑色节点个数,到空时与refValue对比
		// 这里传进去blacknum只能是传值,这样就不会影响上一层的blacknum了
		int blacknum = 0;
		// 检查连续红色节点与每条路径黑色节点个数
		return Check(_root, blacknum, refValue);
	}

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

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

private:
	bool Check(Node* root, int blacknum, const int& refValue)
	{
		if (root == nullptr)
		{
			if (blacknum != refValue)
			{
				cout << "存在黑色节点不相等的路径" << endl;
				return false;
			}

			return true;
		}

		// 反向检查,查看当前与父结点为红色(当前节点为红色就说明不是根节点,即存在父节点)
		if (root->_col == RED && root->_parent->_col == RED)
		{
			cout << "有连续的红色节点" << endl;
			return false;
		}

		if (root->_col == BLACK) blacknum++;

		return Check(root->_left, blacknum, refValue)
			&& Check(root->_right, blacknum, refValue);
	}

	// 左单旋
	void RotateL(Node* parent)
	{
		Node* subR = parent->_right;
		Node* subRL = subR->_left;
		Node* parentParent = parent->_parent;

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

		if (_root == parent) // 父节点就是根节点
		{
			_root = subR;
			subR->_parent = nullptr;
		}
		else // 子树情况
		{
			if (parentParent->_left == parent)
			{
				parentParent->_left = subR;
			}
			else
			{
				parentParent->_right = subR;
			}
			subR->_parent = parentParent;
		}
	}

	// 右单旋
	void RotateR(Node* parent)
	{
		Node* parentParent = parent->_parent;
		Node* subL = parent->_left;
		Node* subLR = subL->_right;

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

		if (_root == parent) // 父节点是根节点
		{
			_root = subL;
			subL->_parent = nullptr;
		}
		else // 子树情况
		{
			if (parentParent->_left == parent)
			{
				parentParent->_left = subL;
			}
			else
			{
				parentParent->_right = subL;
			}
			subL->_parent = parentParent;
		}
	}

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

		_InOrder(pRoot->_left);
		cout << pRoot->_data << " ";
		_InOrder(pRoot->_right);
	}

	size_t _Height(Node* pRoot)
	{
		if (pRoot == nullptr)
			return 0;

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

		return leftHeight > rightHeight ? leftHeight + 1 : rightHeight + 1;
	}

private:
	Node* _root = nullptr;
};

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

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

相关文章

Mybatis之关联

一、一对多关联 eg&#xff1a;一个用户对应多个订单 建表语句 CREATE TABLE t_customer (customer_id INT NOT NULL AUTO_INCREMENT, customer_name CHAR(100), PRIMARY KEY (customer_id) ); CREATE TABLE t_order ( order_id INT NOT NULL AUTO_INCREMENT, order_name C…

git克隆/拉取报错过早的文件结束符(EOF)的原因及解决

近期使用git拉取仓库的时候&#xff0c;拉取了好几次都不行&#xff0c;总是反馈说过早的文件结束符 总是这样&#xff0c;当然我的报错信息并没有描述完整&#xff0c;因为在我检索此类问题的时候&#xff0c;我发现有好多种所谓的过早的文件结束符这样的报错&#xff0c;但是…

机器学习实验报告——EM算法

目录 一、算法介绍 1.1算法背景 1.2算法引入 1.3算法假设 1.4算法原理 1.5算法步骤 二、算法公式推导 2.1数学基础 2.2EM算法推导 三、算法实现 3.1关于EM聚类 3.2EM工具包的使用 3.3 实例测试 四、算法讨论 4.1EM算法的优缺点 4.2EM算法的应用 4.3对于EM算法…

RT Thread Stdio生成STM32L431RCT6无法启动问题

一、问题现象 使用RT thread Stdio生成STM32L431RCT6工程后&#xff0c;编译下载完成后系统无法启动&#xff0c;无法仿真debug&#xff1b; 二、问题原因 如果当前使用的芯片支持包版本为0.2.3&#xff0c;可能是这个版本问题&#xff0c;目前测试0.2.3存在问题&#xff0c…

【51单片机】外部中断

0、前言 参考&#xff1a;普中 51 单片机开发攻略 第16章 及17章 1、硬件 2、软件 #include <reg52.h> #include <intrins.h> #include "delayms.h"typedef unsigned char u8; typedef unsigned int u16;sbit led P2^0; sbit key3 P3^2;//外部中断…

晨控CK-FR03-EC与欧姆龙NX系列EtherCAT通讯连接说明手册

晨控CK-FR03-EC与欧姆龙NX系列EtherCAT通讯连接说明手册 晨控CK-FR03-EC是一款基于射频识别技术的高频RFID标签读卡器&#xff0c;读卡器工作频率为13.56MHZ&#xff0c;支持对I-CODE 2、I-CODE SLI等符合ISO15693国际标准协议格式标签的读取。 读卡器同时支持标准工业通讯协…

机器学习周记(第二十六周:文献阅读-DPGCN)2024.1.15~2024.1.21

目录 摘要 ABSTRACT 1 论文信息 1.1 论文标题 1.2 论文摘要 1.3 论文背景 2 论文模型 2.1 问题描述 2.2 论文模型 2.2.1 时间感知离散图结构估计&#xff08;Time-aware Discrete Graph Structure Estimation Module&#xff0c;TADG Module&#xff09; 2.2.2 时间…

HNU-数据挖掘-实验2-数据降维与可视化

数据挖掘课程实验实验2 数据降维与可视化 计科210X 甘晴void 202108010XXX 文章目录 数据挖掘课程实验<br>实验2 数据降维与可视化实验背景实验目标实验数据集说明实验参考步骤实验过程1.对数据进行初步降维2.使用无监督数据降维方法&#xff0c;比如PCA&#xff0c;I…

网络要素服务(WFS)详解

文章目录 1. 概述2. GetCapabilities3. DescribeFeatureType4. GetFeature4.1 Get访问方式4.2 Post访问方式 5. Transaction5.1 Insert5.2 Replace5.3 Update5.4 Delete 6 注意事项 1. 概述 前置文章&#xff1a; 地图服务器GeoServer的安装与配置 GeoServer发布地图服务&#…

外汇天眼:每一个骗局的背后,可能是倾家荡产!

在网络科技还没有发达的以前&#xff0c;骗子主要通过线下揽客的方式推荐各类虚假的投资理财项目&#xff0c;有的甚至打着专门的理财咨询机构吸引了一大批新手投资者。在当时&#xff0c;外汇投资还不为多数人知道&#xff0c;随便忽悠“高利益保本”就有投资者上当受骗。 现如…

LINUX常用工具之sudo权限控制

一、Sudo基本介绍 sudo是Linux 中用于允许特定用户以超级用户或其他特权用户的身份执行特定的命令或任务。sudo 提供了一种安全的方法&#xff0c;使用户能够临时获取额外的权限&#xff0c;而不需要以完全超级用户的身份登录系统。sudo也可以用了设置黑名单命令清单&#xff…

多列匹配,根据对应状态、排序字段

多列匹配&#xff0c;根据对应状态、排序字段 数据&#xff1a; 查询:

Golang leetcode28 找出字符串中第一个匹配项的下标 KMP算法详解

文章目录 找出字符串中第一个匹配项的下标 leetcode28 串的模式匹配问题暴力求解使用KMP模式匹配算法KMP算法简述 KMP算法的代码实现 找出字符串中第一个匹配项的下标 leetcode28 串的模式匹配问题 暴力求解 func strStr(haystack string, needle string) int { L : len(need…

大模型笔记【3】 gem5 运行模型框架LLama

一 LLama.cpp LLama.cpp 支持x86&#xff0c;arm&#xff0c;gpu的编译。 1. github 下载llama.cpp https://github.com/ggerganov/llama.cpp.git 2. gem5支持arm架构比较好&#xff0c;所以我们使用编译LLama.cpp。 以下是我对Makefile的修改 开始编译&#xff1a; make UNAME…

用pandas实现用前一行的excel的值填充后一行

今天接到一份数据需要分析&#xff0c;数据在一个excel文件里&#xff0c;内容大概形式如下&#xff1a; 后面空的格子里的值就是默认是前面的非空的值&#xff0c;由于数据分析的需要需要对重复的数据进行去重&#xff0c;去重就需要把控的cell的值补上&#xff0c;然后根据几…

2024 前端高频面试题之 JS 篇

JS 篇&#xff08;持续更新中&#xff09; 1、什么是原型、原型链&#xff1f;2、什么是继承&#xff1f;说一说有哪些&#xff1f;继承组合的原理及优点&#xff1f;3、new 操作符具体干了什么&#xff1f;4、js 有哪些方法改变 this 指向&#xff1f;5、bind 有哪些实现的注意…

时空预测网络ST-Resnet 代码复现

ST-ResNet&#xff08;Spatio-Temporal Residual Network&#xff09;是一种用于处理时空数据的深度学习模型&#xff0c;特别适用于视频、时间序列等具有时空结构的数据。下面是一个简单的使用PyTorch搭建ST-ResNet的示例代码。请注意&#xff0c;这只是一个基本的示例&#x…

一文了解国密算法SSL证书

国密算法是中国国家密码管理局发布的一组密码算法标准&#xff0c;用于替代国际上的一些加密算法。在SSL证书中&#xff0c;使用国密算法的证书通常称为"国密SSL证书"。 国密SSL证书与传统的SSL证书在加密算法上有所不同。传统SSL证书通常使用的是RSA或者ECC&#xf…

QQ失效的图片怎么恢复?这3种方法送给大家!

在我们使用QQ时&#xff0c;难免会遇到QQ图片失效的情况。或许是因为误删了聊天记录&#xff0c;又或许是因为没有及时保存而导致失效。 那么&#xff0c;面对这些失效的图片&#xff0c;我们该如何恢复呢&#xff1f;qq失效的图片怎么恢复&#xff1f;今天&#xff0c;小编将…

一起读《奔跑吧Linux内核(第2版)卷1:基础架构》- 了解kmalloc、vmalloc、malloc

大家好&#xff0c;我是硬核王同学&#xff0c;最近在做免费的嵌入式知识分享&#xff0c;帮助对嵌入式感兴趣的同学学习嵌入式、做项目、找工作&#xff01; 移步飞书获得更好阅读体验&#xff1a; Docs Hello&#xff0c;大家好我是硬核王同学&#xff0c;是一名刚刚工作一年…