A* 算法研究(附 Python / C++ 实现)

news2024/12/28 20:56:48

A* 算法研究

参考

A*寻路算法详解 #A星 #启发式搜索

路径规划之 A* 算法

最短路搜索-从Dijkstra到Best-First再到A-Star

路径规划算法学习笔记(一):A*算法

A*算法寻路(C++代码实现)

《基于A*算法的自动泊车全局路径规划算法研究》

《基于ROS平台的仓储AGV系统设计及路径规划研究》

理论基础

先去读那两篇论文的笔记

前面已经学习了 Dijkstra 算法和 Best-First-Search 算法,A* 算法可以看作是这两种算法的组合

A* 算法的思想核心是:核心想是:每一步的选择既要考虑离初始点的距离,也要考虑离目标点的距

这里首先定义这两个需要考虑的距离(函数),G 用来表示当前位置离起点的距离(也就是走过的路径),H 用来表示当前位置离终点的距离(和Best-First一样的启发式函数,比如曼哈顿距离),那么 A* 算法每一步考虑的就是

在这里插入图片描述

A* 算法在运算过程中,每次从优先队列中选取 f(n)最小(优先级最高)的节点作为下一个待遍历的节点

A* 算法使用两个集合来表示待遍历的节点,与已经遍历过的节点,这通常称之为 open_setclose_set

完整的A*算法描述如下:

  1. 初始化起始节点和目标节点,并将起始节点添加到 open_set
  2. 初始化每个节点的代价函数值:g(n) 表示从起始节点到节点 n 的实际代价,h(n) 表示从节点 n 到目标节点的估计代价(启发函数)
  3. 初始化每个节点的父节点为 null
  4. 当 open_set 不为空时,重复以下步骤:
    • 从 open_set 中选择具有最小 f(n) 值的节点,其中 f(n) = g(n) + h(n)
    • 将该节点移出 open_set,并将其添加到 close_set
    • 如果选择的节点是目标节点,表示找到了最短路径,可以停止搜索
    • 否则,对该节点的相邻节点进行以下操作:
      • 如果相邻节点不在 open_set 和 close_set 中,将其添加到 open_set ,并更新其g(n)和h(n)值以及父节点
      • 如果相邻节点已经在 open_set 中,检查通过当前节点到达该相邻节点的路径是否更短,如果是,则更新相邻节点的g(n)值和父节点
    • 继续下一轮循环
  5. 如果 open_set 为空,表示没有找到最短路径,搜索失败
  6. 一旦找到最短路径,可以通过回溯每个节点的父节点来还原整条路径

这里的 open_set 和 close_set 其实就相当于 Dijkstra 算法中的 U 集和 S 集

初始时 S 集中只有起点,U 中是除起点外的其余顶点;open_set 初始时只有顶点,而 close_set 为空

Dijkstra 算法每次迭代时从 U 集中找出路径最短的顶点,并加入 S 集中,同时更新 U 集中顶点的路径及其 parent 节点;A* 算法从 open_set 中选取 f(n) 值最小的节点,加入 close_set,对其相邻节点进行操作,注意如果相邻节点已经在 open_set 中,检查通过当前节点到达该相邻节点的路径是否更短,如果是,则更新相邻节点的 g(n) 值和父节点,这是容易忽略的部分

其实 Dijkstra 算法在更新 U 集时也是更新刚加入 S 集顶点的相邻节点,检查通过当前节点到达该节点相邻节点的路径是否更短,因此 A* 算法也要有相应的操作

启发函数

启发函数对 A* 算法有很重要的影响

  • 在极端情况下,当启发函数 h(n) 始终为0,则将由 g(n) 决定节点的优先级,此时算法就退化成了Dijkstra算法
  • 如果 h(n) 始终小于等于节点n到终点的代价,则A*算法保证一定能够找到最短路径。但是当h(n)的值越小,算法将遍历越多的节点,也就导致算法越慢
  • 如果h(n)完全等于节点n到终点的代价,则A*算法将找到最佳路径,并且速度很快。可惜并非所有场景下都能做到这一点。因为在没有达到终点之前,很难确切算出距离终点还有多远
  • 如果h(n)的值比节点n到终点的代价要,则A*算法不能保证找到最短路径,不过此时会很快
  • 在另外一个极端情况下,如果h(n)相较于g(n)大很多,则此时只有h(n)产生效果,这也就变成了最佳优先搜索

