详细说明如何使用C++编写A*算法

news2024/10/23 16:10:30

A*算法(A-star algorithm)是一种广泛应用于路径规划和图搜索的启发式搜索算法。它结合了广度优先搜索的全面性和深度优先搜索的效率,通过估计当前路径代价和到达目标的预估代价,来找到从起点到目标的最短路径。A算法在游戏开发、机器人导航和人工智能等领域中非常常见,因为它可以有效地处理复杂的搜索空间并找到最优解。

在A*算法中,节点有三个重要参数:

  • g值:从起点到当前节点的实际代价。
  • h值:从当前节点到目标节点的预估代价,通常通过启发式函数(如曼哈顿距离或欧几里得距离)来计算。
  • f值:f = g + h,用于评估当前节点的优先级,f值越小,节点越有可能在接下来的搜索中被探索。

通过构建一个优先队列,A*算法每次从未探索的节点中选择f值最小的节点进行扩展,直至找到目标节点或遍历完整个搜索空间。

现在我们可以开始用C++实现A*算法,基于这个背景编写代码。(先把代码放上来)

#include <iostream>
#include <vector>
#include <queue>
#include <cmath>
#include <cstring>
#include <unordered_set>
using namespace std;

#define ROWS 7       // 地图行数
#define COLS 7       // 地图列数
#define ZXDJ 10       // 正交方向移动代价
#define XXDJ 14       // 对角线方向移动代价

// 枚举方向
enum dirent { 
    p_up, p_down, p_left, p_right, 
    p_lup, p_ldown, p_rdown, p_rup 
};

// 表示坐标的结构体
struct MyPoint {
    int row, col;  // 行和列
    int f, g, h;   // A* 算法中的 f = g + h
    bool operator==(const MyPoint &other) const {
        return row == other.row && col == other.col;
    }
};

// 哈希函数,用于在 unordered_set 中使用 MyPoint 作为键
struct MyPointHash {
    size_t operator()(const MyPoint &p) const {
        return p.row * 31 + p.col;  // 简单的哈希函数
    } 
};

// 树节点结构体,用于存储节点位置和父节点
struct treeNode {
    MyPoint pos;    // 节点位置
    treeNode *pParent;  // 指向父节点的指针
};

// 比较函数,优先队列中比较 f 值大小
struct compare {
    bool operator()(treeNode* a, treeNode* b) {
        return a->pos.f > b->pos.f;  // 小顶堆,f 值小的优先
    }
};

// 创建树节点函数
treeNode* createTreeNode(int row, int col) {
    treeNode* pNew = new treeNode;  // 动态分配内存
    memset(pNew, 0, sizeof(treeNode));  // 清空结构体
    pNew->pos.row = row;
    pNew->pos.col = col;
    return pNew;
}

// 计算 H 值(曼哈顿距离)
int getH(MyPoint pos, MyPoint endpos) {
    // 曼哈顿距离公式:|x1 - x2| + |y1 - y2|
    return ZXDJ * (abs(endpos.row - pos.row) + abs(endpos.col - pos.col));
}

