「C++」红黑树的插入(手撕红黑树系列)

在这里插入图片描述

💻文章目录

  • 📄前言
  • 红黑树
    • 概念
    • 红黑树的结构
      • 红黑树节点的定义
      • 红黑树的定义
      • 红黑树的调整
    • 红黑树的迭代器
      • 迭代器的声明
      • operator( )++
      • opeartor--( )
    • 完整代码
  • 📓总结


📄前言

作为一名程序员相信你一定有所听闻红黑树的大名,像是手撕红黑树这样的名梗已经几乎传遍了程序员之间,如果你还不会“手撕”红黑树,那么本文将会教会你如何“手撕”红黑树。

红黑树

概念

红黑树,顾名思义是只有红色和黑色两种颜色的树,由 Rudolf Bayer 在1972年发明的。红黑树是一种高效的查找树,可以在 O ( l o g 2 n ) O(log_2n) O(log2n)的时间复杂度下进行查找、插入和删除,C++中的map和set的底层也是利用红黑树所构成,在深入学习红黑树前,先让我们学习一下它的特性吧。

红黑树的特性:

  1. 根节点为黑
    t2. 最长路径的长度不超过最短路径的长度的两倍
  2. 每条路径的黑色节点之和都相同
  3. 不能存在连续的红色节点
  4. 只存在红色或黑色的节点
  5. 中序遍历是有序的

红黑树的样例:
在这里插入图片描述在这里插入图片描述

从图例我们可以看出每条路径的黑色节点个数都是相同的并且没有连续的红色节点,只要满足这两条特性,红黑树的最长路径节点个数不会超过最短节点个数的两倍,从而维护了树的平衡。

红黑树的结构

红黑树节点的定义

在进入插入操作前,得先定义好树的节点。因为树的插入需要用到父节点、甚至祖父节点,所以为了方便插入,二叉树的节点新增了父节点的指针。

enum Color	//颜色的定义
{
    RED,	//0
    BLACK	//1
};

template <class _Value>
struct RBTreeNode		//红黑树节点的定义
{
    RBTreeNode<_Value>* _left;	//节点的左孩子
    RBTreeNode<_Value>* _right;	//节点的右孩子
    RBTreeNode<_Value>* _parent;	//节点的双亲
    Color _col;		//节点的颜色
    _Value _data;			//节点的数值

    RBTreeNode(const _Value& data = _Value())	//节点的构造函数
        :_left(nullptr)
        ,_right(nullptr)
        ,_parent(nullptr)
        ,_data(data)
        ,_col(RED)	//默认设节点为红色
    {}
};

红黑树的定义

C++的红黑树在实现上为了同时让map和set复用,增加了一个keyofvalue的模板参数,用来解析需要比较的数值,如果不打算实现set和map可以不用写。

template<class _Key, class _Value, class _KeyOfValue>	
/*如果愿意还可以加上一个compare参数,来比较数值*/
class RBTree
{
public:
    typedef RBTreeNode<_Value> Node;
    /*这里暂时先把insert的返回值设为Node*,迭代器后面介绍时再补充*/
    Node* insert(const _Value data)		
    {
    	if(_root == nullptr)			//节点为空则新建
	    {
	        _root = new Node(data);
	        _root->_col = BLACK;		//红黑书性质规定根节点必须为黑
	        return _root;
	    }
	    
	    _KeyOfValue kot;		//用来解析数据的伪函数
	    Node* cur = _root;
	    Node* parent = nullptr;		
	    while(cur)			/*二叉树搜索树的经典搜索过程*/
	    {
	    	//工作原理:是data是pair类型则返回data.first,正常内置类型直接返回data
	        if(kot(cur->_data) < kot(data))		
	        {															
	            parent = cur;
	            cur = cur->_right;
	        }
	        else if(kot(cur->_data) > kot(data))
	        {
	            parent = cur;
	            cur = cur->_left;
	        }
	        else 
	            return cur;
	    }
	
	    cur = new Node(data);
	    Node* ret = cur;
	    cur->_parent = parent;		/*链接父节点*/
	    /*父节点链接子节点*/
	    if(kot(cur->_data) < kot(parent->_data))
	        parent->_left = cur;
	    else 
	        parent->_right = cur;	
	        
		/***************检查红黑树是否违反性质**************/
    }
}