关于距离

对于网格形式的图,有以下这些启发函数可以使用:

  • 如果图形中只允许朝上下左右四个方向移动,则可以使用曼哈顿距离(Manhattan distance)
  • 如果图形中允许朝八个方向移动,则可以使用对角距离
  • 如果图形中允许朝任何方向移动,则可以使用欧几里得距离(Euclidean distance)

曼哈顿距离

在这里插入图片描述

计算曼哈顿距离的函数如下,这里的D是指两个相邻节点之间的移动代价,通常是一个固定的常数

function heuristic(node) =
    dx = abs(node.x - goal.x)
    dy = abs(node.y - goal.y)
    return D * (dx + dy)

对角距离

在这里插入图片描述

计算对角距离的函数如下,这里的D2指的是两个斜着相邻节点之间的移动代价

function heuristic(node) =
    dx = abs(node.x - goal.x)
    dy = abs(node.y - goal.y)
    return D * (dx + dy) + (D2 - 2 * D) * min(dx, dy)

欧几里得距离

欧几里得距离是指两个节点之间的直线距离,其函数表示如下:

function heuristic(node) =
    dx = abs(node.x - goal.x)
    dy = abs(node.y - goal.y)
    return D * sqrt(dx * dx + dy * dy)

Python 实现

"""

A* grid planning

author: Atsushi Sakai(@Atsushi_twi)
        Nikos Kanargias (nkana@tee.gr)

See Wikipedia article (https://en.wikipedia.org/wiki/A*_search_algorithm)

"""

import math

import matplotlib.pyplot as plt

show_animation = True

class AStarPlanner:

    def __init__(self, ox, oy, resolution, rr):
        """
        Initialize grid map for a star planning

        ox: x position list of Obstacles [m]
        oy: y position list of Obstacles [m]
        resolution: grid resolution [m]
        rr: robot radius[m]
        """

        self.resolution = resolution
        self.rr = rr
        self.min_x, self.min_y = 0, 0
        self.max_x, self.max_y = 0, 0
        self.obstacle_map = None
        self.x_width, self.y_width = 0, 0
        self.motion = self.get_motion_model()
        self.calc_obstacle_map(ox, oy)

    class Node:
        def __init__(self, x, y, cost, parent_index):
            self.x = x  # index of grid
            self.y = y  # index of grid
            self.cost = cost
            self.parent_index = parent_index

        def __str__(self):
            return str(self.x) + "," + str(self.y) + "," + str(
                self.cost) + "," + str(self.parent_index)

    def planning(self, sx, sy, gx, gy):
        """
        A star path search

        input:
            s_x: start x position [m]
            s_y: start y position [m]
            gx: goal x position [m]
            gy: goal y position [m]

        output:
            rx: x position list of the final path
            ry: y position list of the final path
        """

        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, closed_set = dict(), dict()
        open_set[self.calc_grid_index(start_node)] = start_node

        while True:
            if len(open_set) == 0:
                print("Open set is empty..")
                break

            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]

            # show graph
            if show_animation:  # pragma: no cover
                plt.plot(self.calc_grid_position(current.x, self.min_x),
                         self.calc_grid_position(current.y, self.min_y), "xc")
                # for stopping simulation with the esc key.
                plt.gcf().canvas.mpl_connect('key_release_event',
                                             lambda event: [exit(
                                                 0) if event.key == 'escape' else None])
                if len(closed_set.keys()) % 10 == 0:
                    plt.pause(0.001)

            if current.x == goal_node.x and current.y == goal_node.y:
                print("Find goal")
                goal_node.parent_index = current.parent_index
                goal_node.cost = current.cost
                break

            # Remove the item from the open set
            del open_set[c_id]

            # Add it to the closed set
            closed_set[c_id] = current

            # expand_grid search grid based on motion model
            for i, _ in enumerate(self.motion):
                node = self.Node(current.x + self.motion[i][0],
                                 current.y + self.motion[i][1],
                                 current.cost + self.motion[i][2], c_id)
                n_id = self.calc_grid_index(node)

                # If the node is not safe, do nothing
                if not self.verify_node(node):
                    continue

                if n_id in closed_set:
                    continue

                if n_id not in open_set:
                    open_set[n_id] = node  # discovered a new node
                else:
                    if open_set[n_id].cost > node.cost:
                        # This path is the best until now. record it
                        open_set[n_id] = node

        rx, ry = self.calc_final_path(goal_node, closed_set)

        return rx, ry

    def calc_final_path(self, goal_node, closed_set):
        # generate final course
        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:
            n = closed_set[parent_index]
            rx.append(self.calc_grid_position(n.x, self.min_x))
            ry.append(self.calc_grid_position(n.y, self.min_y))
            parent_index = n.parent_index

        return rx, ry

    @staticmethod
    def calc_heuristic(n1, n2):
        w = 1.0  # weight of heuristic
        d = w * math.hypot(n1.x - n2.x, n1.y - n2.y)
        return d

    def calc_grid_position(self, index, min_position):
        """
        calc grid position

        :param index:
        :param min_position:
        :return:
        """
        pos = index * self.resolution + min_position
        return pos

    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):
        px = self.calc_grid_position(node.x, self.min_x)
        py = self.calc_grid_position(node.y, self.min_y)

        if px < self.min_x:
            return False
        elif py < self.min_y:
            return False
        elif px >= self.max_x:
            return False
        elif py >= self.max_y:
            return False

        # collision check
        if self.obstacle_map[node.x][node.y]:
            return False

        return True

    def calc_obstacle_map(self, ox, oy):

        self.min_x = round(min(ox))
        self.min_y = round(min(oy))
        self.max_x = round(max(ox))
        self.max_y = round(max(oy))
        print("min_x:", self.min_x)
        print("min_y:", self.min_y)
        print("max_x:", self.max_x)
        print("max_y:", self.max_y)

        self.x_width = round((self.max_x - self.min_x) / self.resolution)
        self.y_width = round((self.max_y - self.min_y) / self.resolution)
        print("x_width:", self.x_width)
        print("y_width:", self.y_width)

        # obstacle map generation
        self.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):
                    d = math.hypot(iox - x, ioy - y)
                    if d <= self.rr:
                        self.obstacle_map[ix][iy] = True
                        break

    @staticmethod
    def get_motion_model():
        # dx, dy, cost
        motion = [[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)]]

        return motion

