自动驾驶规划算法(一):A*算法原理和代码(c++与python)

news2024/11/18 22:39:59

1. A*算法简介

A*算法(A-star algorithm)诞生于1968年,由彼得·哈特(Peter Hart)、尼尔森·尼尔森(Nils Nilsson)和伯特·拉波特(Bertram Raphael)三位计算机科学家提出。它的设计初衷是为了解决路径搜索问题,尤其是通过启发式函数的引导,使得算法能够高效地在图(graph)或网格(grid)结构中找到最优路径。

A*算法是人工智能与路径搜索领域中最为经典的图搜索算法之一,因其在许多实际应用中的高效性和灵活性广泛受到关注。A*算法可以看作是对传统搜索算法的扩展和改进,结合了广度优先搜索(Breadth-First Search,BFS)的系统性和贪心搜索(Greedy Best-First Search)的启发式策略。通过精巧的启发函数设计,A*算法不仅能够找到从起点到终点的最短路径,还能够在实践中保证较高的执行效率,极大地减少了搜索的计算复杂度。本文将深入探讨A算法的原理、流程、优势、局限性及其应用场景,系统性地解读这一算法。

2. A*算法原理

A*算法是一种基于图搜索的启发式搜索算法,用于找到从起点到目标点的最短路径。它结合了 Dijkstra算法 的代价搜索和 贪婪最佳优先搜索 的启发式搜索方法,是一种高效且常用的路径规划算法。A*算法在搜索过程中使用了两种代价函数:

g(n):从起点到当前节点的实际代价,即路径的累计长度。

h(n):从当前节点到目标节点的启发式估计代价(通常使用欧几里得距离或曼哈顿距离)。

A*算法的总代价函数是:f(n) = g(n) + h(n)其中,f(n)表示从起点经过当前节点到达目标节点的估计总代价。g(n)确保路径不偏离实际代价,而h(n)引导算法朝向目标节点。

3. A*算法的过程

3.1 算法流程
  • 初始化:

    • 将起点加入开放列表(open set),即待探索的节点列表。

    • 初始化一个闭合列表(closed set),即已经探索过的节点。

  • 从开放列表中选择下一个节点:

    • 从开放列表中选择一个代价最小的节点 n,即 f(n) 最小的节点。

  • 检查目标节点:

    • 如果选中的节点 n 是目标节点,则搜索结束,路径已找到。

    • 否则,将该节点从开放列表移除,加入闭合列表。

  • 扩展节点:

    • 计算当前节点 n 的邻居节点(即相邻节点)。

    • 对于每个邻居节点:

      • 如果该邻居节点在闭合列表中,则跳过。

      • 如果不在开放列表中,则将其加入开放列表,并记录当前节点为其父节点。

      • 如果该节点已经在开放列表中,检查从当前节点 n 到该邻居节点的路径是否比之前找到的路径更短。如果更短,则更新路径。

  • 重复步骤2-4:

    • 重复从开放列表中选取代价最小的节点并进行扩展,直到找到目标节点或者开放列表为空(表示没有路径)。

  • 生成路径:

    • 当找到目标节点时,可以通过追踪各个节点的父节点来生成从起点到目标节点的路径。

3.2 启发式函数的选择

启发式函数h(n)的选择至关重要,直接影响到A*算法的效率和搜索方向。常见的启发式函数有:

1. 曼哈顿距离(Manhattan Distance):适用于网格移动,每次只能上下左右移动。 h(n) = |x_{\text{current}} - x_{\text{goal}}| + |y_{\text{current}} - y_{\text{goal}}|

2. 欧几里得距离(Euclidean Distance):适用于自由空间中任意方向的移动。 h(n) = \sqrt{(x_{\text{current}} - x_{\text{goal}})^2 + (y_{\text{current}} - y_{\text{goal}})^2}

3. 对角线距离:适用于允许对角线移动的网格。 h(n) = \max(|x_{\text{current}} - x_{\text{goal}}|, |y_{\text{current}} - y_{\text{goal}}|)

