G2O学习使用

news2024/11/17 14:42:44

g2o全称是General Graph Optimization,也就是图优化,我们在做SLAM后端或者更加常见的任何优化问题(曲线拟合)都可以使用G2O进行处理。

就经验而言,solvers给人的感觉是大同小异而 types 的选取,则是 g2o 用户主要关心的内容。然后 core 下面的内容,我们要争取弄的比较熟悉,才能确保使用中出现错误可以正确地应对。

那么,g2o最基本的类结构是怎么样的呢?我们如何来表达一个Graph,选择求解器呢?我们祭出一张图:

在这里插入图片描述

从代码层面来说,g2o是一个c++编写的项目,用cmake构建。它的github地址在:https://github.com/RainerKuemmerle/g2o 

它是一个重度模板类的c++项目,其中矩阵数据结构多来自Eigen。首先我们来扫一眼它的目录下面都有什么吧:

如你所见,g2o项目中含有若干文件夹。刨开那些gitignore之类的零碎文件,主要有以下几个:

EXTERNAL    三方库,有ceres, csparse, freeglut,可以选择性地编译;
cmake_modules    给cmake用来寻找库的文件。我们用g2o时也会用它里头的东西,例如FindG2O.cmake
doc    文档。包括g2o自带的说明书(难度挺大的一个说明文档)。
g2o    最重要的源代码都在这里!
script    在android等其他系统编译用的脚本,由于我们在ubuntu下就没必要多讲了。

对应的处理流程如下所示:
 

在这里插入图片描述

图的核心

SparseOptimizer是整个图的核心, is-a 是实心箭头,表示这个SparseOptimizer它是一个Optimizable Graph,从而也是一个超图(HyperGraph)

定点和边

has-many 表示这个超图(HyperGraph)包含了许多顶点(HyperGraph::Vertex)和边(HyperGraph::Edge)。而这些顶点继承自 Base Vertex,也就是OptimizableGraph::Vertex,而边可以继承自 BaseUnaryEdge(单边), BaseBinaryEdge(双边)或BaseMultiEdge(多边),它们都叫做OptimizableGraph::Edge

is-a:相当于一个等号

has-a:表示包含一个,这个包含的的属于必备的组件

has-many:和has-a差不多,至少要有一个

边的继承关系上图所示,对应的文件为:

g2o/g2o/core/hyper_graph.h
g2o/g2o/core/optimizable_graph.h
g2o/g2o/core/base_edge.h

BaseUnaryEdge,BaseBinaryEdge,BaseMultiEdge 分别表示一元边,两元边,多元边。
一元边你可以理解为一条边只连接一个顶点,两元边理解为一条边连接两个顶点,也就是我们常见的边啦,多元边理解为一条边可以连接多个(3个以上)顶点

参数
主要就是 几个参数:D, E, VertexXi, VertexXj,他们的分别代表:

D 是 int 型,表示测量值的维度 (dimension)
E 表示测量值的数据类型
VertexXi,VertexXj 分别表示不同顶点的类型

比如我们用边表示三维点投影到图像平面的重投影误差,就可以设置输入参数如下:

BaseBinaryEdge<2, Vector2D, VertexSBAPointXYZ, VertexSE3Expmap>

二元边。第1个2是说测量值是2维的,也就是图像像素坐标x,y的差值,对应测量值的类型是Vector2D,两个顶点也就是优化变量分别是空间点位置 VertexSBAPointXYZ,和李代数位姿VertexSE3Expmap

除了输入参数外,定义边我们通常需要复写一些重要的成员函数,顶点里主要复写了顶点更新函数oplusImpl和顶点重置函数setToOriginImpl

成员函数:

virtual bool read(std::istream& is);
virtual bool write(std::ostream& os) const;
virtual void computeError();
virtual void linearizeOplus();
  • read,write:分别是读盘、存盘函数,一般情况下不需要进行读/写操作的话,仅仅声明一下就可以
  • computeError函数:非常重要,是使用当前顶点的值计算的测量值与真实的测量值之间的误差
  • linearizeOplus函数:非常重要,是在当前顶点的值下,该误差对优化变量的偏导数,也就是我们说的Jacobian

除了上面几个成员函数,还有几个重要的成员变量和函数也一并解释一下:

_measurement:存储观测值
_error:存储computeError() 函数计算的误差
_vertices[]:存储顶点信息,比如二元边的话,_vertices[] 的大小为2,存储顺序和调用setVertex(int, vertex) 是设定的int 有关(0 或1)
setId(int):来定义边的编号(决定了在H矩阵中的位置)
setMeasurement(type) 函数来定义观测值
setVertex(int, vertex) 来定义顶点
setInformation() 来定义协方差矩阵的逆

定义g2o的边

 class myEdge: public g2o::BaseBinaryEdge<errorDim, errorType, Vertex1Type, Vertex2Type>
  {
      public:
      EIGEN_MAKE_ALIGNED_OPERATOR_NEW      
      myEdge(){}     
      virtual bool read(istream& in) {}
      virtual bool write(ostream& out) const {}      
      virtual void computeError() override
      {
          // ...
          _error = _measurement - Something;
      }      
      virtual void linearizeOplus() override
      {
          _jacobianOplusXi(pos, pos) = something;
          // ...         
          /*
          _jocobianOplusXj(pos, pos) = something;
          ...
          */
      }      
      private:
      // data
  }

一元边,主要是定义误差函数

// 误差模型 模板参数:观测值维度,类型,连接顶点类型
class CurveFittingEdge: public g2o::BaseUnaryEdge<1,double,CurveFittingVertex>
{
public:
    EIGEN_MAKE_ALIGNED_OPERATOR_NEW
    CurveFittingEdge( double x ): BaseUnaryEdge(), _x(x) {}
    // 计算曲线模型误差
    void computeError()
    {
        const CurveFittingVertex* v = static_cast<const CurveFittingVertex*> (_vertices[0]);
        const Eigen::Vector3d abc = v->estimate();
        _error(0,0) = _measurement - std::exp( abc(0,0)*_x*_x + abc(1,0)*_x + abc(2,0) ) ;
    }
    virtual bool read( istream& in ) {}
    virtual bool write( ostream& out ) const {}
public:
    double _x;  // x 值, y 值为 _measurement
};

二元边:3D-2D点的PnP 问题,也就是最小化重投影误差问题,。地址:g2o/types/sba/types_six_dof_expmap.h

//继承了BaseBinaryEdge类,观测值是2维,类型Vector2D,顶点分别是三维点、李群位姿
class G2O_TYPES_SBA_API EdgeProjectXYZ2UV : public  BaseBinaryEdge<2, Vector2D, VertexSBAPointXYZ, VertexSE3Expmap>{
  public:
    EIGEN_MAKE_ALIGNED_OPERATOR_NEW;
    //1. 默认初始化
    EdgeProjectXYZ2UV();
    //2. 计算误差
    void computeError()  {
      //李群相机位姿v1
      const VertexSE3Expmap* v1 = static_cast<const VertexSE3Expmap*>(_vertices[1]);
      // 顶点v2
      const VertexSBAPointXYZ* v2 = static_cast<const VertexSBAPointXYZ*>(_vertices[0]);
      //相机参数
      const CameraParameters * cam
        = static_cast<const CameraParameters *>(parameter(0));
     //误差计算,测量值减去估计值,也就是重投影误差obs-cam
     //估计值计算方法是T*p,得到相机坐标系下坐标,然后在利用camera2pixel()函数得到像素坐标。
      Vector2D obs(_measurement);
      _error = obs-cam->cam_map(v1->estimate().map(v2->estimate()));
    }
    //3. 线性增量函数,也就是雅克比矩阵J的计算方法
    virtual void linearizeOplus();
    //4. 相机参数
    CameraParameters * _cam; 
    bool read(std::istream& is);
    bool write(std::ostream& os) const;
};

#include <iostream>
#include <g2o/core/g2o_core_api.h>
#include <g2o/core/base_vertex.h>
#include <g2o/core/base_unary_edge.h>
#include <g2o/core/block_solver.h>
#include <g2o/core/optimization_algorithm_gauss_newton.h>
#include <g2o/core/optimization_algorithm_levenberg.h>
#include <g2o/core/optimization_algorithm_dogleg.h>
#include <g2o/solvers/dense/linear_solver_dense.h>
#include <Eigen/Core>
#include <opencv2/opencv.hpp>
#include <cmath>
#include <chrono>

using namespace std;
using namespace g2o;

class CurveFittingVertex : public g2o::BaseVertex<3,Eigen::vertor3d>{
    public:
        EIGEN_MAKE_ALIGNED_OPERATOR_NEW
        // 重置
        virtual void setToOriginImpl() override{
            _estimate << 0 , 0, 0;
        }