int main() {
    // 0 表示可通行,1 表示障碍物
    int map[ROWS][COLS] = {
        {0,0,0,0,0,0,0},
        {0,0,0,0,1,0,0},
        {0,0,0,0,1,0,0},
        {0,0,0,0,1,0,0},
        {0,0,0,0,0,0,0},
        {0,0,0,0,0,0,0,},
        {0,0,0,0,0,0,0},

    };

    MyPoint beginpos = { 2, 2, 0, 0, 0 };  // 起点
    MyPoint endpos = { 2, 6, 0, 0, 0 };    // 终点

    // openList: 优先队列存储待处理节点,按照 f 值排序
    priority_queue<treeNode*, vector<treeNode*>, compare> openList;
    // closedList: 哈希集合,存储已访问的节点,避免重复处理
    unordered_set<MyPoint, MyPointHash> closedList;

    // 初始化起点节点,设置 h 和 f 值
    treeNode* pRoot = createTreeNode(beginpos.row, beginpos.col);
    pRoot->pos.h = getH(beginpos, endpos);  // 计算起点到终点的 h 值
    pRoot->pos.f = pRoot->pos.h;            // 起点的 f = g + h,其中 g = 0
    openList.push(pRoot);  // 将起点放入 openList

    bool isFindend = false;  // 标记是否找到终点
    treeNode* pCurrent = nullptr;  // 当前处理的节点

    // A* 算法主循环
    while (!openList.empty()) {
        // 从 openList 中取出 f 值最小的节点
        pCurrent = openList.top();
        openList.pop();  

        // 如果到达终点,退出循环
        if (pCurrent->pos.row == endpos.row && pCurrent->pos.col == endpos.col) {
            isFindend = true;
            break;
        }

        // 将当前节点加入 closedList
        closedList.insert(pCurrent->pos);

        // 遍历 8 个方向的邻居节点
        for (int i = 0; i < 8; ++i) {
            int newRow = pCurrent->pos.row;
            int newCol = pCurrent->pos.col;
            int newG = pCurrent->pos.g;  // 继承父节点的 g 值

            // 根据不同方向更新坐标和移动代价
            switch (i) {
                case p_up: newRow--; newG += ZXDJ; break;
                case p_down: newRow++; newG += ZXDJ; break;
                case p_left: newCol--; newG += ZXDJ; break;
                case p_right: newCol++; newG += ZXDJ; break;
                case p_lup: newRow--; newCol--; newG += XXDJ; break;
                case p_ldown: newRow++; newCol--; newG += XXDJ; break;
                case p_rdown: newRow++; newCol++; newG += XXDJ; break;
                case p_rup: newRow--; newCol++; newG += XXDJ; break;
            }

            // 边界检查,确保新位置在地图范围内
            if (newRow < 0 || newRow >= ROWS || newCol < 0 || newCol >= COLS)
                continue;

            // 障碍物检查,如果新位置为障碍物,跳过
            if (map[newRow][newCol] != 0)
                continue;


            // 创建新节点,计算其 g, h, f 值
            MyPoint newPoint = { newRow, newCol };

            // 如果新节点已在 closedList 中,跳过
            if (closedList.count(newPoint))
                continue;
            newPoint.g = newG;
            newPoint.h = getH(newPoint, endpos);  // 计算新的 h 值
            newPoint.f = newPoint.g + newPoint.h; // f = g + h

            // 创建新的子节点
            treeNode* pChild = createTreeNode(newRow, newCol);
            pChild->pos.g = newG;
            pChild->pos.h = newPoint.h;
            pChild->pos.f = newPoint.f;
            pChild->pParent = pCurrent;  // 设置父节点为当前节点

            // 检查是否在 openList 中存在更优路径
            bool isBetter = true;
            priority_queue<treeNode*, vector<treeNode*>, compare> tempQueue;
            while (!openList.empty()) {
                treeNode* existingNode = openList.top();
                openList.pop();
                // 如果发现相同位置的节点
                if (existingNode->pos.row == newRow && existingNode->pos.col == newCol) {
                    // 如果当前路径的 g 值更小,更新节点
                    if (newG < existingNode->pos.g) {
                        existingNode->pos.g = newG;
                        existingNode->pos.f = existingNode->pos.g + existingNode->pos.h;
                        existingNode->pParent = pCurrent;
                    }
                    isBetter = false;  // 已找到更优路径
                }
                tempQueue.push(existingNode);  // 重新将节点放回队列
            }
            openList = tempQueue;  // 更新 openList

            // 如果新路径更好,将子节点放入 openList
            if (isBetter) {
                openList.push(pChild);
            } else {
                delete pChild;  // 否则释放内存
            }
        }
    }

    // 输出结果,如果找到终点,则回溯输出路径
    if (isFindend) {
        cout << "找到终点!" << endl;
        while (pCurrent) {
            cout << "(" << pCurrent->pos.row << "," << pCurrent->pos.col << ")" << endl;
            pCurrent = pCurrent->pParent;  // 回溯路径
        }
    } else {
        cout << "没有找到通往终点的路径。" << endl;
    }
    system("pause");
    return 0;
}