4. A*算法的优缺点

4.1 优点
  • 最优性保证:在启发式函数是可加时,A*算法能够保证找到全局最优路径。这使得它在路径规划问题中广受青睐,特别是在需要找到确切最短路径的应用场景中。

  • 高效性:A*算法通过启发式函数合理引导搜索过程,避免了无效的搜索路径,使得其效率远高于简单的遍历搜索算法。

  • 灵活性:A*算法可以适应不同的启发式函数,针对不同问题场景可以设计出特定的启发函数,使得算法适应性强。

4.2 缺点
  • 性能依赖启发式函数:A*算法的性能极大程度上取决于启发式函数的设计。如果启发式函数设计不当,可能会导致算法性能急剧下降,甚至退化为广度优先搜索。

  • 空间复杂度高:A*算法需要存储开放列表和关闭列表中的大量节点信息,这使得其空间复杂度较高。在图的规模较大时,可能会遇到内存不足的问题。

5. A*算法的应用场景

5.1 自动导航与机器人路径规划

A算法被广泛应用于导航系统和机器人路径规划。通过高效的路径搜索能力,A算法能够为自动驾驶汽车、无人机、仓库机器人等设备规划出从起点到终点的最优路径。在这些应用中,启发式函数通常基于实际的物理距离或交通路况设计。

5.2 游戏开发

在许多实时战略游戏、角色扮演游戏中,A*算法用于角色的路径规划,使得角色能够在复杂的游戏场景中智能地避开障碍物,找到最短的移动路线。

5.3 图像处理

在图像处理领域,A算法可以用于图像中的边缘检测、分割、形状匹配等问题。例如,在医学影像分析中,A算法可以用来自动检测图像中的某些特定区域,帮助医生进行诊断。

5.4 网络路由优化

A算法还可以用于网络中的路由规划,帮助选择数据包从源地址到目的地址的最优传输路径。通过设计合适的代价函数,A算法可以有效地处理不同网络拓扑结构下的路由问题。

6. A*算法的优化策略

随着A*算法在实际应用中的普及,人们不断提出对该算法的改进与优化策略,以提高其效率和适应性。常见的优化方法包括:

6.1 记忆化搜索

通过将已计算的节点信息存储在哈希表或其他数据结构中,避免重复计算,能够显著提高算法的效率。这一方法在状态空间较大、代价计算复杂的问题中尤为有效。

6.2 双向A*算法

双向A*算法通过同时从起点和终点两个方向进行搜索,在两条路径相遇时停止,从而减少搜索空间,缩短计算时间。

6.3 启发式函数改进

针对具体问题场景,通过设计更具针对性的启发式函数,能够有效减少无效搜索,提高搜索效率。例如,在机器人路径规划问题中,可以引入障碍物代价或动态环境代价,使得算法更加贴近实际应用需求。

7. 代码示例

python代码如下:


import math
import matplotlib.pyplot as plt

show_animation = True

