自动驾驶规划 - Apollo Lattice Planner算法【1】

news2024/9/25 0:28:42

文章目录

    • Lattice Planner简介
    • Lattice Planner 算法思路
      • 1. 离散化参考线的点
      • 2. 在参考线上计算匹配点
      • 3. 根据匹配点,计算Frenet坐标系的S-L值
      • 4. parse the decision and get the planning target
      • 5. 生成横纵向采样路径
      • 6. 轨迹cost值计算,进行碰撞检测
      • 7. 优先选择cost最小的轨迹且不碰撞的轨迹
    • 总结

Lattice Planner简介

LatticePlanner算法属于一种局部轨迹规划器,输出轨迹将直接输入到控制器,由控制器完成对局部轨迹的跟踪控制。因此,Lattice Planner输出的轨迹是一条光滑无碰撞满足车辆运动学约束和速度约束的平稳安全的局部轨迹。Lattice Planner的输入端主要由三部分组成,感知及障碍物信息、参考线信息及定位信息。局部规划模块的输出是带有速度信息的一系列轨迹点组成的轨迹,其保证了车辆控制器在车辆跟踪控制过程中的平稳性和安全性。挺复杂的越挖越多,看来得慢慢总结了(主要为了学思路,总结到自己的项目中)。


局部规划模块的输出是带有速度信息(速度信息哪里来的?)的一系列轨迹点组成的轨迹,其保证了车辆控制器在车辆跟踪控制过程中的平稳性和安全性。下面讲一下Apollo中的Lattice Planner的算法过程和代码解析。《刚学,写得可能不好,大佬们评论区指正😭😭》

Lattice Planner 算法思路

  1. 离散化参考线的点
  2. 在参考线上计算匹配点
  3. 根据匹配点,计算Frenet坐标系的S-L值
  4. 计算障碍物的s-t图
  5. 生成横纵向采样路径
  6. 计算cost值,进行碰撞检测
  7. 优先选择cost最小的轨迹且不碰撞的轨迹

在整体Apollo代码的结构,Lattice Planner的主要思路和实现是在Plan里面的PlanOnReferenceLine中,如下。

Status LatticePlanner::Plan(const TrajectoryPoint& planning_start_point,Frame* frame, ADCTrajectory* ptr_computed_trajectory) {
    ...                        
    auto status =
        PlanOnReferenceLine(planning_start_point, frame, &reference_line_info);//主要是逻辑在这个函数里面
    ...
}

PlanOnReferenceLine 主要是7步,按照上面的算法逻辑解析。

1. 离散化参考线的点

作为Lattice Planner的第一步,这部分的主要做的是将reference_line上的点,一个个搬到path_points中,其中增加了计算起点到当前点的累积距离ss的主要作用是为S-L坐标系使用。

  // 1. 离散化参考线的点(obtain a reference line and transform it to the PathPoint format)
  auto ptr_reference_line =
      std::make_shared<std::vector<PathPoint>>(ToDiscretizedReferenceLine(
          reference_line_info->reference_line().reference_points()));

详细函数逻辑:大部分的数据还是没变的,但是新增了s,主要用于S-L坐标系。

std::vector<PathPoint> ToDiscretizedReferenceLine(//离散化referenLine
    const std::vector<ReferencePoint>& ref_points) {
  double s = 0.0;
  std::vector<PathPoint> path_points;
  for (const auto& ref_point : ref_points) {
    PathPoint path_point;
    path_point.set_x(ref_point.x());//笛卡尔坐标系下的点横坐标
    path_point.set_y(ref_point.y());
    path_point.set_theta(ref_point.heading());//航向角
    path_point.set_kappa(ref_point.kappa());//曲率
    path_point.set_dkappa(ref_point.dkappa());//曲率变化

    if (!path_points.empty()) {
      double dx = path_point.x() - path_points.back().x();
      double dy = path_point.y() - path_points.back().y();
      s += std::sqrt(dx * dx + dy * dy);//两点之间的距离
    }
    path_point.set_s(s);//Frenet坐标系会用到,ref_points的起点到当前path_point的距离
    path_points.push_back(std::move(path_point));//std::move的作用?????
  }
  return path_points;
}

