EM Planner算法与代码解读

EM Planner算法概述

EM Planner(Expectation-Maximization Planner)是百度Apollo自动驾驶平台中的一种面向L4级别的实时运动规划算法。该算法通过顶层多车道策略选择参考路径,并在Frenet坐标系下进行车道级的路径和速度规划。EM Planner的设计充分考虑了无人车的安全性、舒适性和可扩展性,能够适应高速公路和低速城区等多种场景。该算法通过动态规划(Dynamic Programming)和基于样条的二次规划(Quadratic Programming)实现,展现了较高的可靠性和低耗时性。

EM Planner的工作原理

EM Planner的工作流程包括以下关键步骤:

  1. 多车道策略:算法首先通过搜索算法结合代价估算形成变道策略,将多车道策略划分为被动变道和主动变道,后者考虑动态障碍物而做出决策。

  2. 路径-速度迭代算法:在Frenet坐标下,EM Planner通过迭代地进行路径和速度最优求解,考虑与障碍物的交互,上一帧的速度规划将帮助下一帧的路径规划。

  3. 决策和交通规则约束:交通规则作为硬约束,与障碍物的交互作为软约束。决策模块为规划带来更明确的意图,减少最优求解的搜索空间。

  4. SL和ST投影:在E-step中,障碍物被投影到车道Frenet坐标系,包括静态和动态障碍物。在M-step中,障碍物在速度时间(ST)图中与生成的速度信息进行估计。

  5. DP路径和QP路径:通过Dynamic Programming获得一个粗略的解,并构建一个凸的通道,然后使用基于Quadratic Programming的样条曲线生产光滑路径。

  6. 参考线轨迹决策:最后,参考线轨迹决策器根据当前车辆状态、相关约束和每条轨迹的代价,决定最优的轨迹。

1. 多车道策略

在多车道策略中,EM Planner采用了一种分层决策机制,将策略划分为主动变道 (Proactive Lane Changing)被动变道(Reactive Lane Changing)。主动变道策略通常由导航需求触发,如到达目的地所需的车道变更。被动变道则是在当前车道受阻时(例如,前方有障碍物或车辆),系统自动选择另一条车道以确保安全行驶。这种策略通过结合搜索算法和代价估算,能够智能地预测和选择最佳变道路径,同时考虑动态障碍物的影响,确保决策的安全性和效率性。

主动变道

主动变道通常是由导航系统触发的,例如当车辆需要在前方路口左转或右转时,系统会提前规划并选择正确的车道。这种变道策略需要考虑目的地、交通规则、车道可用性等因素。

cpp 复制代码
 
  
* 
     
      
     

     

      
       void proactiveLaneChange(const NavigationData &navData, VehicleState &vehicleState) {
      
     

  
* 
     
      
     

     

          // 假设navData包含目的地信息和当前路口信息
      
     

  
* 
     
      
     

     

          // vehicleState包含车辆当前状态和位置
      
     

  
* 
     
      
     

     

       
      
     

  
* 
     
      
     

     

          // 检查是否需要变道
      
     

  
* 
     
      
     

     

          if (navData.needsTurn() && vehicleState.getCurrentLane() != navData.getTargetLane()) {
      
     

  
* 
     
      
     

     

              // 执行变道逻辑
      
     

  
* 
     
      
     

     

              vehicleState.changeLane(navData.getTargetLane());
      
     

  
* 
     
      
     

     

          }
      
     

  
* 
     
      
     

     

      }
      
     

 

 AI写代码cpp
  