红黑树的调整

红黑树的每次插入都需要检查其性质是否遭到了破坏,因为节点默认颜色为红色,所以当父节点为黑色时,则不需要调整。如果父节点为红色,违反了红黑树的性质,根据红黑树的情况,共有六种情况需要讨论,其中需要利用到祖父节点,根据父节点在祖父节点的左孩子/右孩子,又将6种情况划分为两类。

为了方便讨论,这里把当前节点作为cur,cur的父节点为p,cur的祖父节点为g,p的兄弟节点为u

  • 父节点是祖父节点的左孩子

    • 情况一:cur为红,p为红,g为黑,u存在且为红

      这种情况下,需要把p节点和u节点设为黑色,如果g节点为根节点则退出调整,否则将g节点设为红色,并把g赋值给cur,继续向上调整。
      在这里插入图片描述

      if(uncle && uncle->_col == RED)
      {
      	parent->_col = uncle->_col = BLACK;
      	grandParent->_col = RED;
      	
      	cur = grandParent;
      	parent = cur->_parent;
      }
      
    • 情况二:cur为红,p为红,g为黑,u不存在/存在且为黑,并且cur为p的左孩子

      这种情况下,需要对p节点进行右旋操作,并将p节点改为黑,cur和g节点改为红
      在这里插入图片描述

      if(uncle && uncle->_col == BLACK)
      {
           parent->_col = uncle->_col = BLACK;
           grandParent->_col = RED;
           
           cur = grandParent;
           parent = cur->_parent;
      }
      
    • 情况三:cur为红,p为红,g为黑,u不存在/存在且为黑,并且cur为p的左孩子
      在这种情况下,需要对双旋操作,先对p节点进行左旋,使得树变得极端左倾,然后再对g节点进行右倾恢复平衡,最后将g改为红,p改为黑。

      在这里插入图片描述

      else {
      	RotateL(parent);
       	RotateR(grandParent);
       	grandParent->_col = RED;
       	cur->_col = BLACK;
      }
      
  • 父节点是祖父节点的右孩子

    • 情况四:cur为红,p为红,g为黑,u存在且为红
      与情况一的处理一样
      在这里插入图片描述

      if(uncle && uncle->_col == BLACK)
      {
      		parent->_col = uncle->_col = BLACK;
      		grandParent->_col = RED;
      		cur = grandParent;
      		parent = cur->_parent;
      }
      
    • 情况五:cur为红,p为红,g为黑,u不存在/存在且为黑, 并且cur为p的左孩子
      这种情况下,需要对g节点进行左旋操作,并把p节点改黑、g节点改红。
      在这里插入图片描述

      if(cur == parent->_right)
      {
      	RotateL(grandParent);
      	parent->_col = BLACK;
      	grandParent->_col = RED;
      }
      
    • 情况六:cur为红,p为红,g为黑,u不存在/存在且为黑, 并且cur为p的右孩子
      这种情况下,需要对p节点进行右旋,使树变得极端右倾,然后对g节点进行左旋,最后将g节点改红、cur节点改黑。
      在这里插入图片描述

    else 
    {
    	RotateR(parent);
    	RotateL(grandParent);
    	grandParent->_col = RED;
    	cur->_col = BLACK;
    }
    

红黑树的迭代器

做完了树的插入,接下来就是红黑树的迭代器了。因为红黑树是平衡树,所以它的最小节点在树的最左侧,最大节点在树的最右侧,为此我们可以使用一个头节点,让其左右孩子指向最大最小节点,父节点指向跟节点。

在这里插入图片描述

迭代器的声明

template <class T, class Ref, class Ptr>	//Ref、Ptr用于const_iterator
struct _TreeIterator
{
    typedef RBTreeNode<T> Node;
    typedef _TreeIterator<T, Ref, Ptr> self;
    Node* _node;

	self& operator--();
	self& operator++();
}

operator( )++

找平衡树的下一个比当前节点大的节点,有两种情况

  • 当前节点存在右节点,则找右节点最左边的节点。
  • 不存在右节点,则返回父节点直到当前节点不是父节点的左节点