        virtual void oplusImpl(const double * update) override{
            _estimate += Eigen::Vector3d(update);
        }

        // 存取或者读取数据
        virtual bool read(istream & in){}
        virtual bool write(ostream & out) const{}
};

// 误差 ,就是边;模板的参数:观测值维度,类型,连接定点的类型
class CurveFittingEdge : public g2o::BaseUnaryEdge<1,double ,CurveFittingVertex>{
    public:
        EIGEN_MAKE_ALIGNED_OPERATOR_NEW

        CurveFittingEdge(double x):BaseUnaryEdge(),_x(x){}

        // 计算对应的误差
        virtual void computeError() override
        {
            const CurveFittingVertex* v = static_cast<const CurveFittingVertex*>(_vertices[0]);
            const Eigen::Vector3d abc = v->estimate();
            _error(0, 0) = _measurement - std::exp(abc(0, 0) * _x * _x + abc(1, 0) * _x + abc(2, 0));
        }

        virtual void linearizeOplus() override
        {
            const CurveFittingVertex* v = static_cast<const CurveFittingVertex*>(_vertices[0]);
            const Eigen::Vector3d abc = v->estimate();
            double y = exp(abc[0] * _x * _x + abc[1] * _x + abc[2]);
            _jacobianOplusXi[0] = -_x * _x * y;
            _jacobianOplusXi[1] = -_x * y;
            _jacobianOplusXi[2] = -y;
        }
        virtual bool read(istream& in){}
        virtual bool write(ostream& out) const{}
    public:
        double _x;
};
int main(int argc ,char argv[][]){
    double ar = 1.0, br = 2.0, cr = 1.0;            // 真实参数值
    double ae = 0.0, be = 0.0, ce = 0.0;            // 估计参数值
    int N = 100;                                    // 数据点
    double w_sigma = 1.0;                           // 噪声Sigma值
    double inv_sigma = 1.0 / w_sigma;
    cv::RNG rng;  
    
    vector<double> x_data, y_data;                  // 数据
    for (int i = 0; i < N; i++) 
    {
        double x = i / 100.0;
        x_data.push_back(x);
        y_data.push_back(exp(ar * x * x + br * x + cr) + rng.gaussian(w_sigma * w_sigma));
    }                                  
    // 构建图优化,先设定g2o
    // 每个误差项优化变量维度为3,误差值维度为1
    typedef g2o::BlockSolver<g2o::BlockSolverTraits<3, 1>> BlockSolverType; 
     // 线性求解器类型
    typedef g2o::LinearSolverDense<BlockSolverType::PoseMatrixType> LinearSolverType; 
    // 梯度下降方法,可以从GN, LM, DogLeg 中选
    auto solver = new g2o::OptimizationAlgorithmLevenberg(
    g2o::make_unique<BlockSolverType>(g2o::make_unique<LinearSolverType>()));
    g2o::SparseOptimizer optimizer;     // 图模型
    optimizer.setAlgorithm(solver);     // 设置求解器
    optimizer.setVerbose(true);         // 打开调试输出

     CurveFittingVertex* v = new CurveFittingVertex();
    v->setEstimate(Eigen::Vector3d(ae, be, ce));
    v->setId(0);
    optimizer.addVertex(v);

     // 往图中增加边
    for (int i = 0; i < N; i++) 
    {
        CurveFittingEdge* edge = new CurveFittingEdge(x_data[i]);
        edge->setId(i);
        edge->setVertex(0, v);              // 设置连接的顶点
        edge->setMeasurement(y_data[i]);    // 观测数值
        // 信息矩阵:协方差矩阵之逆
        edge->setInformation(
            Eigen::Matrix<double, 1, 1>::Identity() * 1 / (w_sigma * w_sigma)); 
        optimizer.addEdge(edge);
    }

    // 执行优化
    cout << "start optimization" << endl;
    chrono::steady_clock::time_point t1 = chrono::steady_clock::now();
    optimizer.initializeOptimization();
    optimizer.optimize(10);
    chrono::steady_clock::time_point t2 = chrono::steady_clock::now();
    chrono::duration<double> time_used = chrono::duration_cast<chrono::duration<double>>(t2 - t1);
    cout << "solve time cost = " << time_used.count() << " seconds. " << endl;

    // 输出优化值
    Eigen::Vector3d abc_estimate = v->estimate();
    cout << "estimated model: " << abc_estimate.transpose() << endl;

    return 0;
}

