【C++】学习笔记——红黑树

news2024/11/27 3:59:59

文章目录

  • 十七、红黑树
    • 1.红黑树的概念
      • 红黑树的性质
    • 2.红黑树节点的定义
    • 3.红黑树的插入
    • 4.红黑树的验证
    • 5.完整代码+结果演示
    • 6.红黑树与AVL树的比较
  • 未完待续


十七、红黑树

1.红黑树的概念

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

红黑树的性质

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

由以上性质可知:如果存在,红黑树的最短路径是全黑色节点;最长路径则是一个黑色节点一个红色节点交替,所以 红黑树才能确保没有一条路径会比其他路径长出俩倍

2.红黑树节点的定义

红黑树节点的定义与AVL树及其相似,唯二的区别就是,红黑树没有平衡因子,红黑树有颜色值。

// 枚举颜色
enum Color
{
    RED,
    BLACK
};

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

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

为什么我们构造函数将颜色值默认设置成红色?因为我们创建的节点有两个孩子,两个孩子都是空节点,都是黑色的,如果创建的节点不把原本的这个位置的黑色空节点给替换掉,那么这条路径就会比其他路径多出一个黑色,违反了红黑树的性质。因此,新创建的节点一定是红色的。

3.红黑树的插入

先打个预防针:光靠区分节点颜色无法保证红黑树的结构,红黑树的插入在特殊情况同样需要 AVL树的旋转 操作。
红黑树是在二叉搜索树的基础上加上其平衡限制条件,因此红黑树的插入可分为两步:
Ⅰ:按照二叉搜索的树规则插入新节点。 这步就不再多说,应该都比较熟悉。

bool Insert(const std::pair<K, V>& kv)
{
    // 空树则创建根节点
    if (_root == nullptr)
    {
        _root = new Node(kv);
        // 红黑树的根节点是黑色的
        _root->_col = BLACK;
        return true;
    }
    Node* parent = nullptr;
    Node* cur = _root;
    // 找到新节点应该插入的位置
    while (cur)
    {
        // 比父节点小, 往左子树找
        if (kv.first < cur->_kv.first)
        {
            parent = cur;
            cur = cur->_left;
        }
        // 比父节点大, 往右子树找
        else if (kv.first > cur->_kv.first)
        {
            parent = cur;
            cur = cur->_right;
        }
        else
        {
            // 已经存在该值, 插入失败
            return false;
        }
    }
    cur = new Node(kv);

    // 父节点指向新节点
    if (kv.first < parent->_kv.first)
        parent->_left = cur;
    else
        parent->_right = cur;

    // 新节点指向父节点
    cur->_parent = parent;
	
	// ------begin------
	//
    // 修正结构和颜色
    //
    // ------end------
    
    // 保证根节点是黑色的
    _root->_col = BLACK;

    // 插入成功
    return true;
}

Ⅱ:检测新节点插入后,红黑树的性质是否造到破坏 。这一步非常重要,算是红黑树的精华所在。红黑树中,我们需要关注叔叔节点(父亲的兄弟)。红黑树的情况复杂,因此我们要对出现的情况进行分类讨论:
约定:cur为当前节点,p为父节点,g为爷爷节点,u为叔叔节点
①cur为红,p为红,g为黑,u存在且为红。由于新插入的节点必定是红色,但是p父节点也是红色,违反了不能出现连续红色的性质,所以我们需要进行修正颜色。由于刚好u叔叔节点是红色,所以最简单的方法就是 将g爷爷节点的黑色平分到它的两个孩子,p节点和u节点, 然后g节点变红即可
在这里插入图片描述
需要注意的是:由于g节点变成了红色,可能它与它的父节点违反了规则,所以我们需要循环向上判断,直到根节点。
在这里插入图片描述

② cur为红,p为红,g为黑,u不存在/u存在且为黑。
在这里插入图片描述