class AStarPlanner:
    def __init__(self, ox, oy, resolution, rr):
        """
        初始化网格地图用于A*规划

        ox: 障碍物的x坐标列表 [m]
        oy: 障碍物的y坐标列表 [m]
        resolution: 网格的分辨率 [m]
        rr: 机器人半径 [m]
        """
        self.resolution = resolution
        self.rr = rr
        self.min_x, self.min_y = min(ox), min(oy)
        self.max_x, self.max_y = max(ox), max(oy)
        self.motion = self.get_motion_model()
        self.x_width = round((self.max_x - self.min_x) / self.resolution)
        self.y_width = round((self.max_y - self.min_y) / self.resolution)
        self.obstacle_map = self.calc_obstacle_map(ox, oy)

    class Node:
        """ A*算法中的节点定义,包含节点位置、代价和父节点信息 """
        def __init__(self, x, y, cost, parent_index):
            self.x = x
            self.y = y
            self.cost = cost
            self.parent_index = parent_index

        def __str__(self):
            return f"Node({self.x}, {self.y}, {self.cost}, {self.parent_index})"

    def planning(self, sx, sy, gx, gy):
        """
        A*搜索路径

        输入:
            sx, sy: 起点的x、y坐标 [m]
            gx, gy: 终点的x、y坐标 [m]
        
        输出:
            rx: 最终路径的x坐标列表
            ry: 最终路径的y坐标列表
        """
        start_node = self.Node(self.calc_xy_index(sx, self.min_x),
                               self.calc_xy_index(sy, self.min_y), 0.0, -1)
        goal_node = self.Node(self.calc_xy_index(gx, self.min_x),
                              self.calc_xy_index(gy, self.min_y), 0.0, -1)

        open_set = {self.calc_grid_index(start_node): start_node}  # 开放列表
        closed_set = {}  # 闭合列表

        while open_set:
            c_id = min(open_set, key=lambda o: open_set[o].cost + self.calc_heuristic(goal_node, open_set[o]))
            current = open_set[c_id]

            # 动画显示
            if show_animation:
                self.plot_current_node(current)

            # 如果到达目标节点
            if current.x == goal_node.x and current.y == goal_node.y:
                goal_node.parent_index = current.parent_index
                goal_node.cost = current.cost
                break

            del open_set[c_id]
            closed_set[c_id] = current

            # 扩展当前节点的邻居
            for move_x, move_y, move_cost in self.motion:
                node = self.Node(current.x + move_x, current.y + move_y,
                                 current.cost + move_cost, c_id)
                n_id = self.calc_grid_index(node)

                if not self.verify_node(node) or n_id in closed_set:
                    continue

                if n_id not in open_set or open_set[n_id].cost > node.cost:
                    open_set[n_id] = node  # 更新或添加新节点

        return self.calc_final_path(goal_node, closed_set)

    def calc_final_path(self, goal_node, closed_set):
        """ 生成最终路径 """
        rx, ry = [self.calc_grid_position(goal_node.x, self.min_x)], [self.calc_grid_position(goal_node.y, self.min_y)]
        parent_index = goal_node.parent_index
        while parent_index != -1:
            node = closed_set[parent_index]
            rx.append(self.calc_grid_position(node.x, self.min_x))
            ry.append(self.calc_grid_position(node.y, self.min_y))
            parent_index = node.parent_index
        return rx[::-1], ry[::-1]  # 翻转路径,确保从起点到终点

    def calc_heuristic(self, n1, n2):
        """ 启发式函数,使用欧氏距离计算 """
        return math.hypot(n1.x - n2.x, n1.y - n2.y)

    def calc_grid_position(self, index, min_position):
        """ 计算网格节点的实际坐标 """
        return index * self.resolution + min_position

    def calc_xy_index(self, position, min_pos):
        """ 根据实际位置计算网格索引 """
        return round((position - min_pos) / self.resolution)

    def calc_grid_index(self, node):
        """ 计算网格索引,用于在开放列表和闭合列表中存储 """
        return (node.y - self.min_y) * self.x_width + (node.x - self.min_x)

    def verify_node(self, node):
        """ 验证节点是否在地图范围内,且不在障碍物内 """
        if node.x < 0 or node.x >= self.x_width or node.y < 0 or node.y >= self.y_width:
            return False
        if self.obstacle_map[node.x][node.y]:  # 碰撞检测
            return False
        return True

    def calc_obstacle_map(self, ox, oy):
        """ 生成障碍物地图 """
        obstacle_map = [[False for _ in range(self.y_width)] for _ in range(self.x_width)]
        for ix in range(self.x_width):
            x = self.calc_grid_position(ix, self.min_x)
            for iy in range(self.y_width):
                y = self.calc_grid_position(iy, self.min_y)
                for iox, ioy in zip(ox, oy):
                    if math.hypot(iox - x, ioy - y) <= self.rr:
                        obstacle_map[ix][iy] = True
                        break
        return obstacle_map

    @staticmethod
    def get_motion_model():
        """ 机器人运动模型,定义了可能的移动方向及代价 """
        return [[1, 0, 1], [0, 1, 1], [-1, 0, 1], [0, -1, 1],
                [-1, -1, math.sqrt(2)], [-1, 1, math.sqrt(2)],
                [1, -1, math.sqrt(2)], [1, 1, math.sqrt(2)]]

    def plot_current_node(self, node):
        """ 绘制当前节点用于动画展示 """
        plt.plot(self.calc_grid_position(node.x, self.min_x),
                 self.calc_grid_position(node.y, self.min_y), "xc")
        plt.gcf().canvas.mpl_connect('key_release_event',
                                     lambda event: [exit(0) if event.key == 'escape' else None])
        if int(node.cost) % 10 == 0:  # 改为根据cost进行判断
            plt.pause(0.001)