2. 在参考线上计算匹配点

第二步是基于车当前所在的点(x,y),找到参考线上的最近匹配点,为什么要这样做? 因为参考线是车要循迹的线,车都是在不断向参考线修正的,直到此时和参考线完全重合就是最优的状态。

  1. 这一步的思路是,遍历参考线reference_line,找到距离车🚗当前点最近的点,没错就是穷举遍历(相信c++的计算速度😄)。
  2. 找到最近点后,不是直接把这个点当成匹配点,而是基于参考线上的该点往前后各找一个点,把这两个点连接成向量,方向向前。
  3. 然后把车当前的点(下图P)投影到这个向量上,这个投影点才是第二步要找的匹配点。
  PathPoint matched_point = PathMatcher::MatchToPath(
      *ptr_reference_line, planning_init_point.path_point().x(),
      planning_init_point.path_point().y());//planning_init_point.path_point()当前定位点

在这里插入图片描述

  • 根据距离找点
PathPoint PathMatcher::MatchToPath(const std::vector<PathPoint>& reference_line,
                                   const double x, const double y) {//x、y是当前定位点
  CHECK_GT(reference_line.size(), 0U);

  auto func_distance_square = [](const PathPoint& point, const double x,
                                 const double y) {
    double dx = point.x() - x;
    double dy = point.y() - y;
    return dx * dx + dy * dy;//两点距离平方
  };

  double distance_min = func_distance_square(reference_line.front(), x, y);
  std::size_t index_min = 0;

  for (std::size_t i = 1; i < reference_line.size(); ++i) {//遍历reference_line,找到距离最小的点的
    double distance_temp = func_distance_square(reference_line[i], x, y);//计算当前点到reference_line上i点的距离
    if (distance_temp < distance_min) {
      distance_min = distance_temp;//距离
      index_min = i;//点的索引
    }
  }
  //向后找一个点
  std::size_t index_start = (index_min == 0) ? index_min : index_min - 1;
  //向前找一个点
  std::size_t index_end = (index_min + 1 == reference_line.size()) ? index_min : index_min + 1;
  //目的主要是将当前的定位点投影到距离最短点的前后点的连线上,做预处理
  if (index_start == index_end) {
    return reference_line[index_start];
  }
  //投影操作
  return FindProjectionPoint(reference_line[index_start],
                             reference_line[index_end], x, y);
}
  • 找到点之后算当前定位点在参考线上的投影
PathPoint PathMatcher::FindProjectionPoint(const PathPoint& p0,
                                           const PathPoint& p1, const double x,
                                           const double y) {
  //P0A向量
  double v0x = x - p0.x();
  double v0y = y - p0.y();
  //P0P1向量
  double v1x = p1.x() - p0.x();
  double v1y = p1.y() - p0.y();

  double v1_norm = std::sqrt(v1x * v1x + v1y * v1y);
  double dot = v0x * v1x + v0y * v1y;

  double delta_s = dot / v1_norm;//P0B长度
  return InterpolateUsingLinearApproximation(p0, p1, p0.s() + delta_s);//计算投影点的具体各项数据
}

手绘图:
请添加图片描述
简单理解一下这个图就能看懂代码了。😁😁

  • 计算投影点的具体各项数据

主要用线性插值(主要根据占比权重来算),公式做了合并同类项。

PathPoint InterpolateUsingLinearApproximation(const PathPoint &p0,
                                              const PathPoint &p1,
                                              const double s) {
  double s0 = p0.s();
  double s1 = p1.s();

  PathPoint path_point;
  double weight = (s - s0) / (s1 - s0);
  //p0.x()+weight*(p1.x()-p0.x())
  double x = (1 - weight) * p0.x() + weight * p1.x();
  double y = (1 - weight) * p0.y() + weight * p1.y();
  double theta = slerp(p0.theta(), p0.s(), p1.theta(), p1.s(), s);
  double kappa = (1 - weight) * p0.kappa() + weight * p1.kappa();
  double dkappa = (1 - weight) * p0.dkappa() + weight * p1.dkappa();
  double ddkappa = (1 - weight) * p0.ddkappa() + weight * p1.ddkappa();
  path_point.set_x(x);
  path_point.set_y(y);
  path_point.set_theta(theta);
  path_point.set_kappa(kappa);//曲率
  path_point.set_dkappa(dkappa);//曲率求导
  path_point.set_ddkappa(ddkappa);
  path_point.set_s(s);
  return path_point;
}