找到终点!(2,6)(3,5)(4,4)(3,3)(2,2)

 接下来一步步分析

1、枚举是方便switch,并且明白switch(数字)的含义

enum dirent {

    p_up, p_down, p_left, p_right,

    p_lup, p_ldown, p_rdown, p_rup

}

2、表示坐标的结构体,属性就是该点的行、列、F值

代码中的 operator== 函数是 MyPoint 结构体的一个重载运算符,它用于比较两个 MyPoint 对象是否相等。具体来说,它比较两个点的行和列坐标是否相同。如果两个点的行和列坐标都相同,那么这两个点就被认为是相等的。

这个重载运算符的意义在于:

  1. 唯一性检查:在哈希表(如 unordered_set)中,需要判断元素是否已经存在。通过重载 operator==,可以直接使用 MyPoint 对象作为 unordered_set 的键,因为 unordered_set 在插入元素前需要判断元素是否已经存在。

  2. 简化比较逻辑:在代码的其他部分,你可能需要比较两个 MyPoint 对象是否相等。通过重载 operator==,可以直接使用 == 运算符进行比较,而不需要手动比较每个字段,这样代码更加简洁易读。

在这段代码中,MyPoint 结构体被用作 unordered_set 的键,以存储已经访问过的节点。如果没有重载 operator==unordered_set 将无法判断两个 MyPoint 对象是否相等,也就无法正确地存储和检查节点。

此外,代码中还定义了一个 MyPointHash 结构体,这是一个哈希函数,用于为 MyPoint 对象生成哈希值。这是因为 unordered_set 需要哈希值来快速定位元素。MyPointHash 结构体通过行和列坐标计算出一个整数作为哈希值,这个哈希值用于在哈希表中存储和查找 MyPoint 对象。

struct MyPoint {

    int row, col;  // 行和列

    int f, g, h;   // A* 算法中的 f = g + h

    bool operator==(const MyPoint &other) const {

        return row == other.row && col == other.col;

    }

};

// 哈希函数,用于在 unordered_set 中使用 MyPoint 作为键

struct MyPointHash {

    size_t operator()(const MyPoint &p) const {

        return p.row * 31 + p.col;  // 简单的哈希函数

    }

};

 3、treeNode 结构体被用来表示在 A* 算法中搜索路径树的节点。每个 treeNode 实例存储了以下信息:

  1. pos:这是一个 MyPoint 类型的成员,它包含了当前节点在地图上的位置信息,包括行(row)、列(col)坐标,以及 A* 算法中用于路径搜索的 ghf 值。g 值是从起点到当前节点的成本,h 值是从当前节点到终点的估计成本,f 值是 gh 的和,代表从起点到终点通过当前节点的总成本。

  2. pParent:这是一个指向 treeNode 类型的指针,它指向当前节点的父节点。在路径搜索树中,每个节点(除了根节点)都有一个父节点,指向它在树中的直接上级。这个指针用于重建从起点到终点的路径,一旦找到终点,可以通过追踪这些父节点指针回溯到起点。

struct treeNode {

    MyPoint pos;    // 节点位置

    treeNode *pParent;  // 指向父节点的指针

};

treeNode 结构体的使用场景如下:

  • 在 A* 算法的执行过程中,每个待考虑的位置都会创建一个 treeNode 实例。
  • 每个节点都会被评估,以确定它是否是终点,或者是否值得进一步探索其邻居节点。
  • 如果一个节点不是终点,算法会生成该节点的所有可能的子节点(即邻居),并将这些子节点加入到开放列表(openList)中,以便后续处理。
  • 每个节点都会保留指向其父节点的引用,这样一旦找到终点,就可以通过这些引用回溯到起点,从而重建整个路径。