def main():
    print(__file__ + " start!!")

    # start and goal position
    sx = 10.0  # [m]
    sy = 10.0  # [m]
    gx = 50.0  # [m]
    gy = 50.0  # [m]
    grid_size = 2.0  # [m]
    robot_radius = 1.0  # [m]

    # set obstacle positions
    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:  # pragma: no cover
        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:  # pragma: no cover
        plt.plot(rx, ry, "-r")
        plt.pause(0.001)
        plt.show()

if __name__ == '__main__':
    main()

输出如下

E:\Junior\Code\path_plan_test\a_star_py\a_star_py\a_star_py.py start!!
min_x: -10
min_y: -10
max_x: 60
max_y: 60
x_width: 35
y_width: 35
Find goal

在这里插入图片描述

A* 算法主要体现在 planning() 函数中,过程与 A* 算法的描述是对应的

def planning(self, sx, sy, gx, gy):
        """
        A star path search

        input:
            s_x: start x position [m]
            s_y: start y position [m]
            gx: goal x position [m]
            gy: goal y position [m]

        output:
            rx: x position list of the final path
            ry: y position list of the final path
        """

        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, closed_set = dict(), dict()
        open_set[self.calc_grid_index(start_node)] = start_node

        while True:
            if len(open_set) == 0:
                print("Open set is empty..")
                break

            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]

            # show graph
            if show_animation:  # pragma: no cover
                plt.plot(self.calc_grid_position(current.x, self.min_x),
                         self.calc_grid_position(current.y, self.min_y), "xc")
                # for stopping simulation with the esc key.
                plt.gcf().canvas.mpl_connect('key_release_event',
                                             lambda event: [exit(
                                                 0) if event.key == 'escape' else None])
                if len(closed_set.keys()) % 10 == 0:
                    plt.pause(0.001)

            if current.x == goal_node.x and current.y == goal_node.y:
                print("Find goal")
                goal_node.parent_index = current.parent_index
                goal_node.cost = current.cost
                break

            # Remove the item from the open set
            del open_set[c_id]

            # Add it to the closed set
            closed_set[c_id] = current

            # expand_grid search grid based on motion model
            for i, _ in enumerate(self.motion):
                node = self.Node(current.x + self.motion[i][0],
                                 current.y + self.motion[i][1],
                                 current.cost + self.motion[i][2], c_id)
                n_id = self.calc_grid_index(node)

                # If the node is not safe, do nothing
                if not self.verify_node(node):
                    continue

                if n_id in closed_set:
                    continue

                if n_id not in open_set:
                    open_set[n_id] = node  # discovered a new node
                else:
                    if open_set[n_id].cost > node.cost:
                        # This path is the best until now. record it
                        open_set[n_id] = node

        rx, ry = self.calc_final_path(goal_node, closed_set)

        return rx, ry