3. 根据匹配点,计算Frenet坐标系的S-L值

第3步的是计算车当前点P在Frenet坐标系下的坐标,这部分的原理可参考其他博客,主要就是套公式转换一下,为下面在S-T图的生成生成做准备。
在Frenet坐标系下的s就是第一步计算的s,L大小是上面手绘图的 ∣ A B ∣ |AB| AB,方向和 P 0 P 1 P_{0}P_{1} P0P1垂直,指向A。

  std::array<double, 3> init_s;
  std::array<double, 3> init_d;
  ComputeInitFrenetState(matched_point, planning_init_point, &init_s, &init_d);
  auto ptr_prediction_querier = std::make_shared<PredictionQuerier>(
      frame->obstacles(), ptr_reference_line);

补充说明一下,Frenet坐标系就是S-L坐标系,Frenet坐标系下的参数主要还是s,l,s就是累积距离,l就是后面提到的d,类似横向距离。

4. parse the decision and get the planning target

该部分总体代码:

  // 加入感知信息时,里面涉及多个函数,具体包括障碍物筛选,去掉与车辆未来轨迹不发生冲突的障碍物,设置动态障碍物。
  auto ptr_path_time_graph = std::make_shared<PathTimeGraph>(
      ptr_prediction_querier->GetObstacles(), *ptr_reference_line,
      reference_line_info, init_s[0]起始点,
      init_s[0] + FLAGS_speed_lon_decision_horizon=200, 0.0,
      FLAGS_trajectory_time_length=8s, init_d);

  double speed_limit =
      reference_line_info->reference_line().GetSpeedLimitFromS(init_s[0]);
  reference_line_info->SetLatticeCruiseSpeed(speed_limit);//限制巡航速度

  PlanningTarget planning_target = reference_line_info->planning_target();

这部分是计算障碍物的S-T图,这里的S是frenet下的s,t是时间。这部分接收感知那边传来的障碍物信息,计算障碍物在frenet坐标系下的最大值和最小值,代码如下:

SLBoundary PathTimeGraph::ComputeObstacleBoundary(
    const std::vector<common::math::Vec2d>& vertices,
    const std::vector<PathPoint>& discretized_ref_points) const {
  double start_s(std::numeric_limits<double>::max());
  double end_s(std::numeric_limits<double>::lowest());
  double start_l(std::numeric_limits<double>::max());
  double end_l(std::numeric_limits<double>::lowest());

  for (const auto& point : vertices) {
    //转frenet坐标
    auto sl_point = PathMatcher::GetPathFrenetCoordinate(discretized_ref_points,
                                                         point.x(), point.y());
    //计算障碍物在s-l坐标系下的最大值最小值
    start_s = std::fmin(start_s, sl_point.first);
    end_s = std::fmax(end_s, sl_point.first);
    start_l = std::fmin(start_l, sl_point.second);
    end_l = std::fmax(end_l, sl_point.second);
  }

  SLBoundary sl_boundary;//下图障碍物的显示
  sl_boundary.set_start_s(start_s);
  sl_boundary.set_end_s(end_s);
  sl_boundary.set_start_l(start_l);
  sl_boundary.set_end_l(end_l);

  return sl_boundary;
}

在这里插入图片描述
像上图描述的,就是将障碍物投影到frenet坐标系下,和车的预瞄轨迹点是否有交集(apollo 的预瞄距离是200米),没有交集的就不需要考虑。