4、创造树节点函数 

treeNode* createTreeNode(int row, int col) {

    treeNode* pNew = new treeNode;  // 动态分配内存

    memset(pNew, 0, sizeof(treeNode));  // 清空结构体

    pNew->pos.row = row;

    pNew->pos.col = col;

    return pNew;

}

  1. treeNode* createTreeNode(int row, int col):这是函数的声明,它说明函数的返回类型是 treeNode 类型的指针,接收两个整数参数 rowcol,分别代表要创建的树节点在地图上的行和列坐标。

  2. treeNode* pNew = new treeNode;:这行代码使用 new 操作符动态分配了一块内存,用于存储一个新的 treeNode 实例。new 操作符会调用 treeNode 的默认构造函数(如果存在),并返回一个指向这块内存的指针。

  3. memset(pNew, 0, sizeof(treeNode));:这行代码使用 memset 函数将新分配的内存块初始化为零。memset 是一个标准库函数,用于将一块内存中的所有字节设置为特定的值。在这里,它将 pNew 指向的内存块中的所有字节都设置为 0。这样做的目的是确保 treeNode 实例中的所有成员变量都被初始化为零值,避免使用未初始化的内存。

  4. pNew->pos.row = row;pNew->pos.col = col;:这两行代码分别设置新创建的 treeNode 实例的 pos 成员的 rowcol 字段。这两个字段分别代表节点在地图上的行和列坐标。

  5. return pNew;:这行代码返回指向新创建的 treeNode 实例的指针。这样,调用 createTreeNode 函数的代码就可以通过返回的指针来访问和操作这个新创建的树节点。

 5、计算 H 值(曼哈顿距离)

int getH(MyPoint pos, MyPoint endpos) {

    // 曼哈顿距离公式:|x1 - x2| + |y1 - y2|

    return ZXDJ * (abs(endpos.row - pos.row) + abs(endpos.col - pos.col));

}

6、主函数分析 

6.1 创建地图、初始化起点与终点

    int map[ROWS][COLS] = {

        {0,0,0,0,0,0,0},

        {0,0,0,0,1,0,0},

        {0,0,0,0,1,0,0},

        {0,0,0,0,1,0,0},

        {0,0,0,0,0,0,0},

        {0,0,0,0,0,0,0,},

        {0,0,0,0,0,0,0},

    };

 MyPoint beginpos = { 2, 2, 0, 0, 0 };  // 起点

 MyPoint endpos = { 2, 6, 0, 0, 0 };    // 终点

 6.2 创建openList和closedList

  1. priority_queue 是 C++ 标准库中的一个容器适配器,它提供了一个优先队列的功能,其中元素自动按照某种优先级顺序排列。

  2. treeNode* 是队列中存储的元素类型,这意味着队列中的每个元素都是指向 treeNode 结构体的指针。

  3. vector<treeNode*> 是内部容器类型,它指定了 priority_queue 将使用 vector(向量)来存储其元素。vector 是一个动态数组,可以根据需要自动调整大小。

  4. compare 是一个比较函数对象(或称为比较器),它定义了队列中元素的排序规则。在这个例子中,compare 结构体定义了如何比较两个 treeNode 指针的 f 值,以确定它们的优先级顺序。由于 compare 结构体中的 operator() 实现为 a->pos.f > b->pos.f,这意味着优先队列是一个小顶堆,f 值较小的元素会被认为优先级更高,并且会排在队列的前面。

  // openList: 优先队列存储待处理节点,按照 f 值排序

    priority_queue<treeNode*, vector<treeNode*>, compare> openList;

    // closedList: 哈希集合,存储已访问的节点,避免重复处理

    unordered_set<MyPoint, MyPointHash> closedList;

  1. unordered_set 是 C++ 标准库中的一个容器,它基于哈希表实现。它存储唯一的元素,并且查找、插入和删除操作的平均时间复杂度为 O(1)。

  2. MyPointunordered_set 存储的元素类型。这意味着 unordered_set 中的每个元素都将是一个 MyPoint 对象。

  3. MyPointHash 是一个自定义的哈希函数对象,它必须为 MyPoint 类型的对象提供一个哈希值。这个哈希函数对象需要定义一个 operator(),该操作符接受一个 MyPoint 对象作为参数,并返回一个 size_t 类型的哈希值。

  4. closedList 是这个 unordered_set 实例的名称,可以使用这个名字来操作这个集合,比如添加元素、检查元素是否存在等。

  6.3 初始化起始节点,将起始节点放入openlist中,此时没有处理的节点

    // 初始化起点节点,设置 h 和 f 值

    treeNode* pRoot = createTreeNode(beginpos.row, beginpos.col);

    pRoot->pos.h = getH(beginpos, endpos);  // 计算起点到终点的 h 值

    pRoot->pos.f = pRoot->pos.h;            // 起点的 f = g + h,其中 g = 0

    openList.push(pRoot);  // 将起点放入 openList

    bool isFindend = false;  // 标记是否找到终点

    treeNode* pCurrent = nullptr;  // 当前处理的节点