1、初始化起始节点和目标节点,并将起始节点添加到 open_set

		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, closed_set = dict(), dict()
        open_set[self.calc_grid_index(start_node)] = start_node

2、当 open_set 为空时跳出 while 循环

				if len(open_set) == 0:
                print("Open set is empty..")
                break

3、从 open_set 中选择具有最小 f(n) 值的节点

				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]

4、如果选择的节点是目标节点,表示找到了最短路径,可以停止搜索

				if current.x == goal_node.x and current.y == goal_node.y:
                print("Find goal")
                goal_node.parent_index = current.parent_index
                goal_node.cost = current.cost
                break

5、将该节点移出 open_set,并将其添加到 close_set

			# Remove the item from the open set
            del open_set[c_id]

            # Add it to the closed set
            closed_set[c_id] = current

6、对该节点的相邻节点进行操作

			# expand_grid search grid based on motion model
            for i, _ in enumerate(self.motion):
                node = self.Node(current.x + self.motion[i][0],
                                 current.y + self.motion[i][1],
                                 current.cost + self.motion[i][2], c_id)
                n_id = self.calc_grid_index(node)

                # If the node is not safe, do nothing
                if not self.verify_node(node):
                    continue

                if n_id in closed_set:
                    continue

                if n_id not in open_set:
                    open_set[n_id] = node  # discovered a new node
                else:
                    if open_set[n_id].cost > node.cost:
                        # This path is the best until now. record it
                        open_set[n_id] = node

7、回溯每个节点的父节点来还原整条路径

rx, ry = self.calc_final_path(goal_node, closed_set)

C++ 实现

  1. 定义一个三维数组 path,用于存储每个位置的方格对应的“父方格”的坐标
  2. 二维数组 valF 保序每个方格目前情况下最小的 F 值
  3. 由于每次需要从 open 表中弹出的是F值最小的节点,选择使用优先队列来作为 open 表
  4. 定义 visit 二维数组作为 close 表,初始值false,对应位置为true时表示已经加入 close 表
#include<iostream>
#include<algorithm>
#include<string>
#include<vector>
#include<cmath>
#include<queue>
#define N 6 // 棋盘/迷宫 的阶数 
using namespace std;

class Node
{
public:
	int x, y; // 节点所在位置	
	int F, G, H; // G:从起点开始,沿着产的路径,移动到网格上指定方格的移动耗费。
				 // H:从网格上那个方格移动到终点B的预估移动耗费,使用曼哈顿距离。 
				 // F = G + H 
	Node(int a, int b) :x(a), y(b) {}

	// 重载操作符,使优先队列以F值大小为标准维持堆 
	bool operator < (const Node& a) const
	{
		return F > a.F;
	}
};

// 定义八个方向 
int dir[8][2] = { {-1,-1}, {-1, 0}, {-1, 1}, {0, -1},
		 {0, 1},  {1, -1}, {1, 0},  {1, 1} };
// 优先队列,就相当于open表 
priority_queue<Node>que;
// 棋盘
int qp[N][N] = { {0,0,0,0,0,0},
		 {0,1,1,0,1,1},
		 {0,0,1,0,0,0},
			 {0,0,1,1,1,0},
		 {0,1,1,0,0,0},
		 {1,1,0,0,0,0} };
bool visit[N][N]; // 访问情况记录,close表 
int valF[N][N];   // 记录每个节点对应的F值
int path[N][N][2]; // 存储每个节点的父节点