说明:u的情况有两种
1.如果u节点不存在,则cur一定是新插入节点,因为如果cur不是新插入节点,那么前面肯定符合红黑树,则则cur和p一定有一个节点的颜色是黑色,此时u若不存在,则两条路径黑色数量不一致,就不满足红黑树的性质,所以u不存在,则cur一定是新插入的节点。
2.如果u节点存在,且为黑色时(红色已在①中讨论),如果cur是新插入节点,则说明原本的树就不符合红黑树性质,所以cur不是新插入的节点。

此时我们可以借助AVL树的旋转来调整结构,然后修改颜色即可。
调整结构:p为g的左孩子,cur为p的左孩子,则进行右单旋转;相反,p为g的右孩子,cur为p的右孩子,则进行左单旋转;
变色:p、g变色 —> p变黑,g变红
在这里插入图片描述

③cur为红,p为红,g为黑,u不存在/u存在且为黑。有人会说,这不跟情况②一样吗?并不是,情况②是p和cur在同侧,与u相反,而这里,p和cur并不在同侧,cur的位置偏向u,朝内的。
在这里插入图片描述
这个时候怎么办呢?当然还是借助AVL树的思想,我可以在这里进行双旋。先旋转一次,让cur朝向外侧,就变成了情况②,然后再旋转一次即可。
在这里插入图片描述
旋转调整结构:p为g的左孩子,cur为p的右孩子,则针对p做左单旋转;相反,p为g的右孩子,cur为p的左孩子,则针对p做右单旋转。

综上所述:我们只需要记住:
叔叔节点存在并且是红色, 则不需要旋转,将爷爷节点的黑色分给父亲和叔叔,爷爷变红即可(需要向上调整)。
叔叔节点不存在或是黑色, 则需要旋转。cur节点朝外就是单旋,朝内就是双旋(旋转后是不需要再向上调整的),然后进行变色处理。
最后可能会将根节点染成红色,插入结束的时候特判一下即可。
修正结构和颜色的代码

// 修正结构和颜色
// 父亲存在且是红色
while (parent && parent->_col == RED)
{
    // 爷爷节点
    Node* grandfather = parent->_parent;
    // 父亲是爷爷的左
    if (parent == grandfather->_left)
    {
        // 叔叔节点
        Node* uncle = grandfather->_right;
        // 叔叔节点存在且为红色
        if (uncle && uncle->_col == RED)
        {
            // 父节点变黑, 叔叔节点变黑, 爷爷节点变红
            parent->_col = uncle->_col = BLACK;
            grandfather->_col = RED;
            
            // 继续往上修正
            cur = grandfather;
            parent = cur->_parent;
        }
        // 叔叔节点不存在或为黑色
        else
        {
            // 左左
            if (cur == parent->_left)
            {
                // 右单旋 + 修正颜色
                RotateR(grandfather);
                parent->_col = BLACK;
                grandfather->_col = RED;
            }
            // 左右
            else
            {
                // 左单旋父节点
                RotateL(parent);
                // 右单旋爷爷节点
                RotateR(grandfather);

                // 修正颜色
                cur->_col = BLACK;
                grandfather->_col = RED;
            }
            // 旋转后退出
            break;
        }
    }
    // 父亲是爷爷的右
    else
    {
        // 叔叔节点
        Node* uncle = grandfather->_left;
        // 叔叔节点存在且为红色
        if (uncle && uncle->_col == RED)
        {
            // 父节点变黑, 叔叔节点变黑, 爷爷节点变红
            parent->_col = uncle->_col = BLACK;
            grandfather->_col = RED;

            // 继续往上修正
            cur = grandfather;
            parent = cur->_parent;
        }
        // 叔叔节点不存在或为黑色
        else
        {
            // 右右
            if (cur == parent->_right)
            {
                // 左单旋 + 修正颜色
                RotateL(grandfather);
                parent->_col = BLACK;
                grandfather->_col = RED;
            }
            // 右左
            else
            {
                // 右单旋父节点
                RotateR(parent);
                // 左单旋爷爷节点
                RotateL(grandfather);

                // 修正颜色
                cur->_col = BLACK;
                grandfather->_col = RED;
            }
            // 旋转后退出
            break;
        }
    }
}