6.4 A*算法

  1. 首先判断 openList 是否存在值,即是否有待处理的节点。
  2. 将当前处理的节点设为起点,并从 openList 中移除该节点(此时起点是 openList 中的最小元素)。
  3. 判断当前处理节点是否为终点:
    • 如果是终点,则直接跳出 while 循环。
    • 如果不是终点,则将该节点加入 closedList,表示该节点已经被探索过。
  4. 开始遍历当前节点周围的八个节点:
    • 如果遍历的节点不在地图范围内、是障碍物、或者已经在 closedList 中存在,则直接进入下一次 for 循环迭代。
  5. 对于每个有效的遍历节点,计算其 F 值,并设置这些节点的父节点为当前处理节点。
  6. 检查 openList 中是否存在更优路径:
    • 从 openList 的最小元素开始,判断是否存在具有相同位置但 g 值更小的节点。
    • 如果找到更小的 g 值,则更新该节点的父节点为当前遍历的节点,并更新其 f 值。
    • 循环检查 openList 中所有节点,直到完成对所有节点的检查。
  7. 根据是否存在该点,决定是否将当前遍历节点加入 openList
    • 如果存在该点,则更新 openList 中相应节点的信息。
    • 如果不存在,则将当前遍历节点加入 openList
  8. 重复步骤 3 至 7,直到 openList 为空或找到终点。

    while (!openList.empty()) {

        // 从 openList 中取出 f 值最小的节点

        pCurrent = openList.top();

        openList.pop();  

        // 如果到达终点,退出循环

        if (pCurrent->pos.row == endpos.row && pCurrent->pos.col == endpos.col) {

            isFindend = true;

            break;

        }

        // 将当前节点加入 closedList

        closedList.insert(pCurrent->pos);

        // 遍历 8 个方向的邻居节点

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

            int newRow = pCurrent->pos.row;

            int newCol = pCurrent->pos.col;

            int newG = pCurrent->pos.g;  // 继承父节点的 g 值

            // 根据不同方向更新坐标和移动代价

            switch (i) {

                case p_up: newRow--; newG += ZXDJ; break;

                case p_down: newRow++; newG += ZXDJ; break;

                case p_left: newCol--; newG += ZXDJ; break;

                case p_right: newCol++; newG += ZXDJ; break;

                case p_lup: newRow--; newCol--; newG += XXDJ; break;

                case p_ldown: newRow++; newCol--; newG += XXDJ; break;

                case p_rdown: newRow++; newCol++; newG += XXDJ; break;

                case p_rup: newRow--; newCol++; newG += XXDJ; break;

            }

            // 边界检查,确保新位置在地图范围内

            if (newRow < 0 || newRow >= ROWS || newCol < 0 || newCol >= COLS)

                continue;

            // 障碍物检查,如果新位置为障碍物,跳过

            if (map[newRow][newCol] != 0)

                continue;


 

            // 创建新节点,计算其 g, h, f 值

            MyPoint newPoint = { newRow, newCol };

            // 如果新节点已在 closedList 中,跳过

            if (closedList.count(newPoint))

                continue;

对于 unordered_set<MyPoint, MyPointHash> closedList; 这样的声明,count 方法的工作方式如下:

  • 你传递一个 MyPoint 对象给 count 方法。
  • count 方法会使用 MyPointHash 哈希函数来计算这个 MyPoint 对象的哈希值。
  • 然后,它会在内部的哈希表中查找具有相同哈希值的元素。
  • 最后,它会检查这些具有相同哈希值的元素中是否有任何一个与传递给 count 方法的 MyPoint 对象相等。这是通过比较操作符(在这个例子中是 MyPoint 结构体中定义的 operator==)来完成的。

如果 count 方法找到了至少一个与传递的 MyPoint 对象相等的元素,它会返回 1(因为 unordered_set 只存储唯一的元素,所以不可能有多个相等的元素)。如果没有找到相等的元素,它会返回 0

            newPoint.g = newG;

            newPoint.h = getH(newPoint, endpos);  // 计算新的 h 值

            newPoint.f = newPoint.g + newPoint.h; // f = g + h

            // 创建新的子节点

            treeNode* pChild = createTreeNode(newRow, newCol);

            pChild->pos.g = newG;

            pChild->pos.h = newPoint.h;

            pChild->pos.f = newPoint.f;

            pChild->pParent = pCurrent;  // 设置父节点为当前节点

            // 检查是否在 openList 中存在更优路径,isBetter还可以判断该点是否已经存在

            bool isBetter = true;

            priority_queue<treeNode*, vector<treeNode*>, compare> tempQueue;

            while (!openList.empty()) {

                treeNode* existingNode = openList.top();

                openList.pop();

                // 如果发现相同位置的节点

                if (existingNode->pos.row == newRow && existingNode->pos.col == newCol) {

                    // 如果当前路径的 g 值更小,更新节点

                    if (newG < existingNode->pos.g) {

                        existingNode->pos.g = newG;

                        existingNode->pos.f = existingNode->pos.g + existingNode->pos.h;

                        existingNode->pParent = pCurrent;

                    }

                    isBetter = false;  //已找到这个节点,即不用再加入openList

                }

                tempQueue.push(existingNode);  // 重新将节点放回队列

            }

            openList = tempQueue;  // 更新 openList

           // 如果该位置的节点不存在,将子节点放入 openList

            if (isBetter) {

                openList.push(pChild);

            } else {

                delete pChild;  // 否则释放内存

            }

        }

    }

 