class VertexPose : public g2o::BaseVertex<6, Sophus::SE3d> 
{
public:
    EIGEN_MAKE_ALIGNED_OPERATOR_NEW;

    virtual void setToOriginImpl() override 
    {
        _estimate = Sophus::SE3d();
    }

    /// left multiplication on SE3
    virtual void oplusImpl(const double *update) override 
    {
        Eigen::Matrix<double, 6, 1> update_eigen;
        update_eigen << update[0], update[1], update[2], update[3], update[4], update[5];
        _estimate = Sophus::SE3d::exp(update_eigen) * _estimate;
    }

    virtual bool read(istream &in) override {}

    virtual bool write(ostream &out) const override {}
};

class EdgeProjection : public g2o::BaseUnaryEdge<2, Eigen::Vector2d, VertexPose> 
{
public:
    EIGEN_MAKE_ALIGNED_OPERATOR_NEW;

    EdgeProjection(const Eigen::Vector3d& pos, const Eigen::Matrix3d& K) : _pos3d(pos), _K(K) {}

    virtual void computeError() override 
    {
        const VertexPose* v = static_cast<VertexPose*> (_vertices[0]);
        Sophus::SE3d T = v->estimate();
        Eigen::Vector3d pos_pixel = _K * (T * _pos3d);
        pos_pixel /= pos_pixel[2];
        _error = _measurement - pos_pixel.head<2>();
    }

    virtual void linearizeOplus() override 
    {
        const VertexPose* v = static_cast<VertexPose*> (_vertices[0]);
        Sophus::SE3d T = v->estimate();
        Eigen::Vector3d pos_cam = T * _pos3d;
        double fx = _K(0, 0);
        double fy = _K(1, 1);
        double cx = _K(0, 2);
        double cy = _K(1, 2);
        double X = pos_cam[0];
        double Y = pos_cam[1];
        double Z = pos_cam[2];
        double Z2 = Z * Z;
        _jacobianOplusXi
        << -fx / Z, 0, fx * X / Z2, fx * X * Y / Z2, -fx - fx * X * X / Z2, fx * Y / Z,
        0, -fy / Z, fy * Y / (Z * Z), fy + fy * Y * Y / Z2, -fy * X * Y / Z2, -fy * X / Z;
    }

    virtual bool read(istream &in) override {}

    virtual bool write(ostream &out) const override {}

private:
    Eigen::Vector3d _pos3d;
    Eigen::Matrix3d _K;
};

void bundleAdjustmentG2O(const VecVector3d& points_3d, const VecVector2d& points_2d, const Mat& K, Sophus::SE3d& pose)
{
    // 构建图优化,先设定g2o
    typedef g2o::BlockSolver<g2o::BlockSolverTraits<6, 2>> BlockSolverType;  // pose is 6, landmark is 2
    typedef g2o::LinearSolverDense<BlockSolverType::PoseMatrixType> LinearSolverType; // 线性求解器类型
    // 梯度下降方法,可以从GN, LM, DogLeg 中选
    auto solver = new g2o::OptimizationAlgorithmGaussNewton(
        g2o::make_unique<BlockSolverType>(g2o::make_unique<LinearSolverType>()));
    g2o::SparseOptimizer optimizer;     // 图模型
    optimizer.setAlgorithm(solver);   // 设置求解器
    optimizer.setVerbose(true);       // 打开调试输出

    // vertex
    VertexPose* vertex_pose = new VertexPose(); // camera vertex_pose
    vertex_pose->setId(0);
    vertex_pose->setEstimate(Sophus::SE3d());
    optimizer.addVertex(vertex_pose);

    // K
    Eigen::Matrix3d K_eigen;
    K_eigen <<
        K.at<double>(0, 0), K.at<double>(0, 1), K.at<double>(0, 2),
        K.at<double>(1, 0), K.at<double>(1, 1), K.at<double>(1, 2),
        K.at<double>(2, 0), K.at<double>(2, 1), K.at<double>(2, 2);

    // edges
    int index = 1;
    for (size_t i = 0; i < points_2d.size(); ++i) 
    {
        auto p2d = points_2d[i];
        auto p3d = points_3d[i];
        EdgeProjection* edge = new EdgeProjection(p3d, K_eigen);
        edge->setId(index);
        edge->setVertex(0, vertex_pose);
        edge->setMeasurement(p2d);
        edge->setInformation(Eigen::Matrix2d::Identity());
        optimizer.addEdge(edge);
        index++;
    }

    chrono::steady_clock::time_point t1 = chrono::steady_clock::now();
    optimizer.setVerbose(true);
    optimizer.initializeOptimization();
    optimizer.optimize(10);
    chrono::steady_clock::time_point t2 = chrono::steady_clock::now();
    chrono::duration<double> time_used = chrono::duration_cast<chrono::duration<double>>(t2 - t1);
    cout << "optimization costs time: " << time_used.count() << " seconds." << endl;
    cout << "pose estimated by g2o =\n" << vertex_pose->estimate().matrix() << endl;
    pose = vertex_pose->estimate();
}

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

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