def main():
    # 起点和终点
    sx, sy = 10.0, 10.0  # [m]
    gx, gy = 50.0, 50.0  # [m]
    grid_size = 2.0  # [m]
    robot_radius = 1.0  # [m]

    # 设置障碍物
    ox, oy = [], []
    for i in range(-10, 60):
        ox.append(i)
        oy.append(-10.0)
    for i in range(-10, 60):
        ox.append(60.0)
        oy.append(i)
    for i in range(-10, 61):
        ox.append(i)
        oy.append(60.0)
    for i in range(-10, 61):
        ox.append(-10.0)
        oy.append(i)
    for i in range(-10, 40):
        ox.append(20.0)
        oy.append(i)
    for i in range(0, 40):
        ox.append(40.0)
        oy.append(60.0 - i)

    # 动画初始化
    if show_animation:
        plt.plot(ox, oy, ".k")
        plt.plot(sx, sy, "og")
        plt.plot(gx, gy, "xb")
        plt.grid(True)
        plt.axis("equal")

    a_star = AStarPlanner(ox, oy, grid_size, robot_radius)
    rx, ry = a_star.planning(sx, sy, gx, gy)

    # 显示最终路径
    if show_animation:
        plt.plot(rx, ry, "-r")
        plt.pause(0.001)
        plt.show()

if __name__ == '__main__':
    main()

c++代码如下:

#include <iostream>
#include <vector>
#include <cmath>
#include <map>
#include <queue>
#include <algorithm>
#include <matplotlibcpp.h>  // 用于绘图

namespace plt = matplotlibcpp;

using namespace std;

class AStarPlanner {
public:
    AStarPlanner(const vector<double>& ox, const vector<double>& oy, double resolution, double rr, bool show_animation = true) {
        this->resolution = resolution;
        this->rr = rr;
        this->show_animation = show_animation;

        // 计算地图的最小和最大边界
        min_x = static_cast<int>(round(*min_element(ox.begin(), ox.end())));
        min_y = static_cast<int>(round(*min_element(oy.begin(), oy.end())));
        max_x = static_cast<int>(round(*max_element(ox.begin(), ox.end())));
        max_y = static_cast<int>(round(*max_element(oy.begin(), oy.end())));

        // 计算网格的宽度和高度
        x_width = static_cast<int>(round((max_x - min_x) / resolution));
        y_width = static_cast<int>(round((max_y - min_y) / resolution));

        // 获取运动模型
        motion = get_motion_model();

        // 计算障碍物地图
        calc_obstacle_map(ox, oy);
    }

    struct Node {
        int x;  // 网格索引x
        int y;  // 网格索引y
        double cost;  // 从起点到当前节点的代价
        int parent_index;  // 父节点的索引

        Node(int x_, int y_, double cost_, int parent_index_)
            : x(x_), y(y_), cost(cost_), parent_index(parent_index_) {}

        Node() : x(0), y(0), cost(0.0), parent_index(-1) {}  // 默认构造函数

        bool operator<(const Node& other) const {
            return cost > other.cost;  // 用于优先级队列,代价小的优先级高
        }
    };