self& operator++() /*寻找下一个更大节点*/
{
	if(_node->_right)	
	{
	    Node* cur = _node->_right;
	    while(cur->_left)		/*寻找最左侧节点*/
	        cur = cur->_left;
	
	    _node = cur;
	}
	else 
	{
	    Node* cur = _node;		
	    Node* parent = cur->_parent;		
	    while(parent && cur == parent->_right)
	    {		/*右子树不存在,继续向上调整*/
	        cur = parent;
	        parent = parent->_parent;
	    }
	    _node = parent;
	}
	
	return *this;
}

opeartor–( )

寻找上一节点也分两种情况。

  • 当前节点左孩子存在,则找到左孩子的最右侧节点。
  • 当前节点不存在左孩子,则向上寻找直到当前节点不再是父节点的左孩子
self& operator--()
{
    Node* cur = _node;
    if(cur->_col == RED && cur->_parent->_parent == cur)
    {				//当前节点为头节点
        cur = cur->_right;
    }
    if(cur->_left)
    {		//左子树存在,在左子树寻找最大节点
        cur = cur->_left;
        while(cur->_right)
            cur = cur->_right;
    }
    else
    {		//向上调整
        Node* parent = cur->_parent;
        while(parent && cur == parent->_left)
        {
            cur = parent;
            parent = parent->_parent;
        }
        cur = parent;
    }
    _node = cur;

    return *this;
}

完整代码

template <class T, class Ref, class Ptr>
struct _TreeIterator		//迭代器
{
    typedef RBTreeNode<T> Node;
    typedef _TreeIterator<T, Ref, Ptr> self;
    typedef _TreeIterator<T, T&, T*> iterator;  
    Node* _node;

    _TreeIterator(Node* node)
        :_node(node)
    {}

    _TreeIterator(const iterator& _it) //构造函数,方便以后实现set中的inset函数中的pair拷贝
        :_node(_it._node)
    {
    }
    
    Ref operator*()const
    {
        return _node->_data;
    }

    Ptr operator->()const
    {
        return &operator*();
    }

    self& operator--()
    {
        Node* cur = _node;
        if(cur->_col == RED && cur->_parent->_parent == cur)
        {			
            cur = cur->_right;
        }
        if(cur->_left)
        {
            cur = cur->_left;
            while(cur->_right)
                cur = cur->_right;
        }
        else
        {
            Node* parent = cur->_parent;
            while(parent && cur == parent->_left)
            {
                cur = parent;
                parent = parent->_parent;
            }
            cur = parent;
        }
        _node = cur;

        return *this;
    }

    self&& operator--(int)
    {
        self tem = *this;
        Node* cur = _node;
        if(cur->_col == RED && cur->_parent->_parent == cur)
        {
            cur = cur->_right;
        }
        if(cur->_left)
        {
            cur = cur->_left;
            while(cur->_right)
                cur = cur->_right;
        }
        else
        {
            Node* parent = cur->_parent;
            while(parent && cur == parent->_left)
            {
                cur = parent;
                parent = parent->_parent;
            }
            cur = parent;
        }
        _node = cur;

        return tem;
    }

    self& operator++()
    {
        if(_node->_right)
        {
            Node* cur = _node->_right;
            while(cur->_left)
                cur = cur->_left;

            _node = cur;
        }
        else 
        {
            Node* cur = _node;
            Node* parent = cur->_parent;
            while(parent && cur == parent->_right)
            {
                cur = parent;
                parent = parent->_parent;
            }
            _node = parent;
        }

        return *this;
    }

    bool operator!=(const self& s)
    {
        return _node != s._node;
    }

    bool operator==(const self& s)
    {
        return _node == s._node;
    }
};

template<class K, class T, class KeyOfT>	//可是选择加上 class compare
class RBTree
{
public:
	typedef RBTreeNode<T> Node;
    typedef _TreeIterator<T, T&, T*> iterator;
    typedef _TreeIterator<T, const T&, const T*> const_iterator;		
	RBTree()
	{		//提前开好头节点
		_root = new Node;
		_root->_left = _root;
		_root->_right = _root;
	}

    const_iterator begin() const 
    {
        return const_iterator(LeftMost());
    }

    const_iterator end() const 
    {
        return const_iterator(_root);
    }