动态障碍物的思路大概一样,动态障碍物可以看作多个静态障碍物,如下图,主要都是投影到frenet坐标系下,为下一步在S-T图上表达障碍物,以及在S- T图上做撒点采样,生成轨迹做铺垫。
在这里插入图片描述
解析一下这个图,中间是🚗轨迹,左右分别是车道边界,t0,t1在车道线内则考虑,其他时刻不考虑,其实详细思路更加复杂😭。

到这里,我们就能够得到障碍物的距S-T图
在这里插入图片描述

5. 生成横纵向采样路径

这步是关键,前面的Frenet坐标系和障碍物的S-T图可以说都是为了后面生成轨迹做铺垫的。
请添加图片描述
对于上图,有些司机会按照右图中浅红色的轨迹,选择绕开蓝色的障碍车。另外有一些司机开车相对保守,会沿着右图中深红色较短的轨迹做一个减速,给蓝色障碍车让路。

既然对于同一个场景,人类司机会有多种处理方法,那么Lattice规划算法的第一步就是采样足够多的轨迹,提供尽可能多的选择。

如何进行横纵向采样呢?

  • 横向采样
    在这里插入图片描述

在这里插入图片描述
横向轨迹的采样需要涵盖多种横向运动状态。现在Apollo的代码中设计了三个末状态横向偏移量,-0.5,0.0和0.5,以及四个到达这些横向偏移量的纵向位移,分别为10,20,40,80。用两层循环遍历各种组合,再通过多项式拟合,即可获得一系列的横向轨迹。

  • 纵向采样

    纵向轨迹采样需要考虑巡航、跟车或超车、停车这三种状态。也就是说3种情况下都要生成轨迹,后面再进行评估选择cost小的。

    有了S-T图的概念,观察左图中的两条规划轨迹。红色的是一条跟车轨迹,绿色的是超车轨迹。这两条轨迹反映在S-T图中,就如右图所示。红色的跟车轨迹在蓝色阴影区域下方,绿色的超车轨迹在蓝色阴影区域上方

    如果有多个障碍物,就对这些障碍物分别采样超车和跟车所对应的末状态。
    在这里插入图片描述
    总结下来就是遍历所有和车道有关联的障碍物,对他们分别采样超车和跟车的末状态,然后用多项式拟合即可获得一系列纵向轨迹。多项式拟合如何生成多项式轨迹?可以看一下下面这篇blog。

无人车-运动规划-高阶多项式曲线方程求解

在这里插入图片描述
上图是将所有的轨迹整合,交给下一步来评估。

Lattice Planner会根据起点和终点的状态,在位置空间和时间上同时进行撒点。撒点的起始状态和终止状态各有6个参数,包括了3个横向参数,即横向位置、横向位置的导数和导数的导数;3个纵向参数,即纵向位置、纵向位置的一阶导数也就是速度、纵向位置的二阶导数(也就是加速度)。

起点的参数是车辆当时真实的状态,终点状态则是撒点枚举的各个情况。在确定了终点和起点状态以后,再通过五阶或者四阶的多项式连接起始状态和终止状态,从而得到规划的横向和纵向轨迹。

6. 轨迹cost值计算,进行碰撞检测

有了轨迹的cost以后,就是一个循环检测的过程。在这个过程中,每次会先挑选出cost最低的轨迹,对其进行物理限制检测和碰撞检测。如果挑出来的轨迹不能同时通过这两个检测,就将其筛除,考察下一条cost最低的轨迹

  TrajectoryEvaluator trajectory_evaluator(
      init_s, planning_target, lon_trajectory1d_bundle, lat_trajectory1d_bundle,
      ptr_path_time_graph, ptr_reference_line);

  // Get instance of collision checker and constraint checker
  CollisionChecker collision_checker(frame->obstacles(), init_s[0], init_d[0],
                                     *ptr_reference_line, reference_line_info,
                                     ptr_path_time_graph);

7. 优先选择cost最小的轨迹且不碰撞的轨迹

获得采用轨迹之后,接着需要进行目标轨迹的曲率检查和碰撞检测,目的是为了使目标采样轨迹满足车辆的运动学控制要求和无碰撞要求,这样就形成了安全可靠的轨迹簇。这些轨迹簇都可以满足车辆的控制要求,但并不是最优的,因此需要从轨迹簇中选出一组最优的运行轨迹。这时就需要引入轨迹评价函数,用来对候选轨迹进行打分。