运行
 
 
  ![](https://csdnimg.cn/release/blogv2/dist/pc/img/runCode/icon-arrowwhite.png)
 
被动变道

被动变道是在当前车道受阻时自动触发的,例如前方有慢车或障碍物。系统会评估周围车道的情况,并选择一条安全的车道进行变道,以避免碰撞并保持行驶效率。

cpp 复制代码
 
  
* 
     
      
     

     

      
       void reactiveLaneChange(VehicleState &vehicleState, const std::vector<Obstacle> &obstacles) {
      
     

  
* 
     
      
     

     

          // 假设vehicleState包含车辆当前状态和位置
      
     

  
* 
     
      
     

     

          // obstacles是一个包含所有障碍物的向量
      
     

  
* 
     
      
     

     

       
      
     

  
* 
     
      
     

     

          // 检查当前车道是否有障碍物
      
     

  
* 
     
      
     

     

          for (const auto &obstacle : obstacles) {
      
     

  
* 
     
      
     

     

              if (obstacle.isBlocking(vehicleState.getCurrentLane()) &&
      
     

  
* 
     
      
     

     

                  !obstacle.isTooCloseForSafePassing(vehicleState)) {
      
     

  
* 
     
      
     

     

                  // 寻找安全的车道进行变道
      
     

  
* 
     
      
     

     

                  int safeLane = findSafeLane(vehicleState, obstacles);
      
     

  
* 
     
      
     

     

                  if (safeLane != -1) {
      
     

  
* 
     
      
     

     

                      vehicleState.changeLane(safeLane);
      
     

  
* 
     
      
     

     

                      break;
      
     

  
* 
     
      
     

     

                  }
      
     

  
* 
     
      
     

     

              }
      
     

  
* 
     
      
     

     

          }
      
     

  
* 
     
      
     

     

      }
      
     

  
* 
     
      
     

     

       
      
     

  
* 
     
      
     

     

      
       int findSafeLane(const VehicleState &vehicleState, const std::vector<Obstacle> &obstacles) {
      
     

  
* 
     
      
     

     

          // 遍历所有车道,寻找没有障碍物或障碍物距离安全的车道
      
     

  
* 
     
      
     

     

          for (int lane = 0; lane < MAX_LANES; ++lane) {
      
     

  
* 
     
      
     

     

              if (!isLaneBlocked(obstacles, lane)) {
      
     

  
* 
     
      
     

     

                  return lane;
      
     

  
* 
     
      
     

     

              }
      
     

  
* 
     
      
     

     

          }
      
     

  
* 
     
      
     

     

          return -1; // 如果所有车道都被阻塞,返回-1
      
     

  
* 
     
      
     

     

      }
      
     

  
* 
     
      
     

     

       
      
     

  
* 
     
      
     

     

      
       bool isLaneBlocked(const std::vector<Obstacle> &obstacles, int lane) {
      
     

  
* 
     
      
     

     

          // 检查指定车道是否有障碍物
      
     

  
* 
     
      
     

     

          for (const auto &obstacle : obstacles) {
      
     

  
* 
     
      
     

     

              if (obstacle.isInLane(lane)) {
      
     

  
* 
     
      
     

     

                  return true;
      
     

  
* 
     
      
     

     

              }
      
     

  
* 
     
      
     

     

          }
      
     

  
* 
     
      
     

     

          return false;
      
     

  
* 
     
      
     

     

      }
      
     

 

 AI写代码cpp
  

运行
 
 
  ![](https://csdnimg.cn/release/blogv2/dist/pc/img/runCode/icon-arrowwhite.png)
 
2. 路径-速度迭代算法

在Frenet坐标系下,EM Planner采用了路径-速度迭代算法。这一过程涉及两步:首先,算法基于当前车辆状态和环境信息,通过迭代优化求解最优路径。然后,算法利用上一帧的速度规划信息来指导下一帧的路径规划,形成一个闭环反馈机制。这一算法的关键在于,它能够同时考虑路径的平滑性和速度的合理性,以及障碍物的动态变化,通过迭代优化实现路径规划与速度控制的协同优化。

第一步:路径优化
  1. 初始化:基于当前车辆状态(位置、速度)和环境信息(静态障碍物、动态障碍物预测),设定初始路径。
  2. 成本函数构建:定义一个成本函数,该函数考虑路径的平滑性、与障碍物的最小距离、与参考路径的接近程度等因素。
  3. 优化迭代:利用优化算法(如梯度下降、遗传算法、粒子群优化等)迭代调整路径参数,以最小化成本函数。
  4. 终止条件:当路径参数变化小于某个阈值或达到最大迭代次数时,结束迭代。
第二步:速度规划
  1. 参考速度:基于路径规划的结果,确定一个初步的速度参考,考虑道路限制和车辆动力学约束。
  2. 速度调整:考虑前一帧的速度规划信息和当前路径的特性,调整速度以确保车辆能够安全、平顺地行驶。
  3. 反馈机制:利用闭环控制策略,根据车辆的实际状态和路径规划结果调整速度,确保车辆能够按照规划的路径行驶。
3. 决策和交通规则约束

EM Planner在规划过程中严格遵守交通规则作为硬约束,确保规划出的路径合法且安全。同时,算法将与障碍物的交互视为软约束,通过动态调整路径和速度,以最小化与障碍物的潜在冲突。这一机制通过决策模块实现,它为整个规划过程提供了明确的意图和目标,从而缩小了最优求解的搜索空间,提高了规划的效率和准确性。

  1. 硬约束(Hard Constraints)

    这些是必须遵守的规则,如交通信号、速度限制、道路边界等。硬约束确保了规划出的路径在法律和安全上是可行的。

    复制代码
    <pre data-index="2" class="new-version hljs set-code-height set-code-hide" name="code"><code class="language-cpp hljs"><ol class="hljs-ln" style="width:100%"><li><div class="hljs-ln-numbers"><div class="hljs-ln-line hljs-ln-n" data-line-number="1"></div></div><div class="hljs-ln-code"><div class="hljs-ln-line"><span class="hljs-comment">// 确保车辆速度不超过最大限速的硬约束函数</span></div></div></li><li><div class="hljs-ln-numbers"><div class="hljs-ln-line hljs-ln-n" data-line-number="2"></div></div><div class="hljs-ln-code"><div class="hljs-ln-line"><span class="hljs-function"><span class="hljs-type">bool</span> <span class="hljs-title">isSpeedWithinLimit</span><span class="hljs-params">(<span class="hljs-type">float</span> currentSpeed, <span class="hljs-type">float</span> maxSpeed)</span> </span>{</div></div></li><li><div class="hljs-ln-numbers"><div class="hljs-ln-line hljs-ln-n" data-line-number="3"></div></div><div class="hljs-ln-code"><div class="hljs-ln-line">    <span class="hljs-keyword">return</span> currentSpeed &lt;= maxSpeed;</div></div></li><li><div class="hljs-ln-numbers"><div class="hljs-ln-line hljs-ln-n" data-line-number="4"></div></div><div class="hljs-ln-code"><div class="hljs-ln-line">}</div></div></li><li><div class="hljs-ln-numbers"><div class="hljs-ln-line hljs-ln-n" data-line-number="5"></div></div><div class="hljs-ln-code"><div class="hljs-ln-line"> </div></div></li><li><div class="hljs-ln-numbers"><div class="hljs-ln-line hljs-ln-n" data-line-number="6"></div></div><div class="hljs-ln-code"><div class="hljs-ln-line"><span class="hljs-comment">// 确保车辆不越过交通信号灯的硬约束函数</span></div></div></li><li><div class="hljs-ln-numbers"><div class="hljs-ln-line hljs-ln-n" data-line-number="7"></div></div><div class="hljs-ln-code"><div class="hljs-ln-line"><span class="hljs-function"><span class="hljs-type">bool</span> <span class="hljs-title">isAtTrafficLight</span><span class="hljs-params">(<span class="hljs-type">float</span> vehiclePosition, <span class="hljs-type">float</span> lightPosition)</span> </span>{</div></div></li><li><div class="hljs-ln-numbers"><div class="hljs-ln-line hljs-ln-n" data-line-number="8"></div></div><div class="hljs-ln-code"><div class="hljs-ln-line">    <span class="hljs-comment">// 假设车辆在交通灯位置之前必须停下</span></div></div></li><li><div class="hljs-ln-numbers"><div class="hljs-ln-line hljs-ln-n" data-line-number="9"></div></div><div class="hljs-ln-code"><div class="hljs-ln-line">    <span class="hljs-keyword">return</span> vehiclePosition &lt;= lightPosition;</div></div></li><li><div class="hljs-ln-numbers"><div class="hljs-ln-line hljs-ln-n" data-line-number="10"></div></div><div class="hljs-ln-code"><div class="hljs-ln-line">}</div></div></li></ol></code><div class="opt-box"><button class="btn-code-notes ckeditor" style="background-image:url(https://i-operation.csdnimg.cn/images/a5fff6f6c9f0464c9a46b130c972952b.png);" data-report-view="{&quot;spm&quot;:&quot;3001.10436&quot;,&quot;dest&quot;:&quot;https://trae.com.cn?utm_source=community&amp;utm_medium=csdn&amp;utm_campaign=daima&quot;,&quot;extra&quot;:{&quot;index&quot;:2,&quot;ab&quot;:&quot;control&quot;}}" data-report-click="{&quot;spm&quot;:&quot;3001.10436&quot;,&quot;dest&quot;:&quot;https://trae.com.cn?utm_source=community&amp;utm_medium=csdn&amp;utm_campaign=daima&quot;,&quot;extra&quot;:{&quot;index&quot;:2,&quot;ab&quot;:&quot;control&quot;}}">AI写代码</button><span class="code-language" data-language="cpp">cpp</span><div class="hljs-button {2}" data-title="复制" onclick="hljs.copyCode(event)"></div><span class="code-run-btn" data-idx="2" data-report-view="{&quot;spm&quot;:&quot;3001.10758&quot;,&quot;extra&quot;:{&quot;language&quot;:&quot;cpp&quot;, &quot;idx&quot;:&quot;2&quot;}}" data-language="cpp">运行</span></div><div class="hide-preCode-box"><span class="hide-preCode-bt" data-report-view="{&quot;spm&quot;:&quot;1001.2101.3001.7365&quot;}"><img class="look-more-preCode contentImg-no-view" src="https://csdnimg.cn/release/blogv2/dist/pc/img/runCode/icon-arrowwhite.png" alt="" title=""></span></div></pre>
    </li><li>
    <p><strong>软约束(Soft Constraints)</strong>:</p>
    这些是建议性规则,如避免与障碍物的潜在冲突。软约束允许算法在不违反硬约束的情况下,对路径进行微调,以提高效率或舒适度。
    
    <pre data-index="3" class="new-version hljs set-code-height set-code-hide" name="code"><code class="language-cpp hljs"><ol class="hljs-ln" style="width:882px"><li><div class="hljs-ln-numbers"><div class="hljs-ln-line hljs-ln-n" data-line-number="1"></div></div><div class="hljs-ln-code"><div class="hljs-ln-line"><span class="hljs-comment">// 计算与障碍物的距离,作为软约束的函数</span></div></div></li><li><div class="hljs-ln-numbers"><div class="hljs-ln-line hljs-ln-n" data-line-number="2"></div></div><div class="hljs-ln-code"><div class="hljs-ln-line"><span class="hljs-function"><span class="hljs-type">float</span> <span class="hljs-title">calculateObstacleDistance</span><span class="hljs-params">(<span class="hljs-type">float</span> vehiclePosition, <span class="hljs-type">const</span> std::vector&lt;<span class="hljs-type">float</span>&gt;&amp; obstaclePositions)</span> </span>{</div></div></li><li><div class="hljs-ln-numbers"><div class="hljs-ln-line hljs-ln-n" data-line-number="3"></div></div><div class="hljs-ln-code"><div class="hljs-ln-line">    <span class="hljs-type">float</span> minDistance = std::numeric_limits&lt;<span class="hljs-type">float</span>&gt;::<span class="hljs-built_in">max</span>();</div></div></li><li><div class="hljs-ln-numbers"><div class="hljs-ln-line hljs-ln-n" data-line-number="4"></div></div><div class="hljs-ln-code"><div class="hljs-ln-line">    <span class="hljs-keyword">for</span> (<span class="hljs-type">float</span> obstaclePosition : obstaclePositions) {</div></div></li><li><div class="hljs-ln-numbers"><div class="hljs-ln-line hljs-ln-n" data-line-number="5"></div></div><div class="hljs-ln-code"><div class="hljs-ln-line">        minDistance = std::<span class="hljs-built_in">min</span>(minDistance, std::<span class="hljs-built_in">fabs</span>(vehiclePosition - obstaclePosition));</div></div></li><li><div class="hljs-ln-numbers"><div class="hljs-ln-line hljs-ln-n" data-line-number="6"></div></div><div class="hljs-ln-code"><div class="hljs-ln-line">    }</div></div></li><li><div class="hljs-ln-numbers"><div class="hljs-ln-line hljs-ln-n" data-line-number="7"></div></div><div class="hljs-ln-code"><div class="hljs-ln-line">    <span class="hljs-keyword">return</span> minDistance;</div></div></li><li><div class="hljs-ln-numbers"><div class="hljs-ln-line hljs-ln-n" data-line-number="8"></div></div><div class="hljs-ln-code"><div class="hljs-ln-line">}</div></div></li><li><div class="hljs-ln-numbers"><div class="hljs-ln-line hljs-ln-n" data-line-number="9"></div></div><div class="hljs-ln-code"><div class="hljs-ln-line"> </div></div></li><li><div class="hljs-ln-numbers"><div class="hljs-ln-line hljs-ln-n" data-line-number="10"></div></div><div class="hljs-ln-code"><div class="hljs-ln-line"><span class="hljs-comment">// 计算车辆与行人的距离,作为软约束的函数</span></div></div></li><li><div class="hljs-ln-numbers"><div class="hljs-ln-line hljs-ln-n" data-line-number="11"></div></div><div class="hljs-ln-code"><div class="hljs-ln-line"><span class="hljs-function"><span class="hljs-type">float</span> <span class="hljs-title">calculatePedestrianDistance</span><span class="hljs-params">(<span class="hljs-type">float</span> vehiclePosition, <span class="hljs-type">const</span> std::vector&lt;<span class="hljs-type">float</span>&gt;&amp; pedestrianPositions)</span> </span>{</div></div></li><li><div class="hljs-ln-numbers"><div class="hljs-ln-line hljs-ln-n" data-line-number="12"></div></div><div class="hljs-ln-code"><div class="hljs-ln-line">    <span class="hljs-type">float</span> minDistance = std::numeric_limits&lt;<span class="hljs-type">float</span>&gt;::<span class="hljs-built_in">max</span>();</div></div></li><li><div class="hljs-ln-numbers"><div class="hljs-ln-line hljs-ln-n" data-line-number="13"></div></div><div class="hljs-ln-code"><div class="hljs-ln-line">    <span class="hljs-keyword">for</span> (<span class="hljs-type">float</span> pedestrianPosition : pedestrianPositions) {</div></div></li><li><div class="hljs-ln-numbers"><div class="hljs-ln-line hljs-ln-n" data-line-number="14"></div></div><div class="hljs-ln-code"><div class="hljs-ln-line">        minDistance = std::<span class="hljs-built_in">min</span>(minDistance, std::<span class="hljs-built_in">fabs</span>(vehiclePosition - pedestrianPosition));</div></div></li><li><div class="hljs-ln-numbers"><div class="hljs-ln-line hljs-ln-n" data-line-number="15"></div></div><div class="hljs-ln-code"><div class="hljs-ln-line">    }</div></div></li><li><div class="hljs-ln-numbers"><div class="hljs-ln-line hljs-ln-n" data-line-number="16"></div></div><div class="hljs-ln-code"><div class="hljs-ln-line">    <span class="hljs-keyword">return</span> minDistance;</div></div></li><li><div class="hljs-ln-numbers"><div class="hljs-ln-line hljs-ln-n" data-line-number="17"></div></div><div class="hljs-ln-code"><div class="hljs-ln-line">}</div></div></li></ol></code><div class="opt-box"><button class="btn-code-notes ckeditor" style="background-image:url(https://i-operation.csdnimg.cn/images/a5fff6f6c9f0464c9a46b130c972952b.png);" data-report-view="{&quot;spm&quot;:&quot;3001.10436&quot;,&quot;dest&quot;:&quot;https://trae.com.cn?utm_source=community&amp;utm_medium=csdn&amp;utm_campaign=daima&quot;,&quot;extra&quot;:{&quot;index&quot;:3,&quot;ab&quot;:&quot;control&quot;}}" data-report-click="{&quot;spm&quot;:&quot;3001.10436&quot;,&quot;dest&quot;:&quot;https://trae.com.cn?utm_source=community&amp;utm_medium=csdn&amp;utm_campaign=daima&quot;,&quot;extra&quot;:{&quot;index&quot;:3,&quot;ab&quot;:&quot;control&quot;}}">AI写代码</button><span class="code-language" data-language="cpp">cpp</span><div class="hljs-button {2}" data-title="复制" onclick="hljs.copyCode(event)"></div><span class="code-run-btn" data-idx="3" data-report-view="{&quot;spm&quot;:&quot;3001.10758&quot;,&quot;extra&quot;:{&quot;language&quot;:&quot;cpp&quot;, &quot;idx&quot;:&quot;3&quot;}}" data-language="cpp">运行</span></div><div class="hide-preCode-box"><span class="hide-preCode-bt" data-report-view="{&quot;spm&quot;:&quot;1001.2101.3001.7365&quot;}"><img class="look-more-preCode contentImg-no-view" src="https://csdnimg.cn/release/blogv2/dist/pc/img/runCode/icon-arrowwhite.png" alt="" title=""></span></div></pre>
    </li><li>
    <p><strong>动态调整(Dynamic Adjustment)</strong>:</p>
    算法能够根据实时环境变化(如交通流量、障碍物出现等)动态调整路径和速度,以适应不断变化的条件。</li><li>
    <p><strong>决策模块(Decision Module)</strong>:</p>
    这是算法的核心部分,负责制定策略和目标。决策模块通过分析当前环境和交通状况,确定最优的路径规划方案。</li><li>
    <p><strong>意图和目标(Intentions and Goals)</strong>:</p>
    决策模块为规划过程设定明确的意图和目标,如尽快到达目的地、避免拥堵区域等,这有助于缩小搜索空间,提高算法的效率。</li><li>
    <p><strong>最优求解搜索空间(Optimal Solution Search Space)</strong>:</p>
    通过设定意图和目标,算法可以排除那些不符合这些条件的潜在解决方案,从而减少需要考虑的选项,加快求解过程。</li><li>
    <p><strong>效率和准确性(Efficiency and Accuracy)</strong>:</p>
    通过上述机制,EM Planner能够在保证安全和合法性的同时,提供高效和准确的路径规划结果。</li></ol></blockquote>
4. SL和ST投影

Frenet坐标系是一种用于自动驾驶车辆路径规划的坐标系统,它将道路表示为一系列沿着道路中心线的点,这些点的集合称为路径。在Frenet坐标系中,每个点由两个坐标表示: 表示沿路径的距离,而表示垂直于路径的距离。EM Planner使用Frenet坐标系进行路径和速度规划,利用SL(纵向位置)和ST(速度时间)投影,在E-step(期望步)和M-step(最大化步)中处理障碍物信息。在E-step中,所有障碍物(包括静态和动态障碍物)被投影到车道Frenet坐标系中,以便于算法理解和处理。在M-step中,障碍物的位置和速度信息被映射到速度时间图(ST图)中,与车辆生成的速度信息进行比较和优化,从而确保规划出的轨迹能够安全地避免障碍物。

Cartesian坐标系到Frenet坐标系的转换
cpp 复制代码
 
  
* 
     
      
     

     

      
       // 定义点结构体,用于存储二维坐标
      
     

  
* 
     
      
     

     

      
       struct Point {
      
     

  
* 
     
      
     

     

          double x; // x坐标
      
     

  
* 
     
      
     

     

          double y; // y坐标
      
     

  
* 
     
      
     

     

      };
      
     

  
* 
     
      
     

     

       
      
     

  
* 
     
      
     

     

      
       // 定义Frenet结构体,用于存储Frenet坐标系下的s, l坐标和道路曲率
      
     

  
* 
     
      
     

     

      
       struct Frenet {
      
     

  
* 
     
      
     

     

          double s; // 沿路径的距离
      
     

  
* 
     
      
     

     

          double l; // 垂直于路径的距离,这里用l代替d
      
     

  
* 
     
      
     

     

          double yaw; // 道路曲率
      
     

  
* 
     
      
     

     

      };
      
     

  
* 
     
      
     

     

       
      
     

  
* 
     
      
     

     

      
       // 函数实现
      
     

  
* 
     
      
     

     

      
       Frenet toFrenet(const Point& car_pos, const std::vector<Point>& map_waypoints) {
      
     

  
* 
     
      
     

     

          // 初始化变量
      
     

  
* 
     
      
     

     

          double min_l = std::numeric_limits<double>::infinity(); // 最小垂直距离初始化为无穷大
      
     

  
* 
     
      
     

     

          double closest_x, closest_y; // 最近点的x, y坐标
      
     

  
* 
     
      
     

     

          double s = 0; // 初始s坐标
      
     

  
* 
     
      
     

     

          double l = 0; // 初始l坐标
      
     

  
* 
     
      
     

     

          double closest_len = 0; // 最近点到车辆的距离
      
     

  
* 
     
      
     

     

          double prev_s = 0; // 上一个点的s坐标
      
     

  
* 
     
      
     

     

          double prev_yaw = 0; // 上一个点的yaw角度
      
     

  
* 
     
      
     

     

       
      
     

  
* 
     
      
     

     

          // 遍历所有地图路径点
      
     

  
* 
     
      
     

     

          for (int i = 0; i < map_waypoints.size(); i++) {
      
     

  
* 
     
      
     

     

              // 计算当前点到车辆的x, y距离
      
     

  
* 
     
      
     

     

              double x = map_waypoints[i].x - car_pos.x;
      
     

  
* 
     
      
     

     

              double y = map_waypoints[i].y - car_pos.y;
      
     

  
* 
     
      
     

     

       
      
     

  
* 
     
      
     

     

              // 计算当前点到车辆的距离
      
     

  
* 
     
      
     

     

              double dist = sqrt(x * x + y * y);
      
     

  
* 
     
      
     

     

       
      
     

  
* 
     
      
     

     

              // 计算与前一个点的距离
      
     

  
* 
     
      
     

     

              double dist_to_prev = (i > 0) ? 
      
     

  
* 
     
      
     

     

                  sqrt((x - map_waypoints[i - 1].x) * (x - map_waypoints[i - 1].x) + 
      
     

  
* 
     
      
     

     

                       (y - map_waypoints[i - 1].y) * (y - map_waypoints[i - 1].y)) : 0;
      
     

  
* 
     
      
     

     

       
      
     

  
* 
     
      
     

     

              // 更新s坐标
      
     

  
* 
     
      
     

     

              s += dist_to_prev;
      
     

  
* 
     
      
     

     

       
      
     

  
* 
     
      
     

     

              // 如果当前点到车辆的距离小于已知的最小垂直距离,则更新最近点信息
      
     

  
* 
     
      
     

     

              if (dist < min_l) {
      
     

  
* 
     
      
     

     

                  closest_x = x;
      
     

  
* 
     
      
     

     

                  closest_y = y;
      
     

  
* 
     
      
     

     

                  closest_len = dist;
      
     

  
* 
     
      
     

     

                  min_l = dist;
      
     

  
* 
     
      
     

     

              }
      
     

  
* 
     
      
     

     

       
      
     

  
* 
     
      
     

     

              // 如果不是第一个点,计算道路的曲率和切线角度
      
     

  
* 
     
      
     

     

              if (i > 0 && dist_to_prev > 0) {
      
     

  
* 
     
      
     

     

                  // 计算当前点与前一个点的切线角度
      
     

  
* 
     
      
     

     

                  double yaw = atan2(y, x);
      
     

  
* 
     
      
     

     

                  prev_yaw = atan2(map_waypoints[i - 1].y - car_pos.y, map_waypoints[i - 1].x - car_pos.x);
      
     

  
* 
     
      
     

     

                  // 计算道路曲率
      
     

  
* 
     
      
     

     

                  prev_yaw = atan2(sin(prev_yaw - yaw), (cos(prev_yaw) * cos(yaw) + sin(prev_yaw) * sin(yaw)));
      
     

  
* 
     
      
     

     

              }
      
     

  
* 
     
      
     

     

          }
      
     

  
* 
     
      
     

     

       
      
     

  
* 
     
      
     

     

          // 计算车辆到最近点的垂直距离l
      
     

  
* 
     
      
     

     

          l = sqrt(closest_x * closest_x + closest_y * closest_y) - closest_len;
      
     

  
* 
     
      
     

     

       
      
     

  
* 
     
      
     

     

          // 计算车辆的切线角度,这里假设道路是直线,所以yaw为0
      
     

  
* 
     
      
     

     

          double final_yaw = 0;
      
     

  
* 
     
      
     

     

       
      
     

  
* 
     
      
     

     

          // 返回Frenet坐标系下的坐标
      
     

  
* 
     
      
     

     

          return {s, l, final_yaw};
      
     

  
* 
     
      
     

     

      }
      
     

 

 AI写代码cpp
  

运行
 
 
  ![](https://csdnimg.cn/release/blogv2/dist/pc/img/runCode/icon-arrowwhite.png)
 
5. DP路径和QP路径

EM Planner采用**Dynamic Programming(动态规划)和Quadratic Programming(二次规划)**两种方法生成路径。首先,通过DP算法获得一个初步的、粗略的路径解,构建出一个大致的行驶轨迹。接着,算法使用基于QP的样条曲线生成光滑路径,通过优化路径的平滑性和连续性,提高车辆行驶的舒适度和安全性。这一过程确保了路径规划的高效性和路径的平滑性,是EM Planner算法中的关键步骤之一。

Dynamic Programming(动态规划)
  1. 初步路径生成:通过将整个路径规划问题分解为一系列较小的子问题,DP能够逐步构建出一个粗略的路径解。每个子问题只考虑从当前位置到目标位置的局部最优路径。

  2. 状态空间定义:在路径规划中,状态空间通常由车辆的位置和时间(或其他相关参数)定义。

  3. 递归关系:DP通过定义状态转移方程来找到从一个状态到另一个状态的最优路径。这种递归方法允许算法利用之前的计算结果来简化当前的计算。

  4. 边界条件:DP需要定义边界条件,即当车辆到达目标位置时的成本或路径。

  5. 优化计算:DP通过回溯过程找到全局最优解,这种方法在计算上可能比较耗时,但对于初步路径生成是有效的。

cpp 复制代码
 
  
* 
     
      
     

     

      
       // 动态规划路径优化
      
     

  
* 
     
      
     

     

      
       std::pair<double, double> optimizePath(const VehicleState& vehicle, const std::vector<Obstacle>& obstacles, const std::vector<Obstacle>& map_waypoints) {
      
     

  
* 
     
      
     

     

          const int MAX_ITERATIONS = 100;
      
     

  
* 
     
      
     

     

          const double STEP_SIZE = 1.0; // 根据实际情况调整步长
      
     

  
* 
     
      
     

     

          const double THRESHOLD = 1e-6;
      
     

  
* 
     
      
     

     

          const double MAX_S = 100.0; // 最大s坐标值
      
     

  
* 
     
      
     

     

          const double MAX_L = 10.0;  // 最大l坐标值
      
     

  
* 
     
      
     

     

       
      
     

  
* 
     
      
     

     

          auto frenet_coordinates = toFrenet(vehicle, map_waypoints);
      
     

  
* 
     
      
     

     

          double s = frenet_coordinates.first;
      
     

  
* 
     
      
     

     

          double l = frenet_coordinates.second;
      
     

  
* 
     
      
     

     

          double cost = std::numeric_limits<double>::max();
      
     

  
* 
     
      
     

     

       
      
     

  
* 
     
      
     

     

          std::vector<Eigen::Vector2d> control_points; // 用于多项式插值的控制点
      
     

  
* 
     
      
     

     

       
      
     

  
* 
     
      
     

     

          for (int iter = 0; iter < MAX_ITERATIONS; ++iter) {
      
     

  
* 
     
      
     

     

              double prev_cost = cost;
      
     

  
* 
     
      
     

     

              for (double ds = -STEP_SIZE; ds <= STEP_SIZE; ds += STEP_SIZE) {
      
     

  
* 
     
      
     

     

                  for (double dl = -STEP_SIZE; dl <= STEP_SIZE; dl += STEP_SIZE) {
      
     

  
* 
     
      
     

     

                      double new_s = s + ds;
      
     

  
* 
     
      
     

     

                      double new_l = l + dl;
      
     

  
* 
     
      
     

     

       
      
     

  
* 
     
      
     

     

                      // 添加边界条件检查
      
     

  
* 
     
      
     

     

                      if (new_s < 0 || new_s > MAX_S || new_l < 0 || new_l > MAX_L) continue;
      
     

  
* 
     
      
     

     

       
      
     

  
* 
     
      
     

     

                      double new_cost = costFunction(vehicle, obstacles, new_s, new_l);
      
     

  
* 
     
      
     

     

                      if (new_cost < cost) {
      
     

  
* 
     
      
     

     

                          s = new_s;
      
     

  
* 
     
      
     

     

                          l = new_l;
      
     

  
* 
     
      
     

     

                          cost = new_cost;
      
     

  
* 
     
      
     

     

                          control_points.push_back(Eigen::Vector2d(s, l)); // 收集控制点
      
     

  
* 
     
      
     

     

                      }
      
     

  
* 
     
      
     

     

                  }
      
     

  
* 
     
      
     

     

              }
      
     

  
* 
     
      
     

     

       
      
     

  
* 
     
      
     

     

              if (fabs(prev_cost - cost) < THRESHOLD) {
      
     

  
* 
     
      
     

     

                  break; // 如果成本变化小于阈值,则停止迭代
      
     

  
* 
     
      
     

     

              }
      
     

  
* 
     
      
     

     

          }
      
     

  
* 
     
      
     

     

       
      
     

  
* 
     
      
     

     

          // 使用多项式插值进行平滑处理
      
     

  
* 
     
      
     

     

          std::vector<Eigen::Vector2d> smooth_path = generateSmoothPath(control_points);
      
     

  
* 
     
      
     

     

       
      
     

  
* 
     
      
     

     

          // 从平滑后的路径中提取最终的s和l坐标
      
     

  
* 
     
      
     

     

          // 这里假设我们取平滑路径的最后一个点作为最终路径点
      
     

  
* 
     
      
     

     

          if (!smooth_path.empty()) {
      
     

  
* 
     
      
     

     

              Eigen::Vector2d final_point = smooth_path.back();
      
     

  
* 
     
      
     

     

              s = final_point.x();
      
     

  
* 
     
      
     

     

              l = final_point.y();
      
     

  
* 
     
      
     

     

          }
      
     

  
* 
     
      
     

     

       
      
     

  
* 
     
      
     

     

          return {s, l};
      
     

  
* 
     
      
     

     

      }
      
     

  
* 
     
      
     

     

       
      
     

  
* 
     
      
     

     

      
       // 计算成本函数
      
     

  
* 
     
      
     

     

      
       double costFunction(const VehicleState& vehicle, const std::vector<Obstacle>& obstacles, double s, double l) {
      
     

  
* 
     
      
     

     

          double cost = 0.0;
      
     

  
* 
     
      
     

     

          for (const Obstacle& obs : obstacles) {
      
     

  
* 
     
      
     

     

              double dist_to_obs = hypot(s - obs.s, l - obs.l);
      
     

  
* 
     
      
     

     

              cost += 1.0 / std::max(dist_to_obs, 1e-6); // 避免除零
      
     

  
* 
     
      
     

     

          }
      
     

  
* 
     
      
     

     

          return cost;
      
     

  
* 
     
      
     

     

      }
      
     

  
* 
     
      
     

     

       
      
     

  
* 
     
      
     

     

      
       // 定义一个辅助函数,用于转换控制点到Eigen::Vector2d数组
      
     

  
* 
     
      
     

     

      
       Eigen::MatrixXd controlPointsToEigen(const std::vector<Eigen::Vector2d>& control_points) {
      
     

  
* 
     
      
     

     

          Eigen::MatrixXd eigen_control_points(control_points.size(), 2);
      
     

  
* 
     
      
     

     

          for (int i = 0; i < control_points.size(); ++i) {
      
     

  
* 
     
      
     

     

              eigen_control_points.row(i) << control_points[i].x(), control_points[i].y();
      
     

  
* 
     
      
     

     

          }
      
     

  
* 
     
      
     

     

          return eigen_control_points;
      
     

  
* 
     
      
     

     

      }
      
     

  
* 
     
      
     

     

       
      
     

  
* 
     
      
     

     

      
       // 多项式插值函数实现
      
     

  
* 
     
      
     

     

      
       std::vector<Eigen::Vector2d> generateSmoothPath(const std::vector<Eigen::Vector2d>& control_points) {
      
     

  
* 
     
      
     

     

          // 将控制点转换为Eigen矩阵
      
     

  
* 
     
      
     

     

          Eigen::MatrixXd eigen_control_points = controlPointsToEigen(control_points);
      
     

  
* 
     
      
     

     

       
      
     

  
* 
     
      
     

     

          // 定义样条参数
      
     

  
* 
     
      
     

     

          const double tension = 0.0; // 张紧参数,0表示自然样条
      
     

  
* 
     
      
     

     

          const double bias = 0.0;    // 偏差参数,0表示无偏差
      
     

  
* 
     
      
     

     

          const double smoothness = 1.0; // 平滑度参数
      
     

  
* 
     
      
     

     

       
      
     

  
* 
     
      
     

     

          // 创建三次样条曲线
      
     

  
* 
     
      
     

     

          Eigen::Spline3d spline(Eigen::Dynamic, tension, bias, smoothness);
      
     

  
* 
     
      
     

     

          
      
     

  
* 
     
      
     

     

          // 设置样条的控制点
      
     

  
* 
     
      
     

     

          spline.setControlPoints(eigen_control_points.transpose());
      
     

  
* 
     
      
     

     

       
      
     

  
* 
     
      
     

     

          // 定义插值点的数量,这里我们假设在最小和最大s坐标之间均匀采样
      
     

  
* 
     
      
     

     

          double min_s = eigen_control_points.col(0).minCoeff();
      
     

  
* 
     
      
     

     

          double max_s = eigen_control_points.col(0).maxCoeff();
      
     

  
* 
     
      
     

     

          const int num_points = 100; // 插值点的数量
      
     

  
* 
     
      
     

     

          std::vector<Eigen::Vector2d> smooth_path;
      
     

  
* 
     
      
     

     

       
      
     

  
* 
     
      
     

     

          // 计算插值点
      
     

  
* 
     
      
     

     

          for (int i = 0; i <= num_points; ++i) {
      
     

  
* 
     
      
     

     

              double s = min_s + i * (max_s - min_s) / num_points;
      
     

  
* 
     
      
     

     

              Eigen::Vector3d point_on_spline = spline(s);
      
     

  
* 
     
      
     

     

              smooth_path.emplace_back(point_on_spline(0), point_on_spline(1));
      
     

  
* 
     
      
     

     

          }
      
     

  
* 
     
      
     

     

       
      
     

  
* 
     
      
     

     

          return smooth_path;
      
     

  
* 
     
      
     

     

      }
      
     

 

 AI写代码cpp
  

运行
 
 
  ![](https://csdnimg.cn/release/blogv2/dist/pc/img/runCode/icon-arrowwhite.png)
 
Quadratic Programming(二次规划)
  1. 平滑路径生成:在DP生成的初步路径基础上,QP可以用来生成更加平滑和连续的路径。这通常涉及到最小化路径曲率或加速度变化。

  2. 目标函数:QP的目标函数通常是路径的平滑性度量,如路径的二阶导数的平方和,这代表了路径的曲率。

  3. 约束条件:QP的约束条件可能包括车辆动力学限制、避免碰撞的边界以及必须遵守的交通规则。

  4. 数值优化:QP问题通常使用数值优化方法求解,如梯度下降、牛顿法或内点法等。

  5. 实时性:与DP相比,QP可以更快地求解,使其适合于需要快速响应的实时路径规划场景。

cpp 复制代码
 
  
* 
     
      
     

     

      
       // 速度规划函数,基于二次规划求解
      
     

  
* 
     
      
     

     

      
       double planSpeedWithQP(const VehicleState& vehicle, const std::pair<double, double>& frenet_position, const std::vector<Obstacle>& obstacles) {
      
     

  
* 
     
      
     

     

          const int N = 10; // 时间步数
      
     

  
* 
     
      
     

     

          const double TIME_STEP = 0.1; // 时间步长
      
     

  
* 
     
      
     

     

          const double MAX_SPEED = 30; // 最大速度
      
     

  
* 
     
      
     

     

          const double MIN_SPEED = 0; // 最小速度
      
     

  
* 
     
      
     

     

          const double MAX_ACCEL = 5; // 最大加速度
      
     

  
* 
     
      
     

     

          const double MIN_ACCEL = -5; // 最小加速度
      
     

  
* 
     
      
     

     

       
      
     

  
* 
     
      
     

     

          // 定义QP问题的维度
      
     

  
* 
     
      
     

     

          const int n = N; // 状态变量数,即速度
      
     

  
* 
     
      
     

     

          const int m = 2 * N; // 约束数,每个时间步的上下限约束
      
     

  
* 
     
      
     

     

       
      
     

  
* 
     
      
     

     

          // 定义QP问题的矩阵和向量
      
     

  
* 
     
      
     

     

          Eigen::MatrixXd P(n, n); // 目标函数的二次项系数矩阵,对角阵
      
     

  
* 
     
      
     

     

          Eigen::VectorXd q(n); // 目标函数的线性项系数向量
      
     

  
* 
     
      
     

     

          Eigen::MatrixXd A(m, n); // 约束矩阵
      
     

  
* 
     
      
     

     

          Eigen::VectorXd l(m); // 约束下限向量
      
     

  
* 
     
      
     

     

          Eigen::VectorXd u(m); // 约束上限向量
      
     

  
* 
     
      
     

     

       
      
     

  
* 
     
      
     

     

          // 初始化QP矩阵和向量
      
     

  
* 
     
      
     

     

          P.setIdentity();
      
     

  
* 
     
      
     

     

          q.setZero();
      
     

  
* 
     
      
     

     

          A.setZero();
      
     

  
* 
     
      
     

     

          l.setZero();
      
     

  
* 
     
      
     

     

          u.setZero();
      
     

  
* 
     
      
     

     

       
      
     

  
* 
     
      
     

     

          // 设置QP矩阵和向量
      
     

  
* 
     
      
     

     

          // 设置QP矩阵和向量
      
     

  
* 
     
      
     

     

          // 目标函数:最小化加速的平方
      
     

  
* 
     
      
     

     

          for (int i = 0; i < n - 1; ++i) {
      
     

  
* 
     
      
     

     

              P(i, i) = 1.0 / (TIME_STEP * TIME_STEP);
      
     

  
* 
     
      
     

     

              P(i + 1, i) = -2.0 / (TIME_STEP * TIME_STEP);
      
     

  
* 
     
      
     

     

              P(i + 1, i + 1) = 1.0 / (TIME_STEP * TIME_STEP);
      
     

  
* 
     
      
     

     

          }
      
     

  
* 
     
      
     

     

       
      
     

  
* 
     
      
     

     

          // 约束:速度上下限
      
     

  
* 
     
      
     

     

          for (int i = 0; i < N; i++) {
      
     

  
* 
     
      
     

     

              A(i, i) = 1.0;
      
     

  
* 
     
      
     

     

              A(N + i, i) = -1.0;
      
     

  
* 
     
      
     

     

              l(i) = MIN_SPEED;
      
     

  
* 
     
      
     

     

              u(i) = MAX_SPEED;
      
     

  
* 
     
      
     

     

              l(N + i) = MIN_ACCEL;
      
     

  
* 
     
      
     

     

              u(N + i) = MAX_ACCEL;
      
     

  
* 
     
      
     

     

          }
      
     

  
* 
     
      
     

     

       
      
     

  
* 
     
      
     

     

          // 设置初始状态
      
     

  
* 
     
      
     

     

          Eigen::VectorXd x0(n);
      
     

  
* 
     
      
     

     

          x0.setZero();
      
     

  
* 
     
      
     

     

          x0(0) = vehicle.v; // 当前速度
      
     

  
* 
     
      
     

     

       
      
     

  
* 
     
      
     

     

          // 设置QP求解器
      
     

  
* 
     
      
     

     

          OSQPData data = {};
      
     

  
* 
     
      
     

     

          OSQPSettings settings = {};
      
     

  
* 
     
      
     

     

          osqp_setup(&settings, &data);
      
     

  
* 
     
      
     

     

          osqp_set_P(&settings, P.data());
      
     

  
* 
     
      
     

     

          osqp_set_A(&settings, A.data());
      
     

  
* 
     
      
     

     

          osqp_set_q(&settings, q.data());
      
     

  
* 
     
      
     

     

          osqp_set_l(&settings, l.data());
      
     

  
* 
     
      
     

     

          osqp_set_u(&settings, u.data());
      
     

  
* 
     
      
     

     

          osqp_update_settings(&settings, OSQP_DEFAULT);
      
     

  
* 
     
      
     

     

          osqp_setup(&data, &settings);
      
     

  
* 
     
      
     

     

       
      
     

  
* 
     
      
     

     

          // 求解QP问题
      
     

  
* 
     
      
     

     

          osqp_solve(&data);
      
     

  
* 
     
      
     

     

       
      
     

  
* 
     
      
     

     

          // 获取结果
      
     

  
* 
     
      
     

     

          double final_speed = x0(n - 1);
      
     

  
* 
     
      
     

     

       
      
     

  
* 
     
      
     

     

          // 清理资源
      
     

  
* 
     
      
     

     

          osqp_cleanup(&data, &settings);
      
     

  
* 
     
      
     

     

       
      
     

  
* 
     
      
     

     

          return final_speed;
      
     

  
* 
     
      
     

     

      }
      
     

 

 AI写代码cpp
  

运行
 
 
  ![](https://csdnimg.cn/release/blogv2/dist/pc/img/runCode/icon-arrowwhite.png)
 
6. 参考线轨迹决策

最后,参考线轨迹决策模块根据当前车辆状态、交通规则约束、障碍物信息以及每条轨迹的代价,综合评估并决定最优的行驶轨迹。这一决策过程考虑了车辆的动态特性、环境的动态变化以及交通规则的约束,确保了规划出的轨迹既符合安全标准,也满足舒适性和效率的要求。

cpp 复制代码
 
  
* 
     
      
     

     

      
       // 决策模块中用于评估轨迹代价的函数
      
     

  
* 
     
      
     

     

      
       float evaluateTrajectoryCost(const Trajectory& trajectory, float maxSpeed, const std::vector<float>& obstacles) {
      
     

  
* 
     
      
     

     

          // 检查速度是否超过最大限速
      
     

  
* 
     
      
     

     

          for (float position : trajectory.positions) {
      
     

  
* 
     
      
     

     

              // 这里假设有一个函数calculateSpeed(position)来计算当前位置的速度
      
     

  
* 
     
      
     

     

              float speed = calculateSpeed(position); // 需要实现calculateSpeed函数
      
     

  
* 
     
      
     

     

              if (speed > maxSpeed) {
      
     

  
* 
     
      
     

     

                  return std::numeric_limits<float>::infinity(); // 如果速度超过限制,返回无穷大代价
      
     

  
* 
     
      
     

     

              }
      
     

  
* 
     
      
     

     

          }
      
     

  
* 
     
      
     

     

       
      
     

  
* 
     
      
     

     

          // 检查是否避开了所有障碍物
      
     

  
* 
     
      
     

     

          for (float obstacle : obstacles) {
      
     

  
* 
     
      
     

     

              for (float position : trajectory.positions) {
      
     

  
* 
     
      
     

     

                  if (position == obstacle) { // 简化检查,实际可能需要更复杂的距离计算
      
     

  
* 
     
      
     

     

                      return std::numeric_limits<float>::infinity(); // 如果轨迹与障碍物相交,返回无穷大代价
      
     

  
* 
     
      
     

     

                  }
      
     

  
* 
     
      
     

     

              }
      
     

  
* 
     
      
     

     

          }
      
     

  
* 
     
      
     

     

       
      
     

  
* 
     
      
     

     

          // 如果轨迹满足所有约束条件,返回一个合理的非无穷大代价
      
     

  
* 
     
      
     

     

          // 这里只是一个示例值,实际中应根据具体情况计算
      
     

  
* 
     
      
     

     

          return 1.0f; // 可以是任何合理的初始代价值
      
     

  
* 
     
      
     

     

      }
      
     

 

 AI写代码cpp
  

运行
 
 
  ![](https://csdnimg.cn/release/blogv2/dist/pc/img/runCode/icon-arrowwhite.png)
 
EM Planner算法与端到端自动驾驶的结合

将EM Planner算法与端到端自动驾驶技术结合,是自动驾驶领域迈向更高智能化水平的关键一步。EM Planner的精细路径规划与速度控制能力,与端到端技术的环境感知和决策制定优势互补,共同构建了一个既高效又安全的自动驾驶系统。这种结合不仅提高了系统的适应性和鲁棒性,还促进了自动驾驶从理论到实践的无缝衔接,为实现全自动驾驶的愿景提供了坚实的技术支撑。随着技术的不断进步和应用场景的拓展,EM Planner与端到端自动驾驶的融合将更加深入,有望引领自动驾驶领域的未来发展方向,为智慧出行创造更多可能。

复制代码
            </div><div data-report-view="{&quot;mod&quot;:&quot;1585297308_001&quot;,&quot;spm&quot;:&quot;1001.2101.3001.6548&quot;,&quot;dest&quot;:&quot;https://blog.csdn.net/LIUMAO99/article/details/141054445&quot;,&quot;extend1&quot;:&quot;pc&quot;,&quot;ab&quot;:&quot;new&quot;}"><div></div></div>
    </div>
相关推荐
CodeWizard~2 小时前
线性筛法求解欧拉函数以及欧拉反演
算法
45288655上山打老虎2 小时前
右值引用和移动语义
算法
liulilittle2 小时前
C++ 并发双阶段队列设计原理与实现
linux·开发语言·c++·windows·算法·线程·并发
白狐_7982 小时前
【项目实战】我用一个 HTML 文件写了一个“CET-6 单词斩”
前端·算法·html
Jasmine_llq2 小时前
《P3811 【模板】模意义下的乘法逆元》
数据结构·算法·线性求逆元算法·递推求模逆元
Jacob程序员2 小时前
欧几里得距离算法-相似度
开发语言·python·算法
ffcf3 小时前
消息中间件6:Redis副本数变为0和删除PVC的区别
算法·贪心算法
CoderYanger3 小时前
动态规划算法-斐波那契数列模型:2.三步问题
开发语言·算法·leetcode·面试·职场和发展·动态规划·1024程序员节
sin_hielo3 小时前
leetcode 2211
数据结构·算法·leetcode