6.5 回溯路径

    if (isFindend) {

        cout << "找到终点!" << endl;

        while (pCurrent) {

            cout << "(" << pCurrent->pos.row << "," << pCurrent->pos.col << ")" << endl;

            pCurrent = pCurrent->pParent;  // 回溯路径

        }

    } else {

        cout << "没有找到通往终点的路径。" << endl;

    }

    system("pause");

    return 0;

 

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

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

相关文章

Python教程:制作贪吃蛇游戏存以exe文件运行

Python&#xff0c;作为一种解释型、面向对象、动态数据类型的高级程序设计语言&#xff0c;其简洁易懂的语法和丰富的库使得它成为开发小游戏的理想选择。 下面&#xff0c;我们就来一步步教大家如何用Python制作一个贪食蛇小游戏&#xff0c;并将其打包成exe程序&#xff0c…

活体人脸识别技术总结及实践

文章目录 1、背景2、人脸反伪装技术2.1 活体人脸识别常见模式2.2 学术上反伪装研究 3、工程实现3.1 Silent-Face3.2 Silent-Face模型转rknn3.3 Silent-Face模型的限制 1、背景 1.1 什么是活体检测&#xff1f; 在人脸识别之前&#xff0c;先判断一下屏幕前摄像头捕捉到的人脸是…