旋转代码

// 左左 --- 右单旋
void RotateR(Node* parent)
{
    // 父节点的左孩子
    Node* subL = parent->_left;
    // 父节点的左孩子的右孩子
    Node* subLR = subL->_right;
    // 父节点的父节点
    Node* ppnode = parent->_parent;

    // 修改父节点的指向
    parent->_left = subLR;
    parent->_parent = subL;

    // 修改父节点的左孩子的指向
    subL->_right = parent;
    subL->_parent = ppnode;

    // 如果父节点的左孩子的右孩子存在,则修改其指向
    if (subLR)
        subLR->_parent = parent;

    // 修改父节点的父节点的指向
    if (parent == _root)
        _root = subL;
    else if (parent == ppnode->_left)
        ppnode->_left = subL;
    else
        ppnode->_right = subL;
}

// 右右 --- 左单旋
void RotateL(Node* parent)
{
    // 父节点的右孩子
    Node* subR = parent->_right;
    // 父节点的右孩子的左孩子
    Node* subRL = subR->_left;
    // 父节点的父节点
    Node* ppnode = parent->_parent;

    // 修改父节点的指向
    parent->_right = subRL;
    parent->_parent = subR;

    // 修改父节点的右孩子的指向
    subR->_left = parent;
    subR->_parent = ppnode;

    // 如果父节点的右孩子的左孩子存在,则修改其指向
    if (subRL)
        subRL->_parent = parent;

    // 修改父节点的父节点的指向
    if (parent == _root)
        _root = subR;
    else if (parent == ppnode->_left)
        ppnode->_left = subR;
    else
        ppnode->_right = subR;
}

4.红黑树的验证

我们可以根据红黑树的性质来验证我们写的红黑树代码是否有问题。红黑树的性质主要只有三个:①根节点是黑色。②没有连续的红节点。③每条路径上的黑色节点数量相同。
判断根节点颜色和找出最左侧路径的黑色节点个数

bool IsRBT()
{
    // 判断根节点是否是黑色
	if (_root && _root->_col == RED)
	return false;

    // 计算最左路径的黑色节点数量
	int refBlackNum = 0;
	Node* cur = _root;
	while (cur)
	{
	if(cur->_col == BLACK)
	refBlackNum++;

	cur = cur->_left;
	}

    // 检查红黑树的性质
	return Check(_root, 0, refBlackNum);
}

所有路径的黑色节点数量与最左侧路径比较,顺便找连续的两个红色节点

// 检查红黑树的性质
bool Check(Node* cur, int blackNum, int refBlackNum)
{
	if (cur == nullptr)
	{
        // 存在某条路径上黑色节点数量与最左路径的黑色节点数量不相等
		if (refBlackNum != blackNum)
		{
			std::cout << "黑色节点的数量不相等" << std::endl;
			return false;
		}

		return true;
	}

    // 存在连续的红色节点
	if (cur->_col == RED && cur->_parent->_col == RED)
	{
        std::cout << cur->_kv.first << "存在连续的红色节点" << std::endl;
		return false;
	}

    // 该路径黑色节点数量+1
	if (cur->_col == BLACK)
	++blackNum;

    // 递归子树
	return Check(cur->_left, blackNum, refBlackNum)
	&& Check(cur->_right, blackNum, refBlackNum);
}

中序遍历

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

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

void InOrder()
{
	_InOrder(_root);
}

5.完整代码+结果演示

RBTree.h

#pragma once

#include <iostream>
// 枚举颜色
enum Color
{
    RED,
    BLACK
};