    pair<vector<double>, vector<double>> planning(double sx, double sy, double gx, double gy) {
        // 起始节点和目标节点
        Node start_node(calc_xy_index(sx, min_x), calc_xy_index(sy, min_y), 0.0, -1);
        Node goal_node(calc_xy_index(gx, min_x), calc_xy_index(gy, min_y), 0.0, -1);

        // 开放列表和闭合列表
        map<int, Node> open_set;
        map<int, Node> closed_set;
        int start_index = calc_grid_index(start_node);
        open_set[start_index] = start_node;

        while (!open_set.empty()) {
            // 从开放列表中选取代价最小的节点
            int c_id = min_cost_node_index(open_set, goal_node);
            Node current = open_set[c_id];

            // 动画显示
            if (show_animation) {
                plot_current_node(current);
            }

            // 如果到达目标节点
            if (current.x == goal_node.x && current.y == goal_node.y) {
                goal_node.parent_index = current.parent_index;
                goal_node.cost = current.cost;
                break;
            }

            // 从开放列表移除,并加入闭合列表
            open_set.erase(c_id);
            closed_set[c_id] = current;

            // 扩展当前节点的邻居节点
            for (auto& m : motion) {
                Node node(current.x + static_cast<int>(m[0]),
                          current.y + static_cast<int>(m[1]),
                          current.cost + m[2], c_id);
                int n_id = calc_grid_index(node);

                if (!verify_node(node)) {
                    continue;
                }

                if (closed_set.find(n_id) != closed_set.end()) {
                    continue;
                }

                if (open_set.find(n_id) == open_set.end() || open_set[n_id].cost > node.cost) {
                    open_set[n_id] = node;  // 更新或添加新节点
                }
            }
        }

        // 生成最终路径
        return calc_final_path(goal_node, closed_set);
    }

private:
    double resolution;
    double rr;
    int min_x, min_y;
    int max_x, max_y;
    int x_width, y_width;
    vector<vector<bool>> obstacle_map;
    vector<vector<double>> motion;
    bool show_animation;

    void calc_obstacle_map(const vector<double>& ox, const vector<double>& oy) {
        obstacle_map.resize(x_width, vector<bool>(y_width, false));
        for (int ix = 0; ix < x_width; ix++) {
            double x = calc_grid_position(ix, min_x);
            for (int iy = 0; iy < y_width; iy++) {
                double y = calc_grid_position(iy, min_y);
                for (size_t i = 0; i < ox.size(); i++) {
                    double d = hypot(ox[i] - x, oy[i] - y);
                    if (d <= rr) {
                        obstacle_map[ix][iy] = true;
                        break;
                    }
                }
            }
        }
    }

    pair<vector<double>, vector<double>> calc_final_path(Node goal_node, map<int, Node>& closed_set) {
        vector<double> rx, ry;
        rx.push_back(calc_grid_position(goal_node.x, min_x));
        ry.push_back(calc_grid_position(goal_node.y, min_y));
        int parent_index = goal_node.parent_index;
        while (parent_index != -1) {
            Node n = closed_set[parent_index];
            rx.push_back(calc_grid_position(n.x, min_x));
            ry.push_back(calc_grid_position(n.y, min_y));
            parent_index = n.parent_index;
        }
        reverse(rx.begin(), rx.end());
        reverse(ry.begin(), ry.end());
        return make_pair(rx, ry);
    }

    double calc_heuristic(Node n1, Node n2) {
        // 启发式函数,使用欧氏距离
        return hypot(n1.x - n2.x, n1.y - n2.y);
    }

    double calc_grid_position(int index, int min_pos) {
        // 计算网格节点的实际坐标
        return index * resolution + min_pos;
    }

    int calc_xy_index(double position, int min_pos) {
        // 根据实际位置计算网格索引
        return static_cast<int>(round((position - min_pos) / resolution));
    }

    int calc_grid_index(Node node) {
        // 计算网格索引,用于在开放列表和闭合列表中存储
        return (node.y - min_y) * x_width + (node.x - min_x);
    }