【Golang】Gin框架中如何定义路由

✨✨ 欢迎大家来到景天科技苑✨✨ &#x1f388;&#x1f388; 养成好习惯&#xff0c;先赞后看哦~&#x1f388;&#x1f388; &#x1f3c6; 作者简介&#xff1a;景天科技苑 &#x1f3c6;《头衔》&#xff1a;大厂架构师&#xff0c;华为云开发者社区专家博主&#xff0c;…

计算机网络:数据链路层 —— 无线局域网 WLAN

文章目录 局域网无线局域网 WLAN802.11 无线局域网802.11无线局域网的组成WLAN 的组成有固定基础设施的802.11无线局域网漫游服务 无固定基础设施的802.11无线局域网 802.11无线局域网的物理层802.11无线局域网的数据链路层不使用碰撞检测 CD 的原因CSMA/CA 协议CSMA/CA 协议的…

新探索研究生英语读写教程pdf答案(基础级)

《新探索研究生英语读写教程》的设计和编写充分考虑国内研究生人才培养目标和研究生公共英语的教学需求&#xff0c; 教学内容符合研究生认知水平&#xff0c; 学术特征突出&#xff1b;教学设计紧密围绕学术阅读、学术写作和学术研究能力培养&#xff1b;教学资源立体多元&…

阀井燃气监控仪-燃气阀门井数据远程监测设备-旭华智能

在城市的地下&#xff0c;有无数条看不见的生命线——那是为千家万户输送温暖与光明的燃气管线。然而&#xff0c;在这复杂的网络之下&#xff0c;隐藏着不可预知的风险。为了保障每一位市民的安全&#xff0c;我们推出了一款革命性的产品——“智安卫士”可燃气体监测终端。 随…

Python字符串处理深度解析:高级操作技巧、性能优化与实用案例全解

文章目录 前言&#x1f497;一、字符串的定义与特点&#x1f498;1.1 字符串的定义1.1.1 单引号和双引号的字符串定义&#xff1a;1.1.2 三引号定义多行字符串&#xff1a; &#x1f498;1.2 特点&#xff1a;&#x1f498;1.3 字符串是序列小结&#xff1a; &#x1f497;二、…

软件设计模式------抽象工厂模式

抽象工厂模式&#xff08;Abstract Factory Pattern&#xff09;&#xff0c;又称Kit模式&#xff0c;属于对象创建型模式。 一&#xff1a;先理解两个概念&#xff1a; &#xff08;1&#xff09;产品等级结构&#xff1a; 即产品的继承结构。 通俗来讲&#xff0c;就是不同品…

【计算机网络 - 基础问题】每日 3 题(四十九)

✍个人博客&#xff1a;https://blog.csdn.net/Newin2020?typeblog &#x1f4e3;专栏地址&#xff1a;http://t.csdnimg.cn/fYaBd &#x1f4da;专栏简介&#xff1a;在这个专栏中&#xff0c;我将会分享 C 面试中常见的面试题给大家~ ❤️如果有收获的话&#xff0c;欢迎点赞…

如何通过智能T0算法增加持仓收益?

第一&#xff1a;什么是智能T0算法&#xff1f;什么是智能T0算法&#xff1f;简单来说&#xff0c;就是基于用户原有的股票持仓&#xff0c;针对同一标的&#xff0c;配合智能T0算法&#xff0c;每天全自动操作&#xff0c;高抛低吸&#xff0c;抓取行情波动价差。操作后每日持…

MySQL的安装(windows,Centos,ubuntu)

目录 在Windows下安装MySQL数据库 在Centos下安装MySQL数据库 在ubuntu下安装MySQL数据库 在Windows下安装MySQL数据库 安装程序的下载地址: https://dev.mysql.com/downloads/ 点击之后就会出现下面的页面 接下来根据安装提示进行操作即可 在Centos下安装MySQL数据库 1.确认…

