当前位置: 首页 > news >正文

【C++】封装map和set(红黑树实现)

前言:

       前面,我们学习了set和map的用法,这两个容器可以完成查找,排序等操作,后来我们在学习过二叉搜索树的基础上又学习了两种特殊的二叉搜索树——AVL树和红黑树,他们俩可以是效率进一步提高,其实set和map的底层就是由红黑树封装而成的!

       所以,本篇文章我们自己学习用红黑树封装set和map。用红黑树封装,特别是用同一棵红黑树实现封装有一定的难度,其中很多操作(比如复用)我们会第一次尝试,所以大家请跟紧作者的脚步来。

目录

(一)如何复用同一棵红黑树

(二)红黑树的改造流程(为封装做铺垫)

1、结点的定义

2、模拟实现结点的比较大小

3、改造后的红黑树

4、map和set迭代器的模拟实现

4.1模版参数的构成

4.2begin()和end()的模拟实现

4.3operator* 和 operator->模拟实现

4.4 operator++ 和 operator--模拟实现

4.5 operator== 和 operator!=模拟实现

4.6迭代器代码详解

 (三)封装map和set


(一)如何复用同一棵红黑树

前提疑问:

在我们这所有的之前我们知道,map和set这两个容器都是用红黑树来实现的,那么就有了接下来的问题。

  • map和set都是用的同一棵红黑树复用的吗
  • 或者这两个容器各自使用一棵红黑树吗

其实在前言中我们就知道了,根据STL的设计理念和泛型编程的理念,我们不会冗余的写两课红黑树,而是用一棵红黑树实现复用的效果。

我们调用STL库中的原码来看:

我们发现:

  • STL库中的原码也确实调用的同一棵红黑树
  • 他们实现的都是key_value模型

我们设set中存放结点的值是K,map中存放的节点是pair<key,value>键值对:

  • 对于set而言,底层红黑树的模版就是RBTree<K, K>
  • 对于map而言,底层红黑树的模版就是RBTree<K, pair<K, V>>

这时我们就有了另一个疑问,两个模板参数的第一个Key,不能省略掉吗??

  • 首先,答案肯定是不能的
  • 那么原因又是什么呢?

因为map的这个类中,无论怎么省略都会有一个查找函数要单独用到Key

如果第一个Key删去了,那么map和set的查找函数就没法统一实现了,违背了我们一开始泛型编程的思想。

综上所述:

  • map和set都是用了,Key_value模型
  • set中的K是K,V也是K
  • map中的K是K,V是pair<K,V>
  • 并且模板参数中第一个K都不能省

(二)红黑树的改造流程(为封装做铺垫)

1、结点的定义

这里由于set和map存放的结点一个是Key一个是pair<Key,Value>,所以我们使用模版,把存放的结点泛化。

2、模拟实现结点的比较大小

  • 上述提到,在模拟实现中,map和set我们复用同一棵红黑树的时候都是用的是Kye_value的结构
  • 但是红黑树中的数据比较又是Key值的比较,而现在我们用的则是pair的比较
  • 虽然编译上是可以通过但是真的就是我们所想要的吗?

pair的比较大小:

很显然这种比较规则不是我们所想要的,并且map和set想要取到用来比较的数据是不同的。 

为了取到我们想要的数据,我们引入了仿函数:

  • 根据map和set的需求不同
  • 我们在红黑树中新引入了一个模板参数KeyOfT

 

引入KeyOfT模板参数后,我们想要不同方式的比较方法只需要在set和map的封装中给出属于他们自己的比较仿函数。

map中的仿函数: 

set中的仿函数:

使用方法:

 此时前面所说的仿函数的便利之处就体现出来了。

3、改造后的红黑树

具体代码:

//红黑树的实现
//KeyOfT --> 支持取出T对象中key的仿函数
template<class K, class T, class KeyOfT>
class RBTree
{typedef RBTreeNode<T> Node;
public:typedef __RBTreeIterator<T, T&, T*> iterator;typedef __RBTreeIterator<T, const T&, const T*> const_iterator;//构造 拷贝构造 赋值 和析构 跟搜索树实现方式是一样的//迭代器中序遍历,要找最左结点iterator Begin(){Node* subLeft = _root;while (subLeft && subLeft->_left){subLeft = subLeft->_left;}//树的迭代器用结点的指针就可以构造return iterator(subLeft);}iterator End(){return iterator(nullptr);}const_iterator Begin() const{Node* subLeft = _root;while (subLeft && subLeft->_left){subLeft = subLeft->_left;}//树的迭代器用结点的指针就可以构造return const_iterator(subLeft);}const_iterator End() const{return const_iterator(nullptr);}pair<iterator, bool> Insert(const T& data){//1、搜索树的规则插入//2、看是否违反平衡规则,如果违反就需要处理:旋转if (_root == nullptr){_root = new Node(data);_root->_col = BLACK; //根节点是黑色return make_pair(iterator(_root), true);}KeyOfT kot;Node* parent = nullptr;Node* cur = _root;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 make_pair(iterator(cur), false);}}//找到符合规则的位置之后再插入cur = new Node(data); Node* newnode = cur;cur->_col = RED;if (kot(parent->_data) < kot(data)){parent->_right = cur;}else{parent->_left = cur;}//三叉链的链接 -- 链上父节点cur->_parent = parent;//存在连续红色结点while (parent && parent->_col == RED){//理论而言,祖父是一定存在的,父亲存在且是红不可能是根(根一定是黑的)Node* grandfather = parent->_parent;assert(grandfather);if (grandfather->_left == parent){Node* uncle = grandfather->_right;//情况一:(叔叔存在且为红)if (uncle && uncle->_col == RED){//祖父和叔叔变成黑色parent->_col = uncle->_col = BLACK;grandfather->_col = RED;//继续往上处理cur = grandfather;parent = cur->_parent;}//情况二:(叔叔不存在 or 叔叔存在且为黑)else{//单旋//	   g//	 p// cif (cur == parent->_left){RotateR(grandfather);parent->_col = BLACK;grandfather->_col = RED;}//双旋//    g//  p//    celse{RotateL(parent);RotateR(grandfather);cur->_col = BLACK;grandfather->_col = RED;}break;}}//无论父亲和叔叔是左是右都是一样的//grandfather->_right == parent;else{Node* uncle = grandfather->_left;//情况一:if (uncle && uncle->_col == RED){//祖父和叔叔变成黑色parent->_col = uncle->_col = BLACK;grandfather->_col = RED;//继续往上处理cur = grandfather;parent = cur->_parent;}else{//单旋// g//   p//     cif (cur == parent->_right){RotateL(grandfather);parent->_col = BLACK;grandfather->_col = RED;}//双旋//  g//    p//  celse{RotateR(parent);RotateL(grandfather);cur->_col = BLACK;grandfather->_col = RED;}break;}}}//父亲为空就出循环,将根节点设置成黑色_root->_col = BLACK;return make_pair(iterator(newnode), true);}
}

插入(Insert)和寻找(Find)代码:
 