int Manhuattan(int x, int y, int x1, int y1); // 计算曼哈顿距离 
bool NodeIsLegal(int x, int y, int xx, int yy); // 判断位置合法性
void A_start(int x0, int y0, int x1, int y1); // A*算法 
void PrintPath(int x1, int y1); // 打印路径

/* ----------------主函数------------------- */
int main()
{
	fill(visit[0], visit[0] + N * N, false); // 将visit数组赋初值false
	fill(valF[0], valF[0] + N * N, 0); // 初始化F全为0 
	fill(path[0][0], path[0][0] + N * N * 2, -1); // 路径同样赋初值-1 

	//  // 起点 // 终点
	int x0, y0, x1, y1;
	cout << "输入起点:";
	cin >> x0 >> y0;
	cout << "输入终点:";
	cin >> x1 >> y1;
	x0--; y0--; x1--; y1--;

	if (!NodeIsLegal(x0, y0, x0, y0))
	{
		cout << "非法起点!" << endl;
		return 0;
	}

	A_start(x0, y0, x1, y1);  // A*算法 
	PrintPath(x1, y1);        // 打印路径 
}

/* ----------------自定义函数------------------ */
void A_start(int x0, int y0, int x1, int y1)
{
	// 初始化起点 
	Node node(x0, y0);
	node.G = 0;
	node.H = Manhuattan(x0, y0, x1, y1);
	node.F = node.G + node.H;
	valF[x0][y0] = node.F;
	// 起点加入open表 
	que.push(node);

	while (!que.empty())
	{
		Node node_top = que.top(); que.pop();
		visit[node_top.x][node_top.y] = true; // 访问该点,加入closed表 
		if (node_top.x == x1 && node_top.y == y1) // 到达终点 
			break;

		// 遍历node_top周围的8个位置 
		for (int i = 0; i < 8; i++)
		{
			Node node_next(node_top.x + dir[i][0], node_top.y + dir[i][1]); // 创建一个node_top周围的节点 
			// 该节点坐标合法 且 未加入close表 
			if (NodeIsLegal(node_next.x, node_next.y, node_top.x, node_top.y) && !visit[node_next.x][node_next.y])
			{
				// 计算从起点并经过node_top节点到达该节点所花费的代价 
				node_next.G = node_top.G + int(sqrt(pow(dir[i][0], 2) + pow(dir[i][1], 2)) * 10);
				// 计算该节点到终点的曼哈顿距离
				node_next.H = Manhuattan(node_next.x, node_next.y, x1, y1);
				// 从起点经过node_top和该节点到达终点的估计代价
				node_next.F = node_next.G + node_next.H;

				// node_next.F < valF[node_next.x][node_next.y] 说明找到了更优的路径,则进行更新
				// valF[node_next.x][node_next.y] == 0 说明该节点还未加入open表中,则加入 
				if (node_next.F < valF[node_next.x][node_next.y] || valF[node_next.x][node_next.y] == 0)
				{
					// 保存该节点的父节点 
					path[node_next.x][node_next.y][0] = node_top.x;
					path[node_next.x][node_next.y][1] = node_top.y;
					valF[node_next.x][node_next.y] = node_next.F; // 修改该节点对应的valF值 
					que.push(node_next); // 加入open表
				}
			}
		}
	}
}

void PrintPath(int x1, int y1)
{
	if (path[x1][y1][0] == -1 || path[x1][y1][1] == -1)
	{
		cout << "没有可行路径!" << endl;
		return;
	}
	int x = x1, y = y1;
	int a, b;
	while (x != -1 || y != -1)
	{
		qp[x][y] = 2; // 将可行路径上的节点赋值为2 
		a = path[x][y][0];
		b = path[x][y][1];
		x = a;
		y = b;
	}
	// □表示未经过的节点, █表示障碍物, ☆表示可行节点 
	string s[3] = { "□", "█", "☆" };
	for (int i = 0; i < N; i++)
	{
		for (int j = 0; j < N; j++)
			cout << s[qp[i][j]] << "\t";
		cout << endl;
	}
}

int Manhuattan(int x, int y, int x1, int y1)
{
	return (abs(x - x1) + abs(y - y1)) * 10;
}