    iterator begin()
    {
        return iterator(LeftMost());
    }

    iterator end()
    {
        return iterator(_root);
    }

    std::pair<iterator, bool> Insert(const T& data);		//上文insert返回值设为了Node*,但实际应该是这个

    // 检测红黑树中是否存在值为data的节点,存在返回该节点的地址,否则返回nullptr
    iterator Find(const K& data);
    const_iterator Find(const K& data) const;
    
    // 获取红黑树最左侧节点
	Node* LeftMost()const;

    // 中序遍历
    void InOrder() 
    {
        _InOrder(GetRoot());
        std::cout << std::endl;
    }
    
    // 获取红黑树最右侧节点
	Node* RightMost()const;
    
    // 检测红黑树是否为有效的红黑树,注意:其内部主要依靠_IsValidRBTRee函数检测
	bool IsValidRBTRee();
private:
	bool _IsValidRBTRee(Node* pRoot, size_t blackCount, const size_t pathBlack);
    // 左单旋
	void RotateL(Node* pParent);
    // 右单旋
	void RotateR(Node* pParent);
    // 为了操作树简单起见:获取根节点
	Node*& GetRoot() const { return _root->_parent; }
    
    void _InOrder(Node* root);

    void rebalance(Node*& cur, Node*& parent)		//红黑树的平衡调整
    {
        while (parent != _root && parent->_col == RED)
        {
            Node* grandParent = parent->_parent;
            if(parent == grandParent->_left)
            {
                Node* uncle = grandParent->_right;
                if(uncle && uncle->_col == RED)
                {
                     parent->_col = uncle->_col = BLACK;
                     grandParent->_col = RED;

                     cur = grandParent;
                     parent = cur->_parent;
                }
                else 
                {
                    if(cur == parent->_left)
                    {   //右旋
                        RotateR(grandParent);
                        parent->_col = BLACK;
                        grandParent->_col = RED;
                    }
                    else 
                    {   //双旋
                       RotateL(parent);
                       RotateR(grandParent);
                       grandParent->_col = RED;
                       cur->_col = BLACK;
                    }
                    break;
                }
            }
            else 
            {
                Node* uncle = grandParent->_left;

                if(uncle && uncle->_col == BLACK)
                {
                     parent->_col = uncle->_col = BLACK;
                     grandParent->_col = RED;
                     cur = grandParent;
                     parent = cur->_parent;
                }
                else 
                {
                    if(cur == parent->_right)
                    {
                        RotateL(grandParent);
                        parent->_col = BLACK;
                        grandParent->_col = RED;
                    }
                    else 
                    {
                       RotateR(parent);
                       RotateL(grandParent);
                       grandParent->_col = RED;
                       cur->_col = BLACK;
                    }
                    break;
                }
            }
        }
        GetRoot()->_col = BLACK;
    }
private:
	Node* _root = nullptr;
    KeyOfT kot;
};


template <class K, class T, class KeyOfT>
typename RBTree<K, T, KeyOfT>::const_iterator RBTree<K, T, KeyOfT>::Find(const K& data) const
{
    Node* cur = GetRoot();
    while(cur)
    {
        if(kot(cur->_data) < data)
        {
            cur = cur->_right;
        }
        else if(kot(cur->_data) > data)
        {
            cur = cur->_left;
        }
        else 
        {
            return cur;
        }
    }

    return nullptr;
}

template <class K, class T, class KeyOfT>
typename RBTree<K, T, KeyOfT>::iterator RBTree<K, T, KeyOfT>::Find(const K& data) 
{
    Node* cur = GetRoot();
    while(cur)
    {
        if(kot(cur->_data) < data)
        {
            cur = cur->_right;
        }
        else if(kot(cur->_data) > data)
        {
            cur = cur->_left;
        }
        else 
        {
            return cur;
        }
    }

    return nullptr;
}

template <class K, class T, class KeyOfT>
void RBTree<K, T, KeyOfT>::_InOrder(Node* root)	//中序遍历
{
    if(!root)   
        return;

    _InOrder(root->_left);
    std::cout << root->_data << " ";
    _InOrder(root->_right);
}


template <class K, class T, class KeyOfT>
typename RBTree<K, T, KeyOfT>::Node* RBTree<K, T, KeyOfT>::LeftMost()const	//最左节点
{
    return _root->_left;
}