相关文章

C语言小游戏——扫雷

前言 结合前边我们所学的C语言知识&#xff0c;本期我们将使用C语言实现一个简单的小游戏——扫雷 目录 前言 总体框架设计 多文件分装程序 各功能模块化实现 初始化棋盘 棋盘打印 埋雷 判赢与排雷 游戏逻辑安排 总结 总体框架设计 和三子棋相同&#xff0c;游戏开始时…

32岁测试工程师,陷入中年危机,最终我裸辞了....

前言 今年32岁&#xff0c;我从公司离职了&#xff0c;是裸辞。 前段时间&#xff0c;我有一件事情一直憋在心里很难受&#xff0c;想了很久也没找到合适的人倾诉&#xff0c;就借着今天写出来。 我一个十几年IT经验&#xff0c;七年测试经验的职场老人&#xff0c;我慢慢涨…

02 Android开机启动之BootLoader及kernel的启动

Android开机启动之BootLoader及kernel的启动 1、booloader的启动流程 第一阶段:硬件初始化,SVC模式,关闭中断,关闭看门狗,初始化栈,进入C代码 第二阶段:cpu/board/中断初始化;初始化内存以及flash,将kernel从flash中拷贝到内存中,执行bootm,启动内核 2、kernel的启…

学习如何将Jenkins与UI测试报告完美整合,事半功倍,轻松获取高薪职位!

目录 引言 &#xff08;一&#xff09;在本地整合出报告 1.在cmd分别安装pytest和allure-pytest 2.进入需要执行的代码所在的路径 3.运行测试报告&#xff0c;代码如下 4.解析此json文件&#xff0c;代码如下&#xff08;新打开cmd进入路径&#xff09; 5.打开此HTML文件…

包管理工具

包 package&#xff0c;代表了一组特定功能的源码集合。 包管理工具 管理包的应用软件&#xff0c;可以对包进行下载安装、更新、删除、上传等操作。 借助包管理工具&#xff0c;可以快速开发项目&#xff0c;提升开发效率。 常用包管理工具 npm&#xff08;nodejs官方内…

百度API实现自动写诗

作者介绍 张琪&#xff0c;男&#xff0c;西安工程大学电子信息学院&#xff0c;2022级研究生 研究方向&#xff1a;机器视觉与人工智能 电子邮件&#xff1a;3126743452qq.com 王泽宇&#xff0c;男&#xff0c;西安工程大学电子信息学院&#xff0c;2022级研究生&#xff0…

Spring——Spring_IOC

1.Spring_IOC概念引入 控制反转 2.Spring_IOC代码测试 IOC代码演示 控制反转&#xff1a;就是创建对象的权力交给了容器 1.创建一个接口&#xff0c;定义一个抽象方法 package org.example;public interface Empdao {int addemp(); } 2.创建一个实现类&#xff0c;实现这…

两台电脑之间怎么互相传文件?

​随着技术的发展&#xff0c;我们似乎可以从家中或工作电脑远程访问另一台电脑。同时&#xff0c;一些用户也在想&#xff0c;“我能不能把文件从一台电脑远程传输到另一台电脑&#xff0c;这样我就可以在本地电脑上随心所欲地查看和编辑文件了”。 这个问题的答案是…

Android自定义一个省份简称键盘

hello啊各位老铁&#xff0c;这篇文章我们重新回到Android当中的自定义View&#xff0c;其实最近一直在搞Flutter&#xff0c;初步想法是&#xff0c;把Flutter当中的基础组件先封装一遍&#xff0c;然后接着各个工具类&#xff0c;列表&#xff0c;网络&#xff0c;统统由浅入…

PostgreSQL实战之物理复制和逻辑复制(八)