bool NodeIsLegal(int x, int y, int xx, int yy)
{
	if (x < 0 || x >= N || y < 0 || y >= N) return false; // 判断边界 
	if (qp[x][y] == 1) return false; // 判断障碍物 
	// 两节点成对角型且它们的公共相邻节点存在障碍物 
	if (x != xx && y != yy && (qp[x][yy] == 1 || qp[xx][y] == 1)) return false;
	return true;
}

运行输出如下

在这里插入图片描述

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

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

相关文章

恒生电子联合恒生聚源发布数智金融新品,聚焦大模型技术金融业务应用

6月28日&#xff0c;恒生电子和旗下子公司恒生聚源正式发布基于大语言模型技术打造的数智金融新品&#xff0c;金融智能助手光子和全新升级的智能投研平台WarrenQ。此外&#xff0c;恒生电子金融行业大模型LightGPT也首次对外亮相&#xff0c;并公布最新研发进展。 恒生电子董…

升级Win10后多了个恢复分区,有什么用

很多用户从Win7/Win8/Win8.1升级到Win10之后发现电脑硬盘上多出了一个“恢复分区”&#xff0c;64位系统下这个分区大小在450MB左右。那么为什么会多出这样一个分区&#xff0c;这个分区又是干什么的&#xff0c;能不能删除呢&#xff1f;下面以MBR硬盘情况为例来说明。 1.全盘…

MySQL数据库 SQL语言命令总结 数据类型、运算符和聚合函数汇总

数据库&#xff1a;存储数据的仓库&#xff0c;有组织的进行存储数据。SQL&#xff1a;操作关系型数据库的编程语言&#xff0c;定义了一套操作关系型数据库统一标准。常用的关系型数据库管理系统&#xff1a;Oracle、MySQL、Microsoft SQL Server等。 Oracle是大型收费数据库&…

初识express/路由/中间件

路由的概念 模块化路由 中间件(要有输入输出) 简化版本 全局生效中间件 局部生效中间件 注意事项 中间件分类 内置中间件,解析请求体/url-encoded 自定义中间件 使用querystring模块解析请求体数据 编写接口 ​​​​​​​

x86_64(intel64、amd64)和ARM64的区别以及发展

文章目录 区别引用 区别 ARM64架构 ARM 公司研发的&#xff0c;用的是精简指令集&#xff08;追求节能&#xff0c;低功耗&#xff09;。通常用于手机、平板等CPU&#xff0c;目前笔记本电脑也会采用ARM64构架的CPU&#xff0c;比如mac m1就是arm64(查看命令&#xff1a;uname…

智能佳—LoCoBot WX250 6自由度

&#xff08;用于科研与教学的ROS智能车&#xff09; LoCoBot是用于映射、导航和操纵&#xff08;可选&#xff09;等ROS研究的智能车&#xff0c;研究人员、教育工作者和学生都可以使用LoCoBot专注于高级代码的开发&#xff0c;而不是专注硬件和构建低级代码。通过开放的源代码…

EasyExcel实现指定列自定义导出