VMware中Ubuntu安装

VMware官网&#xff1a;https://www.vmware.com/products/desktop-hypervisor/workstation-and-fusion 先在官网下载VMware&#xff0c;一直根据默认点下一步就好了&#xff0c;记得更改安装地址哦&#xff0c;否则默认下在C盘里。 先下载好Ubuntu映像文件&#xff1a;https://…

No.18 笔记 | XXE(XML 外部实体注入)漏洞原理、分类、利用及防御整理

一、XXE 漏洞概述 &#xff08;一&#xff09;定义 XXE&#xff08;XML 外部实体注入&#xff09;漏洞源于 XML 解析器对外部实体的不当处理&#xff0c;攻击者借此注入恶意 XML 实体&#xff0c;可实现敏感文件读取、远程命令执行和内网渗透等危险操作。 &#xff08;二&am…

[含文档+PPT+源码等]精品基于Nodejs实现的水果批发市场管理系统的设计与实现

基于Node.js实现的水果批发市场管理系统的设计与实现背景&#xff0c;可以从以下几个方面进行阐述&#xff1a; 一、行业背景与市场需求 水果批发市场的重要性&#xff1a; 水果批发市场作为农产品流通的重要环节&#xff0c;承载着从生产者到消费者之间的桥梁作用。它的运营效…

传统园区与智慧园区:现代化发展的差异和优势

传统园区和智慧园区代表着城市发展不同阶段的产物&#xff0c;两者在功能、管理、环境等多个方面存在显著差异。通过对传统园区和智慧园区进行对比&#xff0c;可以清晰地看到智慧园区的诸多优势所在。 1. 功能对比&#xff1a; 传统园区通常以简单的生产、办公和商业为主要功…

1.深入理解MySQL索引底层数据结构与算法

文章目录 索引的概念数据结构二叉树红黑树B-B两者的区别 Hash 引擎数据所在位置对应关系MyISAMInnoDB 索引主键聚集索引非聚集索引联合索引 如有写的不对的请指正。 索引的概念 索引是帮助MySQL高效获取数据的排好序的数据结构 数据结构 网址&#xff1a; https://www.cs.us…

Kafka-设计思想-2

一、消息传递语义 现在我们对生产者和消费者的工作方式有了一些了解&#xff0c;让我们讨论一下Kafka在生产者和消费者之间提供的语义保证。 1、最多发送一次&#xff1a;会造成数据丢失 2、至少发送一次&#xff1a;会造成数据重复消费 3、只发送一次&#xff1a;我们想要的效…

MDB收款适配器MDBPOS

LETPOS精简版MDBPOS&#xff08;直接连接MDB协议的刷卡器&#xff0c;按照设定价格收款&#xff0c;输出脉冲&#xff09; 通过串口设定价格&#xff0c;脉冲宽度。 有人刷卡&#xff0c;扣款成功&#xff0c;输出脉冲&#xff0c;使用简单 适合把MDB协议的刷卡器连接到脉冲投…

【算法】归并排序概念及例题运用

&#x1f4e2;博客主页&#xff1a;https://blog.csdn.net/2301_779549673 &#x1f4e2;欢迎点赞 &#x1f44d; 收藏 ⭐留言 &#x1f4dd; 如有错误敬请指正&#xff01; &#x1f4e2;本文由 JohnKi 原创&#xff0c;首发于 CSDN&#x1f649; &#x1f4e2;未来很长&#…

小程序视频SDK解决方案,提供个性化开发和特效定制设计

美摄科技作为视频处理技术的领航者&#xff0c;深知在这一变革中&#xff0c;每一个细微的创新都能激发无限可能。因此&#xff0c;我们精心打造了一套小程序视频SDK解决方案&#xff0c;旨在满足不同行业、不同规模客户的多元化需求&#xff0c;携手共创视频内容的璀璨未来。 …