template <class K, class T, class KeyOfT>
typename RBTree<K, T, KeyOfT>::Node* RBTree<K, T, KeyOfT>::RightMost()const	//最右节点
{
    return _root->_right;
}


template <class K, class T, class KeyOfT>
bool RBTree<K, T, KeyOfT>::IsValidRBTRee()		//检查树的性质是否被破坏
{
    if(!GetRoot() || GetRoot()->_col == RED)  return false;
    
    size_t pathBlack = 0;
    Node* cur = GetRoot();
    while(cur)
    {
        if(cur->_col == BLACK)
            ++pathBlack;	//计算路径黑色节点的总个数
        cur = cur->_left;
    }
    int blackCount = 0;

    return _IsValidRBTRee(GetRoot(), blackCount, pathBlack);
}

template <class K, class T, class KeyOfT>
bool RBTree<K, T, KeyOfT>::_IsValidRBTRee(Node* pRoot, size_t blackCount, const size_t pathBlack)
{
    if(!pRoot)
    {
        if(blackCount != pathBlack)
        {
            std::cout << "有连续的红色结点" << std::endl;
            return false;
        }
        return true;
    }

    if(pRoot->_col == RED && pRoot->_parent->_col == RED)
    {
        std::cout << "有连续的红色结点" << std::endl;
        return false;
    }

    if(pRoot->_col == BLACK)
        ++blackCount;

    return _IsValidRBTRee(pRoot->_left, blackCount, pathBlack)
        && _IsValidRBTRee(pRoot->_right, blackCount, pathBlack);
}


template <class K, class T, class KeyOfT>   
std::pair<typename RBTree<K, T, KeyOfT>::iterator, bool> RBTree<K, T, KeyOfT>::Insert(const T& data)
{
    if(GetRoot() == nullptr)
    {
        Node* node = new Node(data);
        node->_col = BLACK;
        node->_parent = _root;
        _root->_parent = node;
        _root->_left = _root->_parent;
        _root->_right = _root->_parent;

        return std::make_pair(iterator(GetRoot()), true);
    }

    Node* cur = GetRoot();
    Node* parent = nullptr;
    while(cur)
    {
        if(kot(cur->_data) < kot(data))
        {
            parent = cur;
            cur = cur->_right;
        }
        else if(kot(cur->_data) > kot(data))
        {
            parent = cur;
            cur = cur->_left;
        }
        else 
            return std::make_pair(iterator(cur), false);
    }

    cur = new Node(data);
    Node* ret = cur;		//记录新增的节点,因为在调整后,节点可能会丢失
    cur->_parent = parent;

    if(kot(cur->_data) < kot(parent->_data))
    {
        if (parent == _root->_left)	//更新最小节点
            _root->_left = cur;

        parent->_left = cur;
    }
    else 
    {
        if(parent == _root->_right)	//更新最大节点
            _root->_right = cur;

        parent->_right = cur;
    }

    rebalance(cur, parent);

    return std::make_pair(ret, true);
}

template <class K, class V, class KeyOfT>
void RBTree<K, V, KeyOfT>::RotateL(Node* parent)	//左旋
{
    Node* subR = parent->_right;
    Node* subRL = subR->_left;
    Node* parentParent = parent->_parent;

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

    if(GetRoot() == parent)
    {
        GetRoot() = subR;
    }
    else 
    {
        if(parent == parentParent->_left)
        {
            parentParent->_left = subR;
        }
        else 
        {
            parentParent->_right = subR;
        }
    }
}

template <class K, class V, class KeyOfT>
void RBTree<K, V, KeyOfT>::RotateR(Node* parent)	//右旋
{
    Node* subL = parent->_left;
    Node* subLR = subL->_right;
    Node* parentParent = parent->_parent;

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

    parent->_left = subLR;
    parent->_parent = subL;

    if(subLR)
        subLR->_parent = parent;

    if(parent == GetRoot())
        GetRoot() = subL;
    else
    {
        if(parent == parentParent->_left)
            parentParent->_left = subL;
        else
            parentParent->_right = subL;
    }
} 

📓总结

📜博客主页:主页
📫我的专栏:C++
📱我的github:github

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

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