效果展示 全部导出 自定义导出 代码实现 1.引入依赖 <dependency><groupId>com.alibaba</groupId><artifactId>easyexcel</artifactId><version>3.0.1</version> </dependency> 2.实体类 Data public class User {Exc…

vue PC端完成电子签名

最近接到一任务&#xff0c;有一个功能&#xff0c;重来没有遇到过。就是电子签名 看了原型其他基本都是对接口、写表单&#xff0c;难度不大&#xff0c;先把电子签名给攻克了起。 因为项目是vue 所有使用了 vue-esign 组件 1. 安装依赖 npm install vue-esign --save2.使用…

C++中的关联容器map下标运算符[]使用分析

最近使用到C中的map&#xff0c;发现一个问题&#xff0c;如果一个键不存在时&#xff0c;下标运算符会创建一个新的元素&#xff0c;其关键字为键。 一&#xff0c;问题重现 首先看一下问题描述&#xff1a; 本题要求读入 N 名学生的成绩&#xff0c;将获得某一给定分数的学生…

最细接口自动化测试yaml框架,超全详解,一篇打通...

目录&#xff1a;导读 前言一、Python编程入门到精通二、接口自动化项目实战三、Web自动化项目实战四、App自动化项目实战五、一线大厂简历六、测试开发DevOps体系七、常用自动化测试工具八、JMeter性能测试九、总结&#xff08;尾部小惊喜&#xff09; 前言 YAML文件介绍 YAM…

津津乐道设计模式 - 责任链模式详解(教你更优雅的处理商城下单业务)

&#x1f604; 19年之后由于某些原因断更了三年&#xff0c;23年重新扬帆起航&#xff0c;推出更多优质博文&#xff0c;希望大家多多支持&#xff5e; &#x1f337; 古之立大事者&#xff0c;不惟有超世之才&#xff0c;亦必有坚忍不拔之志 &#x1f390; 个人CSND主页——Mi…

Linux系统的目录结构与基本命令

目录 Linux系统使用注意 Linux严格区分大小写 Linux文件"扩展名" Linux系统中常见的后缀名称&#xff1a; Linux中所有内容以文件形式保存 Linux中存储设备都必须在挂载之后才能使用 Linux系统的目录结构 Linux分区与Windows分区 Linux系统文件架构 Linux系…

用AI帮助小学一年级记住常用字

这几天娃就要一年级毕业放假了&#xff0c;感觉时间过得太快了。再开学就要上二年级&#xff0c;可汉字表上的区区三四百字&#xff0c;咋就那么的难读&#xff1f;难记&#xff1f;喊他们来认字&#xff0c;拉都拉不过来。哎&#xff0c;愁啊&#xff0c;替他们焦虑。突发奇想…

Docker发布VUE vhr微人事前端(Nginx 403 forbidden)

本文代码来源于&#xff08;感谢作者&#xff09; GitHub - lenve/vhr: 微人事是一个前后端分离的人力资源管理系统&#xff0c;项目采用SpringBootVue开发。 发布过程参考博主 【Docker】使用docker容器发布vue项目_docker 发布vue_TOP灬小朋友的博客-CSDN博客 1.创建Do…

食品空压机数据采集远程监控系统解决方案

食品行业是一个需求量大、安全标准高、竞争激烈的行业。随着人们消费水平的提升&#xff0c;对食品的品质、口味、营养、卫生等方面有了更高的要求。食品空压机是食品生产过程中不可缺少的设备&#xff0c;它可以提供稳定的压缩空气&#xff0c;用于食品加工、包装、运输等环节…

大聪明教你学Java | 深入浅出聊 ThreadPoolExecutor

前言 🍊作者简介: 不肯过江东丶,一个来自二线城市的程序员,致力于用“猥琐”办法解决繁琐问题,让复杂的问题变得通俗易懂。 🍊支持作者: 点赞👍、关注💖、留言💌~ 在《阿里巴巴 Java 开发手册》中有这么一个强制要求:“线程池不允许使用 Executors 去创建,而是…

6月第4周榜单丨飞瓜数据B站UP主排行榜(哔哩哔哩)发布!

飞瓜轻数发布2023年6月19日-6月25日飞瓜数据UP主排行榜&#xff08;B站平台&#xff09;&#xff0c;通过充电数、涨粉数、成长指数三个维度来体现UP主账号成长的情况&#xff0c;为用户提供B站号综合价值的数据参考&#xff0c;根据UP主成长情况用户能够快速找到运营能力强的B…

【计算机网络】数据链路层之随机接入-CSMA/CD协议

1.概念 2.信号碰撞&#xff08;冲突&#xff09; 3.解决方案 CSMA/CD 4.争用期&#xff08;端到端往返时延&#xff09; 5.最小帧长 6.最大帧长 7.指数退避算法 8.信道利用率 9.帧发送流程 10.帧接受流程 12.题目1 13.题目2 14.题目3 15 小结

linux:docker-compose下载后无法使用

参考&#xff1a; Ubuntu 安装 Docker & Docker-Compose - 知乎 解决方法: PC上下载二进制文件&#xff0c;之后filezilla上传到服务器对应目录

项目一、黑客攻击系统功能菜单->功能菜单

功能菜单 项目需求 注&#xff1a;本小节"是写功能的选项&#xff0c;在有图形化的app当中&#xff0c;肯定有选项 这章节就是讲怎么写选项的" 什么是图形化&#xff1a;把文字翻译成图像或者是图片等等。 功能选项&#xff1a; 1.网站404攻击 2.网站篡改攻击 3.网站…