template<class K, class V>
struct RBTreeNode
{
    RBTreeNode* _left;
    RBTreeNode* _right;
    RBTreeNode* _parent;
    std::pair<K, V> _kv;
    // 颜色
    Color _col;

    RBTreeNode(const std::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 std::pair<K, V>& kv)
    {
        // 空树则创建根节点
        if (_root == nullptr)
        {
            _root = new Node(kv);
            // 红黑树的根节点是黑色的
            _root->_col = BLACK;
            return true;
        }
        Node* parent = nullptr;
        Node* cur = _root;
        // 找到新节点应该插入的位置
        while (cur)
        {
            // 比父节点小, 往左子树找
            if (kv.first < cur->_kv.first)
            {
                parent = cur;
                cur = cur->_left;
            }
            // 比父节点大, 往右子树找
            else if (kv.first > cur->_kv.first)
            {
                parent = cur;
                cur = cur->_right;
            }
            else
            {
                // 已经存在该值, 插入失败
                return false;
            }
        }
        cur = new Node(kv);

        // 父节点指向新节点
        if (kv.first < parent->_kv.first)
            parent->_left = cur;
        else
            parent->_right = cur;

        // 新节点指向父节点
        cur->_parent = parent;

        // 修正祖先的颜色
        while (parent && parent->_col == RED)
        {
            // 爷爷节点
            Node* grandfather = parent->_parent;
            if (parent == grandfather->_left)
            {
                // 叔叔节点
                Node* uncle = grandfather->_right;
                // 叔叔节点存在且为红色
                if (uncle && uncle->_col == RED)
                {
                    // 父节点变黑, 叔叔节点变黑, 爷爷节点变红
                    parent->_col = uncle->_col = BLACK;
                    grandfather->_col = RED;
                    
                    // 继续往上修正
                    cur = grandfather;
                    parent = cur->_parent;
                }
                // 叔叔节点不存在或为黑色
                else
                {
                    // 左左
                    if (cur == parent->_left)
                    {
                        // 右单旋 + 修正颜色
                        RotateR(grandfather);
                        parent->_col = BLACK;
                        grandfather->_col = RED;
                    }
                    // 左右
                    else
                    {
                        // 左单旋父节点
                        RotateL(parent);
                        // 右单旋爷爷节点
                        RotateR(grandfather);

                        // 修正颜色
                        cur->_col = BLACK;
                        grandfather->_col = RED;
                    }
                    break;
                }
            }
            else
            {
                // 叔叔节点
                Node* uncle = grandfather->_left;
                // 叔叔节点存在且为红色
                if (uncle && uncle->_col == RED)
                {
                    // 父节点变黑, 叔叔节点变黑, 爷爷节点变红
                    parent->_col = uncle->_col = BLACK;
                    grandfather->_col = RED;

                    // 继续往上修正
                    cur = grandfather;
                    parent = cur->_parent;
                }
                // 叔叔节点不存在或为黑色
                else
                {
                    // 右右
                    if (cur == parent->_right)
                    {
                        // 左单旋 + 修正颜色
                        RotateL(grandfather);
                        parent->_col = BLACK;
                        grandfather->_col = RED;
                    }
                    // 右左
                    else
                    {
                        // 右单旋父节点
                        RotateR(parent);
                        // 左单旋爷爷节点
                        RotateL(grandfather);

                        // 修正颜色
                        cur->_col = BLACK;
                        grandfather->_col = RED;
                    }
                    break;
                }
            }
        }
        // 保证根节点是黑色的
        _root->_col = BLACK;

        // 插入成功
        return true;
    }