相关文章

xv6 磁盘中断流程和启动时调度流程

首发公号&#xff1a;Rand_cs 本文讲述 xv6 中的一些细节流程&#xff0c;还有对之前文中遗留的问题做一些补充说明&#xff0c;主要有以下几个问题&#xff1a; 一次完整的磁盘中断流程进入调度器后的详细流程sched 函数中的条件判断scheduler 函数中为什么要周期性关中断 …

微信小程序nodejs+vue+uniapp视力保养眼镜店连锁预约系统

作为一个视力保养连锁预约的网络系统&#xff0c;数据流量是非常大的&#xff0c;所以系统的设计必须满足使用方便&#xff0c;操作灵活的要求。所以在设计视力保养连锁预约系统应达到以下目标&#xff1a; &#xff08;1&#xff09;界面要美观友好&#xff0c;检索要快捷简易…

Unity 轨道展示系统(DollyMotion)

DollyMotion &#x1f371;功能展示&#x1f959;使用&#x1f4a1;设置路径点&#x1f4a1;触发点位切换&#x1f4a1;动态更新路径点&#x1f4a1;事件触发&#x1f4a1;设置路径&#x1f4a1;设置移动方案固定速度方向最近路径方向 &#x1f4a1;设置移动速度曲线 &#x1f…

vue3+ts 全局函数和变量的使用

<template><div>{{ $env }}<br />{{ $filters.format("的飞机") }}</div> </template><script setup lang"ts"> import { getCurrentInstance } from "vue"; const app getCurrentInstance(); console.log…

11.27/28 知识回顾与问题(Django之Web应用与http协议)

一、http有哪些主要版本以及特点 1. 主要版本以及各自特点 HTTP/0.9&#xff1a;最初版本的HTTP协议&#xff0c;只支持GET方法&#xff0c;并且没有请求头和响应头的概念&#xff0c;只能传输纯文本。于1991年发布&#xff0c;由Tim Berners-Lee创建&#xff0c;被认为是HTTP的…

AT89S52单片机的定时器

目录 定时器/计数器的结构 工作方式控制寄存器TMOD和TCON 定时器/计数器T1、T0的4种工作方式 1.方式0 2.方式1 3.方式2 4.方式3 定时器/计数器T2的结构与工作方式 1.T2的特殊功能寄存器T2MOD和T2CON 2.特殊功能寄存器T2CON 3.T2的三种工作模式 1. 捕捉方式 2.重新…

Ubuntu 22.04安装Go 1.21.4编译器

lsb_release -r看到操作系统版本是22.04,uname -r看到内核版本是uname -r。 sudo wget https://studygolang.com/dl/golang/go1.21.4.linux-amd64.tar.gz下载编译器。 sudo tar -zxf go1.21.4.linux-amd64.tar.gz -C /goroot将文件解压到/goroot目录下&#xff0c;这个命令…

人工智能技术发展漫谈

版权声明 本文原创作者&#xff1a;谷哥的小弟作者博客地址&#xff1a;http://blog.csdn.net/lfdfhl 人工智能发展历程 人工智能&#xff08;Artificial Intelligence&#xff0c;简称AI&#xff09;的发展历史可以追溯到20世纪中叶。以下是一些关键时刻和阶段&#xff1a; 起…

SELinux零知识学习三十七、SELinux策略语言之约束(1)