轨迹评价函数主要为了使得目标轨迹尽量靠近静态参考线轨迹运行,同时,速度尽量不发生大突变,满足舒适性要求,且尽量远离障碍物。因此最后轨迹评价函数可以通过如下伪代码描述:

traj_cost = k_lat * cost_lat + k_lon * cost_lon + k_obs * obs_cost;

上式中:
k_lat : 表示纵向误差代价权重
cost_lat: 表示纵向误差,综合考虑纵向速度误差,时间误差及加加速度的影响。
k_lon : 表示横向误差代价权重
cost_lon: 表示横向向误差,综合考虑了横向加速度误差及横向偏移误差的影响。
k_obs : 表示障碍物代价权重
obs_cost: 表示障碍物距离损失。
最后选择出代价值最好的一条轨迹输入到控制器,用于控制器的跟踪控制。

  while (trajectory_evaluator.has_more_trajectory_pairs()) {
    double trajectory_pair_cost =
        trajectory_evaluator.top_trajectory_pair_cost();
    auto trajectory_pair = trajectory_evaluator.next_top_trajectory_pair();

    // combine two 1d trajectories to one 2d trajectory
    auto combined_trajectory = TrajectoryCombiner::Combine(
        *ptr_reference_line, *trajectory_pair.first, *trajectory_pair.second,
        planning_init_point.relative_time());

    // check longitudinal and lateral acceleration
    // considering trajectory curvatures
    auto result = ConstraintChecker::ValidTrajectory(combined_trajectory);
    if (result != ConstraintChecker::Result::VALID) {
      ++combined_constraint_failure_count;

      switch (result) {
        case ConstraintChecker::Result::LON_VELOCITY_OUT_OF_BOUND:
          lon_vel_failure_count += 1;
          break;
        case ConstraintChecker::Result::LON_ACCELERATION_OUT_OF_BOUND:
          lon_acc_failure_count += 1;
          break;
        case ConstraintChecker::Result::LON_JERK_OUT_OF_BOUND:
          lon_jerk_failure_count += 1;
          break;
        case ConstraintChecker::Result::CURVATURE_OUT_OF_BOUND:
          curvature_failure_count += 1;
          break;
        case ConstraintChecker::Result::LAT_ACCELERATION_OUT_OF_BOUND:
          lat_acc_failure_count += 1;
          break;
        case ConstraintChecker::Result::LAT_JERK_OUT_OF_BOUND:
          lat_jerk_failure_count += 1;
          break;
        case ConstraintChecker::Result::VALID:
        default:
          // Intentional empty
          break;
      }
      continue;
    }

    // check collision with other obstacles
    if (collision_checker.InCollision(combined_trajectory)) {
      ++collision_failure_count;
      continue;
    }

    // put combine trajectory into debug data
    const auto& combined_trajectory_points = combined_trajectory;
    num_lattice_traj += 1;
    reference_line_info->SetTrajectory(combined_trajectory);
    reference_line_info->SetCost(reference_line_info->PriorityCost() +
                                 trajectory_pair_cost);
    reference_line_info->SetDrivable(true);

    // Print the chosen end condition and start condition
    ADEBUG << "Starting Lon. State: s = " << init_s[0] << " ds = " << init_s[1]
           << " dds = " << init_s[2];
    // cast
    auto lattice_traj_ptr =
        std::dynamic_pointer_cast<LatticeTrajectory1d>(trajectory_pair.first);
    if (!lattice_traj_ptr) {
      ADEBUG << "Dynamically casting trajectory1d ptr. failed.";
    }
  }
}

总结

总体讲清楚了算法的过程,但是代码层面涉及太多,后面还得慢慢深入,特别是采样的过程的代码得再解析。

参考:
Baidu Apollo 6.2 Lattice Planner规划算法
Apollo星火计划学习笔记——第七讲自动驾驶规划技术原理1
Lattice算法之六轨迹评估

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

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

相关文章