    // 左左 --- 右单旋
    void RotateR(Node* parent)
    {
        // 父节点的左孩子
        Node* subL = parent->_left;
        // 父节点的左孩子的右孩子
        Node* subLR = subL->_right;
        // 父节点的父节点
        Node* ppnode = parent->_parent;

        // 修改父节点的指向
        parent->_left = subLR;
        parent->_parent = subL;

        // 修改父节点的左孩子的指向
        subL->_right = parent;
        subL->_parent = ppnode;

        // 如果父节点的左孩子的右孩子存在,则修改其指向
        if (subLR)
            subLR->_parent = parent;

        // 修改父节点的父节点的指向
        if (parent == _root)
            _root = subL;
        else if (parent == ppnode->_left)
            ppnode->_left = subL;
        else
            ppnode->_right = subL;
    }

    // 右右 --- 左单旋
    void RotateL(Node* parent)
    {
        // 父节点的右孩子
        Node* subR = parent->_right;
        // 父节点的右孩子的左孩子
        Node* subRL = subR->_left;
        // 父节点的父节点
        Node* ppnode = parent->_parent;

        // 修改父节点的指向
        parent->_right = subRL;
        parent->_parent = subR;

        // 修改父节点的右孩子的指向
        subR->_left = parent;
        subR->_parent = ppnode;

        // 如果父节点的右孩子的左孩子存在,则修改其指向
        if (subRL)
            subRL->_parent = parent;

        // 修改父节点的父节点的指向
        if (parent == _root)
            _root = subR;
        else if (parent == ppnode->_left)
            ppnode->_left = subR;
        else
            ppnode->_right = subR;
    }