    bool verify_node(Node node) {
        // 验证节点是否在地图范围内,且不在障碍物内
        int px = node.x;
        int py = node.y;

        if (px < 0 || py < 0 || px >= x_width || py >= y_width) {
            return false;
        }

        if (obstacle_map[px][py]) {
            return false;
        }

        return true;
    }

    vector<vector<double>> get_motion_model() {
        // 机器人运动模型,定义了可能的移动方向及代价
        return {
            {1, 0, 1},
            {0, 1, 1},
            {-1, 0, 1},
            {0, -1, 1},
            {-1, -1, sqrt(2)},
            {-1, 1, sqrt(2)},
            {1, -1, sqrt(2)},
            {1, 1, sqrt(2)}
        };
    }

    int min_cost_node_index(map<int, Node>& open_set, Node& goal_node) {
        // 从开放列表中找到代价最小的节点索引
        double min_cost = numeric_limits<double>::max();
        int min_index = -1;
        for (auto& item : open_set) {
            double cost = item.second.cost + calc_heuristic(item.second, goal_node);
            if (cost < min_cost) {
                min_cost = cost;
                min_index = item.first;
            }
        }
        return min_index;
    }

    void plot_current_node(Node& node) {
        // 绘制当前节点用于动画展示
        double x = calc_grid_position(node.x, min_x);
        double y = calc_grid_position(node.y, min_y);
        plt::plot({x}, {y}, "xc");
        plt::pause(0.001);
    }
};

int main() {
    // 起点和终点
    double sx = 10.0;  // [m]
    double sy = 10.0;  // [m]
    double gx = 50.0;  // [m]
    double gy = 50.0;  // [m]
    double grid_size = 2.0;  // [m]
    double robot_radius = 1.0;  // [m]
    bool show_animation = true;  // 是否显示动画

    // 设置障碍物
    vector<double> ox, oy;
    for (int i = -10; i < 60; i++) {
        ox.push_back(i);
        oy.push_back(-10.0);
    }
    for (int i = -10; i < 60; i++) {
        ox.push_back(60.0);
        oy.push_back(i);
    }
    for (int i = -10; i <= 60; i++) {
        ox.push_back(i);
        oy.push_back(60.0);
    }
    for (int i = -10; i <= 60; i++) {
        ox.push_back(-10.0);
        oy.push_back(i);
    }
    for (int i = -10; i < 40; i++) {
        ox.push_back(20.0);
        oy.push_back(i);
    }
    for (int i = 0; i < 40; i++) {
        ox.push_back(40.0);
        oy.push_back(60.0 - i);
    }

    // 创建A*规划器
    AStarPlanner a_star(ox, oy, grid_size, robot_radius, show_animation);

    // 如果显示动画,初始化绘图
    if (show_animation) {
        // 绘制障碍物
        plt::plot(ox, oy, ".k");
        // 绘制起点和终点
        plt::plot(vector<double>{sx}, vector<double>{sy}, "og");
        plt::plot(vector<double>{gx}, vector<double>{gy}, "xb");
        plt::grid(true);
        plt::axis("equal");
        plt::pause(0.001);
    }

    // 进行路径规划
    pair<vector<double>, vector<double>> result = a_star.planning(sx, sy, gx, gy);

    // 输出路径坐标
    vector<double> rx = result.first;
    vector<double> ry = result.second;

    // 绘制最终路径
    if (show_animation) {
        // 绘制最终的路径
        plt::plot(rx, ry, "-r");
        plt::pause(0.001);
        plt::show();
        plt::close();
    } else {
        cout << "找到的路径坐标:" << endl;
        for (size_t i = 0; i < rx.size(); i++) {
            cout << "(" << rx[i] << ", " << ry[i] << ")" << endl;
        }
    }

    return 0;
}

搜索结果如下:

参考文献:

https://github.com/AtsushiSakai/PythonRobotics/tree/master

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

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

相关文章

[机器学习]04-逻辑回归(python)-03-API与癌症分类案例讲解