Fluent Python 笔记 第 8 章 对象引用、可变性和垃圾回收

本章先以一个比喻说明 Python 的变量&#xff1a;变量是标注&#xff0c;而不是盒子。如果你不知道引用式变量是什么&#xff0c;可以像这样对别人解释别名。 然后&#xff0c;本章讨论对象标识、值和别名等概念。随后&#xff0c;本章会揭露元组的一个神奇特性&#xff1a;元…

2023 年腾讯云服务器配置价格表出炉(2核2G/2核4G/4核8G/8核16G、16核32G)

腾讯云轻量应用服务器为轻量级的云服务器&#xff0c;使用门槛低&#xff0c;按套餐形式购买&#xff0c;轻量应用服务器套餐自带的公网带宽较大&#xff0c;4M、6M、7M、10M、14M及20M套餐可选&#xff0c;如果是云服务器CVM这个带宽价格就要贵很多了。 1、轻量应用服务器优惠…

openpyxl表格的简单实用

示例:创建简单的电子表格和条形图 在这个例子中,我们将从头开始创建一个工作表并添加一些数据,然后绘制它。我们还将探索一些有限的单元格样式和格式。 我们将在工作表上输入的数据如下: 首先,让我们加载 openpyxl 并创建一个新工作簿。并获取活动表。我们还将输入我们…

java ArrayList

目录 一.简单介绍 二.ArrayList的底层结构 2.1ArrayList的底层结构和操作分析 2.ArrayList 底层源码分析 三.ArrayList 方法 四.代码使用方法 一.简单介绍 ArrayList 类是一个可以动态修改的数组&#xff0c;与普通数组的区别就是它是没有固定大小的限制&#xff0c;我们…

Mac系统Mysql的8.0.22版本安装笔记和密码重置修改密码等问题方法

忘记密码官网教程地址&#xff1a;https://dev.mysql.com/doc/refman/5.7/en/resetting-permissions.html 5.7数据库安装指南参考&#xff1a;https://jingyan.baidu.com/article/fa4125ac0e3c2928ac709204.html 初次安装8.0.22遇到许多坑&#xff0c;密码修改失败&#xff1b…

【Flutter入门到进阶】Dart基础篇---面向对象

1 类 1.1 构造 //java中写法 class P {double x;double y;P(int x, int y) {this.x x;this.y y;} }//dart建议写法 class P {num x;num y;Point(this.x, this.y); } 1.2 重定向构造 class P { num x; num y; Point(this.x, this.y); //重定向构造函数&#xff0c;使用冒号…

C++:红黑树

红黑树的概念 红黑树是一棵二叉搜索树&#xff0c;但是红黑树通过增加一个存储位表示结点的颜色RED或BLACK。通过对任何一条从根到叶子的路径上各个结点着色方式的限制&#xff0c;红黑树确保没有一条路径会比其他路径长出2倍&#xff0c;因而是接近平衡的。 红黑树的性质 ⭐…

「期末复习」线性代数

第一章 行列式 行列式是一个数&#xff0c;是一个结果三阶行列式的计算&#xff1a;主对角线的乘积全排列与对换逆序数为奇就为奇排列&#xff0c;逆序数为偶就为偶排列对换&#xff1a;定理一&#xff1a;一个排列的任意两个元素对换&#xff0c;排列改变奇偶性&#xff08;和…

【Unity3D】Unity 3D 连接 MySQL 数据库

1.Navicat准备 test 数据库&#xff0c;并在test数据库下创建 user 数据表&#xff0c;预先插入测试数据。 2.启动 Unity Hub 新建一个项目&#xff0c;然后在Unity编辑器的 Project视图 中&#xff0c;右击新建一个 Plugins 文件夹将连接 MySQL的驱动包 导入&#xff08;附加驱…

Java链表模拟实现+LinkedList介绍

文章目录一、模拟实现单链表成员属性成员方法0&#xff0c;构造方法1&#xff0c;addFirst——头插2&#xff0c;addLast——尾插3&#xff0c;addIndex——在任意位置插入3.1&#xff0c;checkIndex——判断index合法性3.2&#xff0c;findPrevIndex——找到index-1位置的结点…