Node* Find(const K& key){Node* cur = _root;KeyOfT kot;while (cur){if (kot(cur->_data) < key){cur = cur->_right;}else if (kot(cur->_data) > key){cur = cur->_left;}else{return cur;}}return nullptr;}pair<itertaor, bool> Insert(const T& data){if (_root == nullptr){_root = new Node(data);_root->_col = BLACK;return make_pair(itertaor(_root), true);}KeyOfT kot;Node* parent = nullptr;Node* cur = _root;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 make_pair(itertaor(cur), false);}}cur = new Node(data);Node* newnode = cur;if (kot(parent->_data) > kot(data)){parent->_left = cur;}else{parent->_right = cur;}cur->_parent = parent;while (parent && parent->_col == RED){Node* grandfather = parent->_parent;if (grandfather->_left == parent){Node* uncle = grandfather->_right;// 情况1:u存在且为红,变色处理,并继续往上处理if (uncle && uncle->_col == RED){parent->_col = BLACK;uncle->_col = BLACK;grandfather->_col = RED;// 继续往上调整cur = grandfather;parent = cur->_parent;}else // 情况2+3:u不存在/u存在且为黑,旋转+变色{//     g//   p   u// c if (cur == parent->_left){RotateR(grandfather);parent->_col = BLACK;grandfather->_col = RED;}else{//     g//   p   u//     cRotateL(parent);RotateR(grandfather);cur->_col = BLACK;//parent->_col = RED;grandfather->_col = RED;}break;}}else // (grandfather->_right == parent){//    g//  u   p//        cNode* uncle = grandfather->_left;// 情况1:u存在且为红,变色处理,并继续往上处理if (uncle && uncle->_col == RED){parent->_col = BLACK;uncle->_col = BLACK;grandfather->_col = RED;// 继续往上调整cur = grandfather;parent = cur->_parent;}else // 情况2+3:u不存在/u存在且为黑,旋转+变色{//    g//  u   p//        cif (cur == parent->_right){RotateL(grandfather);grandfather->_col = RED;parent->_col = BLACK;}else{//    g//  u   p//    cRotateR(parent);RotateL(grandfather);cur->_col = BLACK;grandfather->_col = RED;}break;}}}_root->_col = BLACK;return make_pair(itertaor(newnode), true);;}

4、map和set迭代器的模拟实现

上面改造后的红黑树中已经涉及到了迭代器,这里我们来模拟实现。

备注:

  • T:数据
  • Ref:引用
  • Ptr:指针
  • Sef:iterator本身

4.1模版参数的构成

要想实现同一棵红黑树的复用,模版参数的构成极为重要。

之前我们也遇到过相似的情况,我们这里的实现方法参看之前。

迭代器的实现中:

  • 涉及到了*解引用操作,返回的是数据的引用;
  • 涉及到了->指针操作,返回的是数据的地址;
  • 涉及到++,--操作,返回的是操作后迭代器本身的位置。

但是以上的行为,我们都涉及结点,所以结点的数据类型也尤为重要

综上,模版参数的构成如下图:

4.2begin()和end()的模拟实现

  • 因为是二叉搜索树,为了更有顺序,所以我们采取的是中序遍历。
  • 那么中序遍历Begin()就应该是最左结点
  • 我们实现的版本中End()定义为空是(nullptr)

ps:

而库中的红黑树则是设计了一个哨兵位的头结点:

所以我们实现的只是简化版本。

4.3operator* 和 operator->模拟实现

就是正常的运用operator,但是operator->和list中讲的一样,返回的是地址的原因是为了连续访问(连续的operator->会优化成一个->)

4.4 operator++ 和 operator--模拟实现

这里迭代器的++和- - 需要分类一下,分别是:前置++,- - 、后置++,- -

前置++ :
因为我们是中序遍历,我们访问完自己之后,下一个该访问哪一个结点?

我们观察任何一棵二叉树都能看出,无非是下面两种情况:

分如下两种情况:(重点)

  • 右子树为空:找孩子是父亲的左的那个祖先节点,否则继续往上走,直到空(nullptr)
  • 右子树为非空:找右子树的最左节点

这一过程有点类似于递归思想,但是是非递归实现的

代码实现:

 前置-- :

有了上面的思路,我们联想一下,现在需要看当前位置左子树是否是空。

前置- -就是倒着走,同样还是对当前位置分两种情况:(重点)

  1. 左子树为空:找孩子是父亲的右的那个祖先节点,否则继续往上走,直到空(nullptr)
  2. 左子树为非空:找左子树的最右节点

只要前置++理解了,那么前置- -完全就是前置++倒过来走一遍。

后置++、后置- - :

和之前实现容器的得带器一样,我们这里直接复用即可:

4.5 operator== 和 operator!=模拟实现

有了之前封装迭代器的经验,这两个的视线还是比较容易得:

4.6迭代器代码详解

template<class T, class Ref, class Ptr>
struct __RBTreeIterator
{typedef RBTreeNode<T> Node;Node* _node;typedef __RBTreeIterator<T, Ref, Ptr> Self;__RBTreeIterator(Node* node):_node(node){}Ref operator*(){return _node->_data;}Ptr operator->(){return &_node->_data;}Self& operator++(){if (_node->_right == nullptr){//找祖先里面,孩子是父亲左的那个Node* cur = _node;Node* parent = cur->_parent;while (parent && parent->_right == cur){cur = cur->_parent;parent = parent->_parent;}_node = parent;}else{//右子树的最左结点Node* subLeft = _node->_right;while (subLeft->_left){subLeft = subLeft->_left;}//左为空_node = subLeft;}return *this;}Self& operator--(){if (_node->_left == nullptr){//找祖先里面,孩子是父亲Node* cur = _node;Node* parent = cur->_parent;while (parent && cur == parent->_left){cur = cur->_parent;parent = parent->_parent;}_node = parent;}else{//左子树的最右结点Node* subRight = _node->_left;while (subRight->_right){subRight = subRight->_right;}_node = subRight;}return *this;}Self operator++(int){Self tmp(*this);++(*this);return tmp;}Self operator--(int){Self tmp(*this);--(*this);return tmp;}bool operator!=(const Self& s) const{return _node != s._node;}bool operator==(const Self& s) const{return _node == s._node;}
};

 (三)封装map和set

有了上面的红黑树的改装,我们这里的对map和set的封装就显得很得心应手了。

封装主要是把map和set的基本操作封装在一个类中。

map的封装:

template<class K, class V>
class map
{//定义一个内部类struct MapKeyOfT{const K& operator()(const pair<K, V>& kv)//operator() 可以像函数一样去使用{return kv.first;}};public:typedef typename RBTree<K, pair<K, V>, MapKeyOfT>::iterator iterator;typedef typename RBTree<K, pair<K, V>, MapKeyOfT>::const_iterator const_iterator;iterator begin(){return _t.Begin();}iterator end(){return _t.End();}pair<iterator, bool> insert(const pair<K, V>& kv){return _t.Insert(kv);}V& operator[](const K& key){pair<iterator, bool> ret = insert(make_pair(key, V()));return ret.first->second;}
private:RBTree<K, pair<K, V>, MapKeyOfT> _t;
};

这里map中的operator[ ]我们知道其原理之后,模拟实现就非常方便,直接调用插入函数,控制好参数和返回值即可。

对set的封装:

template<class K>
class set    
{struct SetKeyOfT{const K& operator()(const K& key)//operator() 可以像函数一样去使用{return key;}};
public://加上typename告诉编译器这是个类型,类模板实例化了再去取typedef typename RBTree<K, K, SetKeyOfT>::const_iterator iterator;typedef typename RBTree<K, K, SetKeyOfT>::const_iterator const_iterator;iterator begin() const{return _t.Begin();}iterator end() const{return _t.End();}pair<iterator, bool> insert(const K& key){//pair<typename RBTree<K, K, SetKeyOfT>::iterator, bool> ret = _t.Insert(key);auto ret = _t.Insert(key);return pair<iterator, bool>(iterator(ret.first._node), ret.second);}iterator find(const K& key){return _t.Find(key);}private:RBTree<K, K, SetKeyOfT> _t;
};

附详细代码:——》红黑树封装map和set

相关文章:

【C++】封装map和set(红黑树实现)

前言&#xff1a; 前面&#xff0c;我们学习了set和map的用法&#xff0c;这两个容器可以完成查找&#xff0c;排序等操作&#xff0c;后来我们在学习过二叉搜索树的基础上又学习了两种特殊的二叉搜索树——AVL树和红黑树&#xff0c;他们俩可以是效率进一步提高&#xff0c;其…...

【补】代码随想录算法训练营day38|动态规划 |509. 斐波那契数|70. 爬楼梯|746. 使用最小花费爬楼梯

动态规划&#xff0c;英文&#xff1a;Dynamic Programming&#xff0c;简称DP&#xff0c;如果某一问题有很多重叠子问题&#xff0c;使用动态规划是最有效的。所以动态规划中每一个状态一定是由上一个状态推导出来的&#xff0c;这一点就区分于贪心&#xff0c;贪心没有状态推…...

C语言sizeof()计算空间大小为8的问题

在练习数据结构过程中&#xff0c;定义指针p&#xff0c;并且申请了10个char类型空间&#xff0c;但在计算p所指空间大小时候&#xff0c;发现了一些奇怪的现象。 #include <stdio.h> #include <stdlib.h>int main(){char s[12];printf("the size of memory …...

时序分解 | MATLAB实现基于LMD局部均值分解的信号分解分量可视化

时序分解 | MATLAB实现基于LMD局部均值分解的信号分解分量可视化 目录 时序分解 | MATLAB实现基于LMD局部均值分解的信号分解分量可视化效果一览基本介绍程序设计参考资料 效果一览 基本介绍 LMD局部均值分解 直接替换Excel即可运行包含频谱图相关系数图 Matlab语言 1.算法新颖…...

景区AR虚拟三维场景沉浸式体验成为新兴的营销手段

科技的迅速崛起正在改变我们的世界&#xff0c;旅游业也在这股浪潮中掀起了一场全新的变革。增强现实(AR)技术正成为旅行中的一股强大力量&#xff0c;通过增添趣味和交互性&#xff0c;为旅程注入了前所未有的活力。本文将带您深入了解AR如何为旅游带来全新的体验&#xff0c;…...

【深度学习】 Python 和 NumPy 系列教程(五):Python容器:3、集合Set详解(初始化、访问元素、常用操作、常用函数)

目录 一、前言 二、实验环境 三、Python容器&#xff08;Containers&#xff09; 0、容器介绍 1、列表&#xff08;List&#xff09; 2、元组&#xff08;Tuple&#xff09; 3、集合&#xff08;Set&#xff09; 1. 初始化 2. 访问集合元素 3. 常用操作 a. 添加单个…...

单片机C语言实例:6、定时器的应用

一、定时器0控制LED闪烁 实例程序1&#xff1a; #include<reg52.h> //包含头文件&#xff0c;一般情况不需要改动&#xff0c;头文件包含特殊功能寄存器的定义sbit LED P1^2; //定义LED端口/*------------------------------------------------定时器初始化子程序 …...

ChatGPT Prompting开发实战(五)

一、如何编写有效的prompt 对于大语言模型来说&#xff0c;编写出有效的prompt能够帮助模型更好地理解用户的意图(intents)&#xff0c;生成针对用户提问来说是有效的答案&#xff0c;避免用户与模型之间来来回回对话多次但是用户不能从LLM那里得到有意义的反馈。本文通过具体…...

MySQL——DQL union合并、limit限制与DDL建表和删表

一、Union 合并 union:是实现两个查询结果的合并。 例如&#xff1a;当我们查询员工名字为manager 和 salesman的员工名字和 工作&#xff1f; select e.ename,e.job from emp e where e.jobmanager or e.job salesman; select e.ename,e.job from emp e where e.job in(man…...

Java“牵手”唯品会商品列表数据,关键词搜索唯品会商品数据接口,唯品会API申请指南

唯品会商城是一个网上购物平台&#xff0c;售卖各类商品&#xff0c;包括服装、鞋类、家居用品、美妆产品、电子产品等。要获取唯品会商品列表和商品详情页面数据&#xff0c;您可以通过开放平台的接口或者直接访问唯品会商城的网页来获取商品详情信息。以下是两种常用方法的介…...

Springboot整合JWT完成验证登录

目录 一、引入依赖二、JwtUtil 代码解读三、LoginController 代码解读四、整体代码五、结果展示 一、引入依赖 <dependency><groupId>io.jsonwebtoken</groupId><artifactId>jjwt</artifactId><version>0.9.1</version></depende…...

centos7 下使用docker安装常见的软件:Redis

关于docker的基础知识&#xff0c;请见《别在说自己不知道docker了&#xff0c;全文通俗易懂的给你说明白docker的基础与底层原理》 在自己学习的过程中经常会需要动手安装一下常见的工具&#xff0c;本篇就手把手带你用docker安装一遍。 jdk安装 如果先要更换之前的jdk从第…...

sql:SQL优化知识点记录(十五)

&#xff08;1&#xff09;MySQL主从复制 我们这里配置一Windows上的MySql做主机&#xff0c;Linux上的MySql做从机&#xff0c;搭建一主一从 测试以下是否能够拼通&#xff1a;从Linux上&#xff1a;167&#xff0c;连接Windows的165 从Windows的165 连接Linux上&#xff1a;…...

vue3+ts 分享海报

安装依赖1. npm install html2canvas --save<div class"flex-box"><div><div v-for"(item,index ) in from.list" :key"index" click"actvieFuntion(index)"><div>{{item}}</div><div :class"…...

Ubuntu23.10将推出全磁盘加密功能,提高系统安全性

Canonical 宣布其即将推出的 Ubuntu 23.10&#xff08;Mantic Minotaur&#xff09;将引入基于 TPM 的全磁盘加密的初步支持。这个特性将利用系统可信平台模块&#xff08;TPM&#xff09;&#xff0c;在系统级别上进行全磁盘加密&#xff0c;从而提高系统的安全性。 但需要注…...

防火墙的设置主要是为了防范什么

防火墙的设置主要是为了防范网络攻击和数据泄露。随着互联网的普及和信息化的加速&#xff0c;网络安全问题越来越受到人们的关注。其中&#xff0c;防火墙是一种常见的网络安全设备&#xff0c;其设置的重要性也日益凸显。 防火墙的设置主要是为了防范什么 防火墙的设置主要目…...

Vim9和其他软件的文本复制、粘贴

大家都知道&#xff1a;在Vim9中使用y和p命令来进行文本的复制和粘贴&#xff0c;今天我来说一说Vim和其他软件之间的文本复制、粘贴操作。 Vim9和其他软件进行复制、粘贴&#xff0c;其原理就是通过系统剪贴板作为中介来执行操作。 一、从Vim9复制文本内容 按住鼠标左键滑出…...

MySQL学习5:事务、存储引擎

事务 简介 事务是一组数据库操作的执行单元&#xff0c;它要么完全执行&#xff0c;要么完全不执行。事务是确保数据库中的数据一致性和完整性的重要机制之一。 事务具有以下四个特性&#xff08;称为ACID特性&#xff09;&#xff1a; 原子性&#xff08;Atomicity&#xf…...

redis如何保证接口的幂等性

背景 如何防止接口中同样的数据提交&#xff0c;以及如何保证消息不被重复消费&#xff0c;这些都是shigen在学习的过程中遇到的问题。今天&#xff0c;趁着在学习redis的间隙&#xff0c;我写了一篇文章进行简单的实现。 注意&#xff1a;仅使用于单机的场景&#xff0c;对于…...

避坑之路 —— 前后端 json 的注意问题

当我们在进行开发项目的时候&#xff0c;在前后端需要进行数据之间的传输&#xff0c;那么就会需要到json。而json算是字符串中的一种 1.先说一下前端的, 其实这两种都是表示前端希望能收到后端json这样的数据格式&#xff0c;那么我们在后端就需要注意将数据进行转换为json进…...

[构建 Vue 组件库] 小尾巴 UI 组件库 —— 横向商品卡片(仿淘宝)

文章归档于&#xff1a;https://www.yuque.com/u27599042/row3c6 组件库地址 npm&#xff1a;https://www.npmjs.com/package/xwb-ui?activeTabreadmegitee&#xff1a;https://gitee.com/tongchaowei/xwb-ui 下载 npm i xwb-ui配置 按需导入 import {组件名 } from xwb-…...

【Python】Python实现五子棋游戏(带可视化界面)【独一无二】

&#x1f449;博__主&#x1f448;&#xff1a;米码收割机 &#x1f449;技__能&#x1f448;&#xff1a;C/Python语言 &#x1f449;公众号&#x1f448;&#xff1a;测试开发自动化【获取源码商业合作】 &#x1f449;荣__誉&#x1f448;&#xff1a;阿里云博客专家博主、5…...

用Maloja创建音乐收听统计数据

什么是 Maloja &#xff1f; Maloja 是简单的自托管音乐记录数据库&#xff0c;用于创建个人收听统计数据。没有推荐&#xff0c;没有社交网络&#xff0c;没有废话。Maloja 是一个跟踪您一段时间内的收听习惯的工具。 官方演示站点&#xff1a;https://maloja.krateng.ch/ 导出…...

GRU门控循环单元

GRU 视频链接 https://www.bilibili.com/video/BV1Pk4y177Xg?p23&spm_id_frompageDriver&vd_source3b42b36e44d271f58e90f86679d77db7Zt—更新门 Rt—重置门 控制保存之前一层信息多&#xff0c;还是保留当前神经元得到的隐藏层的信息多。 Bi-GRU GRU比LSTM参数少 …...

使用Puppeteer构建博客内容的自动标签生成器

导语 标签是一种用于描述和分类博客内容的元数据&#xff0c;它可以帮助读者快速找到感兴趣的主题&#xff0c;也可以提高博客的搜索引擎优化&#xff08;SEO&#xff09;。然而&#xff0c;手动为每篇博客文章添加合适的标签是一件费时费力的工作&#xff0c;有时候也容易遗漏…...

大数据分析案例-基于随机森林算法构建二手房价格预测模型

&#x1f935;‍♂️ 个人主页&#xff1a;艾派森的个人主页 ✍&#x1f3fb;作者简介&#xff1a;Python学习者 &#x1f40b; 希望大家多多支持&#xff0c;我们一起进步&#xff01;&#x1f604; 如果文章对你有帮助的话&#xff0c; 欢迎评论 &#x1f4ac;点赞&#x1f4…...

SLAM从入门到精通(ROS安装)

【 声明&#xff1a;版权所有&#xff0c;欢迎转载&#xff0c;请勿用于商业用途。 联系信箱&#xff1a;feixiaoxing 163.com】 ROS科研上面用的多&#xff0c;实际生产其实用的也不少。它本身还是很好的应用框架。当然&#xff0c;它对于很多初学的同学来说还是很友好的。学完…...

Linux 下spi设备驱动

参考&#xff1a; Linux kernel 有关 spi 设备树参数解析 Linux kernel 有关 spi 设备树参数解析 - 走看看 Linux SPI驱动框架(1)——核心层 Linux SPI驱动框架(1)——核心层_linux spi驱动模型_绍兴小贵宁的博客-CSDN博客 Linux SPI驱动框架(2)——控制器驱动层 Linux SPI驱…...

一:图形的位置和尺寸测量

绘制的基本要素: onDraw(Canvas)&#xff1a;是用来重写的 Canvas&#xff1a;实际执行绘制的 Paint&#xff1a;调整粗细和颜色等 坐标系&#xff1a;以屏幕左上角为原点&#xff0c;向右、向下为正向数值的坐标系 尺寸单位&#xff1a;在绘制过程中所有的尺寸单位都是px…...

rtthread下基于spi device架构MCP25625驱动

1.CAN驱动架构 由于采用了RTT的spi device架构&#xff0c;不能再随心所遇的编写CAN驱动 了&#xff0c;之前内核虽然采用了RTT内核&#xff0c;但是驱动并没有严格严格按RTT推荐的架构来做&#xff0c;这次不同了&#xff0c;上次是因为4个MCP25625挂在了4路独立的SPI总线上&…...

山东网站开发制作/放心网站推广优化咨询

错误处理 try、except、finally 先执行try中的语句&#xff0c;如果发生了错误则被except捕获&#xff0c;否则无错误则执行else语句&#xff0c;最终执行finally语句 try:print(try...)r 10 / int(2)print(result:, r) except ValueError as e:print(ValueError:, e) exce…...

洛阳响应式网站建设/汽车seo是什么意思

原文地址&#xff1a;百分比布局支持库参考原文&#xff1a;Percent Support Library: Bring dimension in % to RelativeLayout and FrameLayout虽然有很多的布局可以在 Android 应用程序开发的世界供我们使用&#xff0c;但我们总是只用这三种布局&#xff1a;LinearLayout, …...

武汉做营销型网站的公司/怎么样在百度上推广自己的产品

3.1 问题 本案例要求对Nginx服务器进行适当优化&#xff0c;解决如下问题&#xff0c;以提升服务器的处理性能&#xff1a; 如何自定义返回给客户端的404错误页面 如何查看服务器状态信息 如果客户端访问服务器提示“Too many open files”如何解决 如何解决客户端访问头部信…...

商城网站建设用乐云seo系统/什么是竞价

众所周知SSD的读写速度远比hdd磁盘要快&#xff0c;理解ssd的工作原理使我们开发处高效储存方案。 linux 相关指令 fstrim --fstab --verbose ## 回收(discard)文件系统上对应磁盘未使用的块 blkdiscard /dev/nvme1n1 ## 回收并擦除(discard)整个SSD块设备 wipefs -a /dev/n…...

网站建设与管理培训方案/宁波seo搜索平台推广专业

标签 PostgreSQL , CityJSON , CityGML , 3D City , 3D City Database , Geometrical Model , Semantic Model , COLLADA , VRML , X3D , IFC , gbXML , KML , OGC (Open Geospatial Consortium)开放地理空间协会 背景 空间应用在城市规划建设中&#xff0c;在发挥越来越重要的…...

wordpress怎么设置页面导航/营销渠道管理

一 GTID的介绍 GTID是一个基于原始mysql服务器生成的一个已经被成功执行的全局事务ID&#xff0c;它由服务器ID以及事务ID组合而成。这个全局事务ID不仅仅在原始服务器器上唯一&#xff0c;在所有存在主从关系 的mysql服务器上也是唯一的。正是因为这样一个特性使得mysql的主从…...