目录 PostgreSQL实战之物理复制和逻辑复制&#xff08;八&#xff09; 8 级联复制 8.1 级联复制物理架构 8.2 级联复制部署 PostgreSQL实战之物理复制和逻辑复制&#xff08;八&#xff09; 8 级联复制 实际上PostgreSQL支持备库既可接收主库发送的将WAL&#xff0c;也支持…

Java泛型基本知识附面试题

一次平平无奇的面试 为什么要写这篇文档&#xff0c;主要就是在字节二面的时候&#xff0c;面试官提了这么一个问题 面试官&#xff1a;Java中的List<Integer>里有可能存String类型元素吗&#xff1f; 当时的我&#xff1a;应该…不可以吧&#xff0c;好像编译器会报错…

2023口腔护理行业分析:市场需求多元化,细分市场持续多变

随着人们生活水平的提高以及口腔护理意识的提升&#xff0c;消费者对于口腔护理的诉求愈发多样化&#xff0c;对于与此相关的产品&#xff0c;包括牙膏、牙刷、牙齿美白产品、漱口水、牙线等产品的需求也日益提高&#xff0c;在这种情况下&#xff0c;口腔护理相关细分产品的销…

软件研发管理高效的关键:11项自动化功能

1、自动锁定需求缺陷 为了提高用户需求分析质量&#xff0c;尽早发现需求缺陷&#xff0c;CoCode开发云特开发了需求分析工具&#xff0c;使用AI&#xff0c;通过需求测试和一致性检测&#xff0c;能够在几分钟内快速分析用户需求缺陷&#xff0c;如歧义、重复、遗漏、不一致和…

深度学习神经网络学习笔记-多模态方向-09-VQA: Visual Question Answering

摘要 -我们提出了自由形式和开放式视觉问答(VQA)的任务。给定一张图像和一个关于图像的自然语言问题&#xff0c;任务是提供一个准确的自然语言答案。镜像现实场景&#xff0c;比如帮助视障人士&#xff0c;问题和答案都是开放式的。视觉问题有选择地针对图像的不同区域&#…

nodejs+vue飞机机票在线预订票网站

本机票预订系统以vue作为框架&#xff0c;b/s模式以及MySql作为后台运行的数据库&#xff0c;同时使用Tomcat用为系统的服务器。本系统主要包括首页&#xff0c;个人中心&#xff0c;用户管理&#xff0c;机票类型管理&#xff0c;机票信息管理&#xff0c;订票信息管理&#x…

STM32F4_DMA直接存储器详解

目录 1. 什么是DMA 2. DMA的主要特性 3. DMA功能 3.1 DMA功能框图 3.2 DMA事务 3.3 通道选择 3.4 仲裁器 3.5 DMA数据流 3.6 源、目标和传输模式 3.6.1 外设到存储器模式 3.6.2 存储器到外设模式 3.6.3 存储器到存储器模式 3.7 指针递增 3.8 DMA内存占用 3.9 存…

<SQL>《SQL命令(含例句)精心整理版(1)》

《SQL命令精心整理》 1 SQL基础2 关键字 select & distinct3 排序检索 - order by & desc & asc4 where 语句5 操作符 -and & or & not & in6 通配符6.1 LIKE % 谓词 下划线 方括号 7 计算、拼接、别名 1 SQL基础 名词概念数据库&#xff08;database…

Mit6.006-problemSet03

3-1 哈希练习&#xff08;Hash Practice&#xff09; (a) 按顺序插入整数keys A[47, 61, 36, 52, 56, 33, 92]到尺寸为7的哈希表中&#xff0c;使用哈希函数 h ( k ) ( 10 k 4 ) m o d 7 h(k)(10k4)mod7 h(k)(10k4)mod7。哈希表的每个插槽&#xff0c;存储一个key&#xff…

【Eslint】vscode 配置 eslint 教程

文章目录 一、初始化配置二、文件配置2.1、.eslintrc.js 文件2.2、.eslintignore 文件2.3、settings.json 文件 一、初始化配置 操作步骤&#xff1a; 选择&#xff1a;上下方向键确定&#xff1a;enter 回车键退出&#xff1a;Ctrl c 安装&#xff1a;npm install -g eslint初…

Java高并发核心编程(JUC)—线程池详细笔记

线程池 基本概述 线程池&#xff1a;一个容纳多个线程的容器&#xff0c;容器中的线程可以重复使用&#xff0c;省去了频繁创建和销毁线程对象的操作 线程池作用&#xff1a; 降低资源消耗&#xff0c;减少了创建和销毁线程的次数&#xff0c;每个工作线程都可以被重复利用…