Java围棋游戏的设计与实现

技术&#xff1a;Java等摘要&#xff1a;围棋作为一个棋类竞技运动&#xff0c;在民间十分流行&#xff0c;为了熟悉五子棋规则及技巧&#xff0c;以及研究简单的人工智能&#xff0c;决定用Java开发五子棋游戏。主要完成了人机对战和玩家之间联网对战2个功能。网络连接部分为S…

Mac下拉式终端的安装与配置 (iTerm2)

Mac下拉式终端的安装与配置 使用效果如图所示 安装前置软件 iTerm2 很可惜&#xff0c;如此炫酷的功能在原终端中并不能实现&#xff0c;我们需要借助iTerm2这个软件来实现。 官网链接&#xff1a;iTerm2 - macOS Terminal Replacement 我们点击download下载即可 配置 当我…

代码随想录第十天(28)

文章目录28. 找出字符串中第一个匹配项的下标看答案KMPnext数组&#xff08;前缀表&#xff09;最长公共前后缀如何计算前缀表前缀表与next数组时间复杂度分析28. 找出字符串中第一个匹配项的下标 莫得思路……好久没做题&#xff0c;都已经忘得差不多了 看答案 其实就是自己…

ModelScope 垂类检测系列模型介绍

文章目录ModelScope介绍垂类模型介绍调用方式1 Demo Service2 Notebook3 本地使用* 二次开发总结ModelScope介绍 ModelScope 是阿里达摩院推出的 中文版模型即服务&#xff08;MaaS, Model as a Service&#xff09;共享平台。该平台在2022年的云栖大会上发布&#xff0c;之前…

Windows安装系列:SVN Server服务

一、下载与安装 1、下载VisualSVN-Server-5.1.1-x64.msi 地址&#xff1a;Download | VisualSVN Server 2、找到最新版本SVN 5.1.1&#xff0c;直接双击它&#xff0c;弹出如下安装界面 3、点击Next 4、勾选我接受&#xff0c; 点击"Next" 5、默认选项&#xff0c…

stack、queue和priority_queue

目录 一、栈&#xff08;stack&#xff09; 1.stack的使用 2.容器适配器 3.stack的模拟实现 二、队列&#xff08;queue&#xff09; 1.queue的使用 2.queue的模拟实现 三、双端队列&#xff08;deque&#xff09; 1.vector&#xff0c;list的优缺点 2.认识deque 四…

如何调试段错误?

刚接触指针的时候&#xff0c;经常会遇到段错误。 rootTurbo:linklist# ls link.c link.h main main.c rootTurbo:linklist# ./main 链表初始化成功 Segmentation fault (core dumped) rootTurbo:linklist#所谓段错误&#xff0c;就是访问了不能访问的内存。 比如内存不存在…

1.8 正则表达式

正则表示式是用来匹配与查找字符串的&#xff0c;从网上爬取数据不可避免的会用到正则表达式。 Python 的表达式要先引入 re 模块&#xff0c;正则表达式以 r 引导。Re库主要功能函数函数说明re.search()在一个字符串中搜索匹配正则表达式的第一个位置&#xff0c;返回match对象…

七大设计原则之里氏替换原则应用

目录1 里氏替换原则2 里氏替换原则应用1 里氏替换原则 里氏替换原则&#xff08;Liskov Substitution Principle,LSP&#xff09;是指如果对每一个类型为 T1 的对象 o1,都有类型为 T2 的对象 o2,使得以 T1 定义的所有程序 P 在所有的对象 o1 都替换成 o2 时&#xff0c;程序 P…

基于蜣螂算法改进的LSTM预测算法-附代码

基于蜣螂算法改进的LSTM预测算法 文章目录基于蜣螂算法改进的LSTM预测算法1.数据2.LSTM模型3.基于蜣螂算法优化的LSTM4.测试结果5.Matlab代码摘要&#xff1a;为了提高LSTM数据的预测准确率&#xff0c;对LSTM中的参数利用蜣螂搜索算法进行优化。1.数据 采用正弦信号仿真数据&…