接前一篇文章:SELinux零知识学习三十六、SELinux策略语言之角色和用户(7) 四、SELinux策略语言之约束 SELinux对策略允许的访问提供了更严格的约束机制,不管策略的allow规则如何。 1. 近距离查看访问决定算法 为了理解约束的用途,先来看一下SELinux Linux安全模块(Lin…

SVD recommendation systems

SVD recommendation systems 为什么在推荐系统中使用SVD 一个好的推荐系统一定有小的RMSE R M S E 1 m ∑ i 1 m ( Y i − f ( x i ) 2 RMSE \sqrt{\frac{1}{m} \sum_{i1}^m(Y_i-f(x_i)^2} RMSEm1​i1∑m​(Yi​−f(xi​)2 ​ 希望模型能够在已知的ratings上有好的结果的…

高频Latex公式速查表,写论文技术博客不愁了

常见上下标X_{2}X^{2}\hat{X}\bar{X}\frac{1}{X}常见希腊字母\alpha \beta \gamma \delta \varepsilon \eta \theta \rho \sigma \phi \varphi \omega常见数学符号\leq \geq \neq\approx 其他\sum \prod \int \bigoplus \forall \exists \times \setminus \bigotimes \bigodot …

C#通过NPOI 读、写Excel数据;合并单元格、简单样式修改;通过读取已有的Excel模板另存为文件

文章目录 1 需要引用的DLL2 调用示例3 工具类 1 需要引用的DLL 2 调用示例 public static void WriteExcel() {string templateFile "F:\12312\excel.xlsx"; // 文件必须存在string outFile "F:\12312\" DateTime.Now.ToString("yyyyMMddHHmmssff…

2023年港澳台联考中录取分数高性价比的985和211大学来啦

导读 一直以来&#xff0c;985和211都是港澳台联考报名录取中&#xff0c;大家最关心的两类大学。其实每年的港澳台联考都有一些性价比很高的学校&#xff0c;今天我们就来看一下这些优秀的985和211大学吧&#xff01;&#xff08;景于行跟您承诺&#xff0c;本篇文章分享的分数…

医疗影像数据集—CT、X光、骨折、阿尔茨海默病MRI、肺部、肿瘤疾病等图像数据集

最近收集了一大波关于CT、X光等医疗方面的数据集包含骨折、阿尔茨海默病MRI、肺部疾病等类型的医疗影像数据&#xff0c;废话不多说&#xff0c;给大家逐一介绍&#xff01;&#xff01; 1、彩色预处理阿尔茨海默病MRI(磁共振成像)图像数据集 彩色预处理阿尔茨海默病MRI(磁共…

ERRO报错

无法下载nginx 如下解决&#xff1a; 查看是否有epel 源 安装epel源 安装第三方 yum -y install epel-release.noarch NGINX端口被占用 解决&#xff1a; 编译安装的NGINX配置文件在/usr/local/ngin/conf 修改端口

63 权限提升-Linux脏牛内核漏洞SUID信息收集

目录 演示案例:Linux提权自动化脚本利用-4个脚本Linux提权SUID配合脚本演示-AliyunLinux提权本地配合内核漏洞演示-MozheLinux提权脏牛内核漏洞演示-Aliyun&#xff0c;Vulnhub涉及资源: linux提权相对windows提权方法相对少一些&#xff0c;linux提权方法相对来讲有七八种方式…

NX二次开发UF_CURVE_create_arc_3point 函数介绍

文章作者&#xff1a;里海 来源网站&#xff1a;https://blog.csdn.net/WangPaiFeiXingYuan UF_CURVE_create_arc_3point Defined in: uf_curve.h int UF_CURVE_create_arc_3point(tag_t point1, tag_t point2, tag_t point3, UF_CURVE_limit_p_t limit_p [ 2 ] , tag_t supp…

不同路径(力扣LeetCode)动态规划

不同路径 题目描述 一个机器人位于一个 m x n 网格的左上角 &#xff08;起始点在下图中标记为 “Start” &#xff09;。 机器人每次只能向下或者向右移动一步。机器人试图达到网格的右下角&#xff08;在下图中标记为 “Finish” &#xff09;。 问总共有多少条不同的路径&…

基于Tomcat+Eclipse+Mysql开发的图书信息管理系统

基于TomcatEclipseMysql开发的图书信息管理系统 项目介绍&#x1f481;&#x1f3fb; 环境要求&#xff1a; eclipse j2ee mysql5 jdk8 tomcat9 必须按上述环境要求运行项目&#xff0c;否则将无法运行&#xff01; 步骤&#xff1a; 1.打开eclipse导入项目 2.修改book-context…

Nginx系列-正向代理和反向代理

Nginx系列-正向代理和反向代理 文章目录 Nginx系列-正向代理和反向代理1. 三个对象2. 两种场景代理2.1. 正向代理2.2. 反向代理 3. 两种场景的对比3.1 为什么叫做反向代理3.2 正向代理和反向代理的作用 1. 三个对象 客户端&#xff1a;发出请求到代理&#xff0c;并接收代理的…
最新文章