逻辑回归&#xff08;Logistic Regression&#xff09; 的一API 介绍 关于如何配置模型中的优化器、正则化和惩罚项。 1. 逻辑回归 API 在 Scikit-learn 中&#xff0c;逻辑回归可以通过如下方式定义&#xff1a; from sklearn.linear_model import LogisticRegression ​ …

C嘎嘎入门篇:类和对象(1)

前言&#xff1a; 小编在之前讲述了C的部分入门基础&#xff0c;读者朋友一定要掌握好那些&#xff0c;因为C的学习和C有点不同&#xff0c;C的知识都是比较连贯的&#xff0c;所以我们学好了前面才可以学习后面的内容&#xff0c;本篇文章小编将会讲述C真正的入门篇&#xff1…

爬虫逆向学习(九):记录一个集cookie、请求参数、请求体、响应文本加密的站点反爬

此分享只用于学习用途&#xff0c;不作商业用途&#xff0c;若有冒犯&#xff0c;请联系处理 反爬前置信息 站点&#xff1a;aHR0cHM6Ly96d2Z3LmNxLmdvdi5jbi9pY2l0eS9pY2l0eS9lbmdpbmVlcmluZy9uYXZpZ2F0aW9u 接口&#xff1a;/icity/api-v2/cq.app.icity.engineering.Engine…

vue3组件通信(组合式API)

vue3组件通信&#xff08;组合式API&#xff09; vue3组件通信&#xff0c;采用组合式API。选项式API&#xff0c;参看官网 Vue3组件通信和Vue2的区别&#xff1a; 移出事件总线&#xff0c;使用mitt代替。 vuex换成了pinia。把.sync优化到了v-model里面了。把$listeners所…

fo-dicom开源库,深入理解项目的模块化设计

前言 DICOM&#xff08;Digital Imaging and Communications in Medicine&#xff09;是医学图像和相关信息的国际标准&#xff0c;广泛应用于医学影像领域。FO-DICOM是一个开源的DICOM库&#xff0c;提供了丰富的功能和模块&#xff0c;用于处理DICOM数据、实现DICOM网络通信…

16.面试算法-树的层次遍历与相关面试题

1. 树的层次遍历与相关面试题 1.1 层次遍历简介 广度优先在面试里出现的频率非常高&#xff0c;但是相对简单&#xff0c;题目也比较少&#xff0c;常见的题目也就七八道。 广度优先又叫层次遍历&#xff0c;基本过程如下&#xff1a; 层次遍历就是从根节点开始&#xff0c…

【STM32】 TCP/IP通信协议(1)

一、前言 TCP/IP是干啥的&#xff1f;它跟SPI、IIC、CAN有什么区别&#xff1f;它如何实现stm32的通讯&#xff1f;如何去配置&#xff1f;为了搞懂这些问题&#xff0c;查询资料可解决如下疑问&#xff1a; 1.为什么要用以太网通信? 以太网(Ethernet) 是指遵守 IEEE 802.3 …

【mbti课堂】计算机系统的六个层次与指令系统

课件&#xff1a;可以从 6 个层次分析和看待计算机系统的基本组成。 指令系统层处在硬件系统和软 件系统之间&#xff0c; 是硬、 软件之间的接口部分&#xff0c; 对两部分都有重要影响。 硬件系统用于实现每条指令的功能&#xff0c; 解决指令之间的衔接关系&#xff1b; 软件…

Golang | Leetcode Golang题解之第437题路径总和III

题目&#xff1a; 题解&#xff1a; func pathSum(root *TreeNode, targetSum int) (ans int) {preSum : map[int64]int{0: 1}var dfs func(*TreeNode, int64)dfs func(node *TreeNode, curr int64) {if node nil {return}curr int64(node.Val)ans preSum[curr-int64(targ…

复杂网络分析_NetworkX

一&#xff1a;NetworkX简介 NetworkX库是一个用于创建、操作复杂网络的结构、动态和功能的Python库。在经济网络中&#xff0c;它可以帮助分析各种经济实体&#xff08;如公司、个人、国家&#xff09;之间的相互关系和互动模式。以下是一些NetworkX在经济网络分析中的实际应…