    // 中序遍历
    void _InOrder(Node* root)
	{
		if (root == nullptr)
			return;

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

	void InOrder()
	{
		_InOrder(_root);
	}

    // 检查红黑树的性质
    bool Check(Node* cur, int blackNum, int refBlackNum)
	{
		if (cur == nullptr)
		{
            // 存在某条路径上黑色节点数量与最左路径的黑色节点数量不相等
			if (refBlackNum != blackNum)
			{
				std::cout << "黑色节点的数量不相等" << std::endl;
				return false;
			}

			return true;
		}

        // 存在连续的红色节点
		if (cur->_col == RED && cur->_parent->_col == RED)
		{
            std::cout << cur->_kv.first << "存在连续的红色节点" << std::endl;
			return false;
		}

        // 该路径黑色节点数量+1
		if (cur->_col == BLACK)
			++blackNum;
		
        // 递归子树
		return Check(cur->_left, blackNum, refBlackNum)
			&& Check(cur->_right, blackNum, refBlackNum);
	}

    bool IsRBT()
	{
        // 判断根节点是否是黑色
		if (_root && _root->_col == RED)
			return false;

        // 计算最左路径的黑色节点数量
		int refBlackNum = 0;
		Node* cur = _root;
		while (cur)
		{
			if(cur->_col == BLACK)
				refBlackNum++;

			cur = cur->_left;
		}

        // 检查红黑树的性质
		return Check(_root, 0, refBlackNum);
	}
private:
    Node* _root = nullptr;
};

void TestRBTree1()
{
	//int a[] = { 16, 3, 7, 11, 9, 26, 18, 14, 15 };
	int a[] = { 4, 2, 6, 1, 3, 5, 15, 7, 16, 14,16, 3, 7, 11, 9, 26, 18, 14, 15 };
	RBTree<int, int> t;
	for (auto e : a)
	{
		t.Insert(std::make_pair(e, e));
	}

	t.InOrder();
	std::cout << t.IsRBT() << std::endl;
}

test.cpp

#include "RBTree.h"
#include <iostream>

int main()
{
	TestRBTree1();
	return 0;
}

结果演示
在这里插入图片描述

6.红黑树与AVL树的比较

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


未完待续

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

XXL-JOB 定时任务在AI大模型中的应用

目录 1. 应用场景 2. 部署 XXL-JOB 3. SpringBoot 集成 XXL-JOB 定时任务代码示例 3.1 添加依赖 3.2 添加配置 3.3 添加执行器到Ioc容器 3.4 添加定时任务 3.5 控制台新增执行器 3.6 控制台添加任务 3.7 控制台开启任务 1. 应用场景 AI 大模型的调用往往是一个高资源…

在 Linux 系统上安装 GCC 编译器(不同发行版)

GCC&#xff08;GNU Compiler Collection&#xff09;是一个功能强大的开源编译器&#xff0c;支持多种编程语言&#xff0c;如 C、C、Fortran 等。在这篇博客中&#xff0c;我们将介绍如何在不同的 Linux 发行版上安装 GCC 编译器。 一、在 Ubuntu 上安装 GCC 1. 更新软件包…

numpy(史上最全)

目录 numpy简介 性能对比 ndarray属性 numpy中的数组&#xff1a; 几个创建的函数&#xff1a; 1) np.ones(shape, dtypeNone, orderC) shape: 形状&#xff0c;使用元组表示 2) np.zeros(shape, dtypefloat, orderC) 3) np.full(shape, fill_value, dtypeNone, orderC)…

阿里国际站运营工具 :一键提升你的全球竞争力!

在当今全球化的商业环境中&#xff0c;阿里巴巴国际站作为全球知名的B2B电子商务平台&#xff0c;为无数企业提供了一个展示产品、拓展国际市场的重要渠道。随着越来越多的企业加入到这个平台&#xff0c;如何在激烈的竞争中脱颖而出&#xff0c;成为了每个外贸人员必须面对的挑…

【BUG】已解决:ModuleNotFoundError: No module named ‘requests‘

ModuleNotFoundError: No module named ‘requests‘ 目录 ModuleNotFoundError: No module named ‘requests‘ 【常见模块错误】 【解决方案】 欢迎来到英杰社区https://bbs.csdn.net/topics/617804998 欢迎来到我的主页&#xff0c;我是博主英杰&#xff0c;211科班出身&a…

java-selenium 截取界面验证码图片并对图片文本进行识别

参考链接 1、需要下载Tesseract工具并配置环境变量&#xff0c;步骤如下 Tesseract-OCR 下载安装和使用_tesseract-ocr下载-CSDN博客 2、需要在IDEA中导入tess4j 包&#xff1b;在pom.xml文件中输入如下内容 <!--导入Tesseract 用于识别验证码--><dependency>&l…

SpringSecurity通用权限管理系统

1、介绍 权限管理是所有后台系统都会涉及的一个重要组成部分&#xff0c;而权限管理的核心流程是相似的&#xff0c;如果每个后台单独开发一套权限管理系统&#xff0c;就是重复造轮子&#xff0c;是人力的极大浪费&#xff0c;本项目就是针对这个问题&#xff0c;提供了一套通…

堆的相关特点

一.建堆的两种方法 给定一个数组&#xff0c;其中数组里面的元素个数是n个如何能够把这个数组建立成为一个堆&#xff0c;今天探讨两种方法&#xff0c;分别是向上调整法和向下调整法&#xff0c;分别探讨他们的时间复杂度 向上调整法&#xff08;以小堆为例&#xff09; 回…

【好玩的经典游戏】Docker环境下部署赛车小游戏

【好玩的经典游戏】Docker环境下部署赛车小游戏 一、小游戏介绍1.1 小游戏简介1.2 项目预览二、本次实践介绍2.1 本地环境规划2.2 本次实践介绍三、本地环境检查3.1 安装Docker环境3.2 检查Docker服务状态3.3 检查Docker版本3.4 检查docker compose 版本四、构建容器镜像4.1 下…

解决Visual studio内报错信息:MSB8036:找不到 Windows SDK 版本问题

问题描述&#xff1a; 找不到WindowsSDK版本&#xff0c;请安装所需版本的Windows SDK&#xff0c;或者在项目属性页中通过右键单击解决方案并选择“重定解决方案目标”来更改SDK版本。 首先&#xff0c;如果你尝试了以下两种方法&#xff1a; &#xff08;1&#xff09;重新…

深入理解Java 8的流式API:简化代码,提升效率

文章目录 深入理解Java 8的流式API&#xff1a;简化代码&#xff0c;提升效率一、流 Stream二、Int | Long | Double Stream三、收集器 Collectors 深入理解Java 8的流式API&#xff1a;简化代码&#xff0c;提升效率 Java 8引入了Stream API&#xff0c;它提供了一种新的抽象&…

String 和StringBuilder字符串操作快慢的举例比较

System.currentTimeMillis(); //当前时间与1970年1月1日午夜UTC之间的毫秒差。public class HelloWorld {public static void main(String[] args) {String s1 "";StringBuilder s2 new StringBuilder("");long time System.currentTimeMillis();long s…

Django Web框架~后台美化

1、执行命令 pip install django-simpleui 2、注册simpleui到项目中 3、访问http://127.0.0.1/admin/ 4、设置中文 5、收集admin静态文件 python manage.py collectstatic

Zookeeper入门篇,了解ZK存储特点

Zookeeper入门篇&#xff0c;了解ZK存储特点 前言一、为什么要用 Zookeeper&#xff1f;二、Zookeeper存储特色1. 树状结构2. 节点类型 三、存储位置1. 内存存储1. DataTree2. DataNode 2. 硬盘存储1. 事务日志2. 快照 前言 继上次说完 Zookeeper 的安装后&#xff0c;已经过去…

设计模式学习[2]---策略模式+简单工厂回顾

文章目录 前言1.简单工厂模式回顾2.策略模式3.策略模式简单工厂的结合总结 前言 上一篇讲到简单工厂模式。 在我的理解中工厂的存在就是&#xff0c;为了实例化对象。根据不同条件实例化不同的对象的作用。 这篇博客写的策略模式&#xff0c;可以说是把这个根据不同情况实例化…

pdf2docx - pdf 提取内容转 docx

文章目录 一、关于 pdf2docx主要功能限制 二、安装1、 PyPI2、从remote安装3、从源码安装4、卸载 三、转化 PDF例 1: convert all pages例 2: 转换指定页面例 3: multi-Processing例 4: 转换加密的pdf 四、提取表格五、命令行交互1、按页面范围2、按页码3、Multi-Processing 六…

使用Vue.js集成百度地图WebGL实现3D地图应用

本文由ScriptEcho平台提供技术支持 项目地址&#xff1a;传送门 使用Vue.js集成百度地图WebGL实现3D地图应用 应用场景介绍 本代码用于在Vue.js应用程序中集成百度地图WebGL&#xff0c;实现3D地图的可视化展示。它可以应用于各种场景&#xff0c;例如&#xff1a; 城市规…

助燃新质生产力,魔珐科技亮相IMC2024制造业数字科技大会展示有言AIGC视频工具价值

2024年7月19日&#xff0c;IMC2024第八届制造业数字科技大会在上海盛大开幕&#xff0c;本次大会以《向“智”而行》为主题&#xff0c;250智能制造行业数字化转型企业、行业领军者及实践者共聚一堂&#xff0c;共同助力企业增强技术“硬核力”&#xff0c;为新质生产力蓄势赋能…

【SpringBoot3】全局异常处理

【SpringBoot3】全局异常处理 一、全局异常处理器step1&#xff1a;创建收入数字的页面step2:创建控制器&#xff0c;计算两个整数相除step3:创建自定义异常处理器step5&#xff1a;创建给用提示的页面step6&#xff1a;测试输入&#xff08;10/0&#xff09; 二、BeanValidato…

TCP/IP网络模型详解

在计算机网络领域&#xff0c;网络模型通常指的是 OSI&#xff08;Open Systems Interconnection&#xff09;参考模型或 TCP/IP&#xff08;Transmission Control Protocol/Internet Protocol&#xff09;模型。这些模型描述了网络中数据传输的层次结构&#xff0c;便于理解和…