ArrayList源码实现(一)

ArrayList源码实现&#xff08;一&#xff09; 1. ArrayList的大小是如何自动增加的&#xff1f; 初始化 在构造函数中&#xff0c;可以设定列表的初始值大小&#xff0c;如果没有的话默认使用&#xff0c;提供的静态数据 public ArrayList(int initialCapacity) {if (initi…

Milvus - 架构设计详解

Milvus 是一个专为在大规模密集向量数据集上进行相似性搜索而设计的开源向量数据库系统。其架构建立在流行的向量搜索库之上&#xff0c;如 Faiss、HNSW、DiskANN 和 SCANN&#xff0c;能够处理数百万、数十亿甚至数万亿的向量数据。为了全面了解 Milvus 架构&#xff0c;我们首…

计网作业3

1.交换机是依据 MAC地址 来转发数据包的 2.数据链路层 负责将数据封装成帧&#xff0c;在相邻节点间进行传输 数据链路层负责以下任务&#xff1a; 封装数据 物理地址寻址&#xff1a;使用MAC地址进行寻址&#xff0c;确保数据能够在局域网中正确传输到目标节点 介质访问控…

正确理解C++的友元friend

C的友元&#xff08;friend&#xff09;是个很重要的概念&#xff0c;该如何正确理解呢&#xff1f;本文将以友元函数为例讲解一下&#xff0c;仔细看。 友元的特性&#xff1a; 1、使用friend修饰的成员函数可以访问其他成员的私有成员(private)和保护成员(protected)。 2、…

快速上手基于Vue的动画引擎vueuse/motion

在现代前端开发中&#xff0c;动画可以极大地提升用户体验和界面美感。VueUse Motion 是一个强大的动画库&#xff0c;旨在为 Vue 应用提供简单易用的动画功能。那我们就来看看它在Vue项目中是如何应用的&#xff0c;快速手上为主&#xff0c;官网地址&#x1f447; vueuse/mo…

DevExpress WinForms中文教程:Data Grid - 如何添加或删除行?

本教程介绍DevExpress WinForm的Data Grid控件UI元素和API&#xff0c;它们使您和最终用户能够添加或删除数据行。您将首选学习如何启用内置的数据导航器&#xff0c;然后学习如何使用Microsoft Outlook启发的New Item行添加新记录。最后教程将向您展示基本的API&#xff0c;它…

全景可视化特点+可视化功能实现

全景可视化介绍 全景可视化是一种利用现代计算机技术、图像处理技术和虚拟现实技术&#xff0c;将现实世界中的场景以360度全景的方式呈现在用户面前的技术。它不仅能够提供水平方向360度的全景视野&#xff0c;还能通过垂直方向的视角变化&#xff0c;实现上下视角的调节&…

MVC core 、MVC framework addTagHelper、htmlhelper 、Environment

mvc core 标签助手 TagHelper 只有core 支持 htmlhelper mvc、mvc core 都支持 Environment <environment include"Development">*开发环境,使用不压缩的文件&#xff0c;排除压缩的文件*<link rel"stylesheet" asp-href-include"css/*"…

Linux相关概念和重要知识点(8)(操作系统、进程的概念)

1.操作系统&#xff08;OS&#xff09; &#xff08;1&#xff09;基本结构的认识 任何计算机系统都包含一个基本的程序集合&#xff0c;用于实现计算机最基本最底层的操作&#xff0c;这个软件称为操作系统。操作系统大部分使用C语言编写&#xff0c;少量使用汇编语言。 从…

[极客大挑战 2019]RCE ME1

<?php error_reporting(0); if(isset($_GET[code])){$code$_GET[code];if(strlen($code)>40){die("This is too Long.");}if(preg_match("/[A-Za-z0-9]/",$code)){die("NO.");}eval($code); } else{highlight_file(__FILE__); }// ?>…