HNU-人工智能-实验1-A*算法

人工智能-实验1

计科210x 甘晴void
在这里插入图片描述

一、实验目的

  • 掌握有信息搜索策略的算法思想;

  • 能够编程实现搜索算法;

  • 应用A*搜索算法求解罗马尼亚问题。

二、实验平台

  • 课程实训平台https://www.educoder.net/shixuns/vgmzcukh/challenges

三、实验内容

3.0 题目要求

罗马尼亚问题:agent在罗马尼亚度假,目前位于 Arad 城市。agent明天有航班从Bucharest 起飞,不能改签退票。

现在你需要寻找到 Bucharest 的最短路径,在右侧编辑器补充void A_star(int goal,node &src,Graph &graph)函数,使用编写的搜索算法代码求解罗马尼亚问题:

在这里插入图片描述

3.1 A*算法原理

A*算法的原理是设计一个代价估计函数:其中 **评估函数F(n)**是从起始节点通过节点n的到达目标节点的最小代价路径的估计值,函数G(n)是从起始节点到n节点的已走过路径的实际代价,函数H(n)是从n节点到目标节点可能的最优路径的估计代价 。

函数 H(n)表明了算法使用的启发信息,它来源于人们对路径规划问题的认识,依赖某种经验估计。根据 F(n)可以计算出当前节点的代价,并可以对下一次能够到达的节点进行评估。

采用每次搜索都找到代价值最小的点再继续往外搜索的过程,一步一步找到最优路径。

3.2 算法实现

根据题目要求,实现A*算法,如下图。
在这里插入图片描述

初始给定出发节点,放入openList队列,然后进行扩展操作

  • 扩展操作:对于所有与该节点相连的节点,计算它们的g()值,然后加上h()值,得到f()值,并加入openList队列。
  • openList队列:该队列本质是一个优先队列,以队列中各元素节点的f()值为键进行排序。给定的程序中使用sort+优先队列来实现,实际上这里直接使用优先队列来进行维护会效率更高,这是可以优化的一个点。
  • 选定操作:在扩展,优先化(也就是题中的sort)之后,选定一个f()值最大的作为下一个访问的节点。
  • 访问节点:任何节点的访问操作只能进行一次,但是扩展操作可以进行多次(这其实也可以理解,再次访问该节点显然比原来访问该节点的代价高)
  • 终止条件:按照上述循环重复执行,直至访问到(不是扩展到)目标终点为止。

基本思路可以用下述伪代码展示

void A_star(int goal, node *src, Graph &graph)
{
    openList.push_back(src);
    sort(openList.begin(), openList.end(), cmp);

    while (!openList.empty())
    {
        node *now = new node;
        now = openList.front();
        openList.erase(openList.begin());
        // 选定操作(从优先的openList中获取第一个元素)
        if (now->name == goal)
        {
            // 终止条件:到达终点,保存退出
            return;
        }
        for (int i = 0; i < 20; i++)
        {
            if (graph.getEdge(now->name, i) != -1 && !visited[i])// 有边且未被访问过
            {
                node *expand = new node(i, now->g + graph.getEdge(now->name, i), h[i], now);
                openList.push_back(expand);
                // 执行扩展操作
            }
        }
        sort(openList.begin(), openList.end(), cmp);
        // 保证按照键优先化
    }
}

3.3 源码&分析

#include <algorithm>
#include <iostream>
#include <memory.h>
#include <stack>
#include <vector>
#define A 0
#define B 1
#define C 2
#define D 3
#define E 4
#define F 5
#define G 6
#define H 7
#define I 8
#define L 9
#define M 10
#define N 11
#define O 12
#define P 13
#define R 14
#define S 15
#define T 16
#define U 17
#define V 18
#define Z 19

using namespace std;

int h[20] =
    {366, 0, 160, 242, 161,
     178, 77, 151, 226, 244,
     241, 234, 380, 98, 193,
     253, 329, 80, 199, 374};

struct node
{
    int g;
    int h;
    int f;
    int name;
    node(int name, int g, int h)
    {
        this->name = name;
        this->g = g;
        this->h = h;
        this->f = g + h;
    };
    bool operator<(const node &a) const
    {
        return f < a.f;
    }
};

class Graph
{
public:
    Graph()
    {
        memset(graph, -1, sizeof(graph));
    }
    int getEdge(int from, int to)
    {
        return graph[from][to];
    }
    void addEdge(int from, int to, int cost)
    {
        if (from >= 20 || from < 0 || to >= 20 || to < 0)
            return;
        graph[from][to] = cost;
    }

    void init()
    {
        addEdge(O, Z, 71);
        addEdge(Z, O, 71);

        addEdge(O, S, 151);
        addEdge(S, O, 151);

        addEdge(Z, A, 75);
        addEdge(A, Z, 75);

        addEdge(A, S, 140);
        addEdge(S, A, 140);

        addEdge(A, T, 118);
        addEdge(T, A, 118);

        addEdge(T, L, 111);
        addEdge(L, T, 111);

        addEdge(L, M, 70);
        addEdge(M, L, 70);

        addEdge(M, D, 75);
        addEdge(D, M, 75);

        addEdge(D, C, 120);
        addEdge(C, D, 120);

        addEdge(C, R, 146);
        addEdge(R, C, 146);

        addEdge(S, R, 80);
        addEdge(R, S, 80);

        addEdge(S, F, 99);
        addEdge(F, S, 99);

        addEdge(F, B, 211);
        addEdge(B, F, 211);

        addEdge(P, C, 138);
        addEdge(C, P, 138);

        addEdge(R, P, 97);
        addEdge(P, R, 97);

        addEdge(P, B, 101);
        addEdge(B, P, 101);

        addEdge(B, G, 90);
        addEdge(G, B, 90);

        addEdge(B, U, 85);
        addEdge(U, B, 85);

        addEdge(U, H, 98);
        addEdge(H, U, 98);

        addEdge(H, E, 86);
        addEdge(E, H, 86);

        addEdge(U, V, 142);
        addEdge(V, U, 142);

        addEdge(I, V, 92);
        addEdge(V, I, 92);

        addEdge(I, N, 87);
        addEdge(N, I, 87);
    }

private:
    int graph[20][20];
};

bool list[20];
vector<node> openList;
bool closeList[20];
stack<int> road;
int parent[20];

void A_star(int goal, node &src, Graph &graph)
{
    openList.push_back(src);
    sort(openList.begin(), openList.end());

    while (!openList.empty())
    {
        /********** Begin **********/
        node now = openList.front();
        if (now.name == goal)
            return;
        openList.erase(openList.begin());
        closeList[now.name] = 1;
        for (int i = 0; i < 20; i++)
        {
            if (graph.getEdge(now.name, i) != -1 && !closeList[i])
            {
                node expand(i, now.g + graph.getEdge(now.name, i), h[i]);
                openList.push_back(expand);
                int flag = true;
                for (unsigned int j = 0; j < openList.size(); j++)
                {
                    if (openList[j].name == expand.name && openList[j].g < expand.g)
                    {
                        flag = false;
                    }
                }
                if (flag == true)
                    parent[i] = now.name;
            }
        }
        sort(openList.begin(), openList.end());
        /********** End **********/
    }
}

void print_result(Graph &graph)
{
    int p = openList[0].name;
    int lastNodeNum;
    road.push(p);
    while (parent[p] != -1)
    {
        road.push(parent[p]);
        p = parent[p];
    }
    lastNodeNum = road.top();
    int cost = 0;
    cout << "solution: ";
    while (!road.empty())
    {
        cout << road.top() << "-> ";
        if (road.top() != lastNodeNum)
        {
            cost += graph.getEdge(lastNodeNum, road.top());
            lastNodeNum = road.top();
        }
        road.pop();
    }
    cout << "end" << endl;
    cout << "cost:" << cost;
}

int main()
{
    Graph graph;
    graph.init();
    for (int i = 0; i < 20; i++)
        parent[i] = -1;
    node src(0, 0, h[0]);
    A_star(1, src, graph);
    print_result(graph);
}

具体分析如下:

  • 结构体定义:结构体 node 定义了节点的属性,包括节点名称 name,从起点到该节点的路径长度 g,该节点到目标节点的估计距离 h,以及综合路径长度和估计距离的总代价 f。重载了小于操作符,以便在优先队列中进行排序。
  • 图的表示:使用二维数组 graph[20][20] 表示图,数组大小为 20x20,即有 20 个节点。数组中存储了节点之间的边的权值。
  • 初始化图:在 Graph 类的 init() 方法中,添加了节点之间的边及对应的权值。
  • A*搜索算法A_star 函数实现了A*搜索算法。它通过优先队列 openList 来管理待扩展的节点,并且利用数组 closeList 来记录已经访问过的节点。parent 数组用于记录每个节点的父节点,方便后续回溯路径。算法首先将起始节点加入到 openList 中,并进行排序。然后,循环进行以下步骤:
    • 取出 openList 中的首节点 now,如果该节点是目标节点,则搜索结束。
    • now 加入到 closeList 中,表示已经访问过。
    • 遍历与当前节点相邻的节点,如果相邻节点未被访问过,则将其加入到 openList 中,并更新其父节点为当前节点,并根据当前节点到该相邻节点的路径长度以及该节点到目标节点的估计距离计算总代价 f
    • 最后对 openList 进行排序,以保证优先扩展代价较小的节点。
  • 打印结果print_result 函数用于打印搜索结果,即输出找到的最短路径以及路径的总代价。
    • 将起始节点压入栈中。
    • 从目标节点开始,通过 parent 数组逐步向上回溯,将经过的节点依次压入栈中,直到回溯到起始节点。
    • 在压入栈的过程中,同时计算路径的总代价。因为 A* 算法是一种启发式搜索算法,搜索到的路径并不一定是最优的,但它会在每一步中选择一个启发性最好的节点进行扩展,因此得到的路径一般是较优的。
    • 最后,从栈中依次弹出节点,打印出完整的路径,并输出路径的总代价。
  • 主函数:在主函数中,首先初始化图,然后调用 A_star 函数进行搜索,并最终打印搜索结果。【注意】主函数是在线平台中没有的,在线平台应该指定了程序入口并做了变量初始化的工作。因此我们用主函数来实现这个工作。

综上所述,这段代码实现了使用A*算法在给定图中寻找从起点到目标点的最短路径,并输出了路径以及路径的总代价。

3.4 基于题目的代码:算法分析

时间复杂度可能趋近于O(n^3),主要原因与维护parent数组的最新性有关,这个后面在讲想法的时候会具体说明。

空间复杂度应该有O(n^2),因为使用邻接矩阵来存储图。

3.5 基于题目的代码:困惑思考

基于题目做这道题的时候,我感觉很困惑。

A*算法不是一个非常复杂的算法,但题目中的一些操作让我感觉有点迷。

①parent数组问题

题目使用openList中保存被扩展的节点,然后用closeList来标记被访问的节点,用parent来存储每个节点的父亲这种方式。

这会带来一个挑战:在一个节点被扩展之后,它不一定被立即访问。

如下面这张图,Sibiu节点的四个子节点中,最右子节点Rimnicu Vilcca先被访问,但后来Sibiu的左起第二个子节点Fagaras又被访问到了。此时若该两个子节点都有相同的另一个子节点M,则M可能同时具有Rimnicu Vilcca和Fagaras两个父节点。这样用一个parent数组显然是没办法表示的(注意这里不能是覆盖关系,因为这两个子节点都是“被扩展”的状态而不是“已被访问”的状态,真正被访问的节点有可能从它们之一产生)

在这里插入图片描述

比如,出现下图所示情况。若Bucharest不是最终节点,则它同时又两个parent,这显然无法用一个parent数组存下。

在这里插入图片描述

因此理想的方法是将parent作为一个属性写入该节点的node结构体中去。但这样还没解决一个问题,输出结果会有点烦。

或者使用后面改进的方法,直接用指针来作为属性。这样只需要指针走一遍,就可以把顺序给呈现出来了。

但是,原题中我也想办法解决了,其实观察或者是从题目中可以发现,一个节点如果发现有比它g()值更小的节点,实际上g()值较大的那个显然就没有用了,即使予以保留,最终也会在较低优先级而不会被调用(这个实际上看的是f()值,事实上是一样的,因为f()=g()+h(),h()显然只与节点有关系)。故我直接略去g()值较大的节点,默认它们直接被淘汰掉了,parent数组只保存g()值最小的那个所对应的。

②vector+sort替代priority_queue

题目中使用了vector来保存访问节点的结构体,再加上sort来保证优先级,其实可以直接用priority_queue来实现,效率更高。

3.6 改进代码-结构体

只展示核心代码,略去重复的宏定义部分和graph类。

使用结构体和指针绕开了parent数组的问题

#include <algorithm>
#include <iostream>
#include <memory.h>
#include <stack>
#include <vector>
#define A-Z (略)

using namespace std;

int h[20] =
    {366, 0, 160, 242, 161,
     178, 77, 151, 226, 244,
     241, 234, 380, 98, 193,
     253, 329, 80, 199, 374};

struct node
{
    int g;
    int h;
    int f;
    int name;
    node *parent;
    node() {}
    node(int name, int g, int h, node *parent)
    {
        this->name = name;
        this->g = g;
        this->h = h;
        this->f = g + h;
        this->parent = parent;
    };
    bool operator<(const node a) const
    {
        return f < a.f;
    }
};

class Graph //(略)

vector<node *> openList;
node *des;
bool visited[20];

bool cmp(node *a, node *b) { return a->f < b->f; }
void A_star(int goal, node *src, Graph &graph)
{
    openList.push_back(src);
    sort(openList.begin(), openList.end(), cmp);

    while (!openList.empty())
    {
        node *now = new node;
        now = openList.front();
        openList.erase(openList.begin());
        visited[now->name] = 1;
        // cout << now->name << endl;
        // system("pause");
        if (now->name == goal)
        {
            des = now;
            return;
        }
        for (int i = 0; i < 20; i++)
        {
            if (graph.getEdge(now->name, i) != -1 && !visited[i])
            {
                node *expand = new node(i, now->g + graph.getEdge(now->name, i), h[i], now);
                openList.push_back(expand);
                // cout << "expand: " << expand->name << endl;
            }
        }
        sort(openList.begin(), openList.end(), cmp);
    }
}

void print_result(Graph &graph)
{
    cout << "solution: ";
    stack<int> ans;
    node *now = des;
    while (now != NULL)
    {
        ans.push(now->name);
        // cout << now->name << endl;
        now = now->parent;
    }
    while (!ans.empty())
    {
        cout << ans.top() << "-> ";
        ans.pop();
    }
    cout << "end" << endl;
    cout << "cost:" << des->g << endl;
}

int main()
{
    Graph graph;
    graph.init();
    memset(visited, 0, sizeof(visited));
    node *src = new node(0, 0, h[0], NULL);
    A_star(1, src, graph);
    print_result(graph);
}

3.7 改进代码2-<priority_queue>

只需要在上面代码的基础上更改部分即可,但考虑到这里使用的其实是结构体的指针,故重载运算符其实不太好操作。我采取使用比较函数的方法来完成大小的判断。

bool cmp(node *a, node *b) { return a->f > b->f; }
priority_queue<node *, vector<node *>, decltype(&cmp)> openList(&cmp);

void A_star(int goal, node *src, Graph &graph)
{
    openList.push(src);

    while (!openList.empty())
    {
        node *now = new node;
        now = openList.top();
        openList.pop();
        visited[now->name] = 1;
        // cout << now->name << endl;
        // system("pause");
        if (now->name == goal)
        {
            des = now;
            return;
        }
        for (int i = 0; i < 20; i++)
        {
            if (graph.getEdge(now->name, i) != -1 && !visited[i])
            {
                node *expand = new node(i, now->g + graph.getEdge(now->name, i), h[i], now);
                openList.push(expand);
                // cout << "expand: " << expand->name << endl;
            }
        }
    }
}

3.8 运行截图

在这里插入图片描述

四、思考题

1:宽度优先搜索,深度优先搜索,一致代价搜索,迭代加深的深度优先搜索算法哪种方法最优?

首先分析这四种算法,

  • 宽度优先搜索(BFS):通常在最短路径问题上表现优异,但是空间复杂度很高,因为需要保存所有已经访问的节点。
  • 深度优先搜索(DFS):解空间较大,在解相对较浅的问题上可能更有效率,但是可能会陷入无限深度的分支。
  • 迭代加深深度优先搜索(IDDFS):结合了DFS和BFS的优点,在不断增加的深度限制上调用深度受限搜索。对于深度搜索问题而言,是一种比较有效的方法。
  • 一致代价搜索(UCS):保证在图中搜索的每一步都是最小代价的算法,通常在无启发式的情况下用于解决最短路径问题。

对于一般的问题而言,一致代价搜索是更优的。但对于不同问题要具体问题具体分析,如问题的时间或空间限制等。

2:贪婪最佳优先搜索和A*搜索哪种方法最优?

首先分析这两种算法,

  • 贪婪最佳优先搜索:根据启发式函数h()所提供的信息,每次选择看起来最有希望的节点进行扩展,但是它不能保证找到最优解,因为它没有考虑到节点到目标的真实代价。
  • A*搜索算法:通过综合考虑节点的实际代价g()和启发式函数h()的估计值,保证了在每一步都能选择到最优的节点进行扩展,从而保证找到最优解。

A*搜索算法通常在需要找到最优解的问题上更为优秀,因为它考虑了实际代价。

3:分析比较无信息搜索策略和有信息搜索策略。

无信息搜索策略和有信息搜索策略是指搜索算法是否利用额外的信息来指导搜索方向:

  • 无信息搜索策略,如深度优先搜索(DFS)、宽度优先搜索(BFS)和一致代价搜索(UCS),只利用当前节点的信息进行搜索,不考虑节点到目标的距离或代价,因此可能需要更多的搜索步骤来找到解。
  • 有信息搜索策略,如A*搜索算法和贪婪最佳优先搜索,利用启发式函数提供的额外信息(如节点到目标的估计距离)来指导搜索方向,从而更快地找到解。有信息搜索策略通常能更快地找到最优解,但是需要在空间和时间上付出更多的代价来计算和存储启发式函数的值。

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

高扬程水泵助力森林消防,守护绿色生命线/恒峰智慧科技

随着人类社会的不断发展&#xff0c;森林资源的保护和管理变得越来越重要。然而&#xff0c;森林火灾却时常威胁着这一宝贵资源。为了有效应对森林火灾&#xff0c;提高灭火效率&#xff0c;高扬程水泵在森林消防中发挥了重要作用。本文将重点介绍高扬程水泵在森林消防中的应用…

AI终端设备的自动化分级

摘要&#xff1a; AI智体被定义为感知环境、做出决策和采取行动的人工实体。 受SAE&#xff08;汽车工程师学会&#xff09;自动驾驶6个级别的启发&#xff0c;AI智体也根据效用和强度进行分类&#xff0c;分为以下几个级别&#xff1a; L0——无AI&#xff0c;有工具&#xf…

机器学习中线性回归算法的推导过程

线性回归是机器学习中监督学习中最基础也是最常用的一种算法。 背景&#xff1a;当我们拿到一堆数据。这堆数据里有参数&#xff0c;有标签。我们将这些数据在坐标系中标出。我们会考虑这些数据是否具有线性关系。简单来说 我们是否可以使用一条线或者一个平面去拟合这些数据的…

力扣每日一题111:二叉树的最小深度

题目 简单 给定一个二叉树&#xff0c;找出其最小深度。 最小深度是从根节点到最近叶子节点的最短路径上的节点数量。 说明&#xff1a;叶子节点是指没有子节点的节点。 示例 1&#xff1a; 输入&#xff1a;root [3,9,20,null,null,15,7] 输出&#xff1a;2示例 2&#x…

SpringCloud生态体系介绍

Spring Cloud是一系列框架的有序集合。它利用Spring Boot的开发便利性巧妙地简化了分布式系统基础设施的开发&#xff0c;如服务发现注册、配置中心、智能路由、消息总线、负载均衡、断路器、数据监控等&#xff0c;都可以用Spring Boot的开发风格做到一键启动和部署。 必要说…

【面试经典 150 | 图】除法求值

文章目录 写在前面Tag题目来源解题思路方法一&#xff1a;广度优先搜索 写在最后 写在前面 本专栏专注于分析与讲解【面试经典150】算法&#xff0c;两到三天更新一篇文章&#xff0c;欢迎催更…… 专栏内容以分析题目为主&#xff0c;并附带一些对于本题涉及到的数据结构等内容…

2024年第二十六届“华东杯”(B题)大学生数学建模挑战赛|数学建模完整代码+建模过程全解全析

当大家面临着复杂的数学建模问题时&#xff0c;你是否曾经感到茫然无措&#xff1f;作为2022年美国大学生数学建模比赛的O奖得主&#xff0c;我为大家提供了一套优秀的解题思路&#xff0c;让你轻松应对各种难题。 让我们来看看华东杯 (B题&#xff09;&#xff01; 第一个问题…

CI/CD笔记.Gitlab系列.新用户管理

CI/CD笔记.Gitlab系列 新用户管理 - 文章信息 - Author: 李俊才 (jcLee95) Visit me at CSDN: https://jclee95.blog.csdn.netMy WebSite&#xff1a;http://thispage.tech/Email: 291148484163.com. Shenzhen ChinaAddress of this article:https://blog.csdn.net/qq_285502…

使用CNN或resnet,分别在flower5,flower17,flower102数据集上实现花朵识别分类-附源码-免费

前言 使用cnn和resnet实现了对flower5&#xff0c;flower17&#xff0c;flower102数据集上实现花朵识别分类。也就是6份代码&#xff0c;全部在Gitee仓库里&#xff0c;记得点个start支持谢谢。 本文给出flower17在cnn网络实现&#xff0c;flower102在resnet网络实现的代码。…

正则表达式-前瞻和后顾

正则表达式中的前瞻和后顾。 前瞻(Lookahead) 前瞻是一种断言,它会检查在当前位置之后是否存在某种模式,但不会实际匹配该模式。前瞻有两种形式: 正向前瞻 (?pattern) 检查当前位置之后是否存在指定的模式如果存在,则匹配成功,但不会消耗该模式例如 \w(?\d) 将匹配后面跟数…

Mysql 8.0.33 迁移至 Postgresql 16.2

小伙伴们&#xff0c;你们好&#xff0c;我是老寇&#xff0c;我又回来&#xff0c;几个月不见&#xff0c;甚是想念啊&#xff01;&#xff01;&#xff01;&#xff01; 这不&#xff0c;云平台需要改造&#xff0c;将Mysql替换成Postgresql&#xff0c;话说回来&#xff0c…

步态识别论文(6)GaitDAN: Cross-view Gait Recognition via Adversarial Domain Adaptation

摘要: 视角变化导致步态外观存在显着差异。因此&#xff0c;识别跨视图场景中的步态是非常具有挑战性的。最近的方法要么在进行识别之前将步态从原始视图转换为目标视图&#xff0c;要么通过蛮力学习或解耦学习提取与相机视图无关的步态特征。然而&#xff0c;这些方法有许多约…

【管理篇】如何处理团队里的老资格员工和高能力员工?

目录标题 两类员工对比&#x1f93a;老资格员工高能力员工 作为领导你应该怎么做&#xff1f; 在管理团队时&#xff0c;处理老资格员工和高能力员工是一项至关重要的任务。这两类员工在团队中扮演着不同的角色和有着不同的需求&#xff0c;因此需要针对性的管理和激励。下面将…

程序设计——前后端分离实现简单表白墙

文章目录 一、前端页面样式代码二、前后端衔接1. 后端创建 maven 项目2. 针对前后端交互的解释以及后端代码的实现针对 post 请求解释前后端衔接针对 Get 请求解释前后端衔接 3.后端与数据库的联系以及对数据的存取单独封装数据库连接代码解释后端存储 save 数据的代码解释后端…

神经网络中的算法优化(皮毛讲解)

抛砖引玉 在深度学习中&#xff0c;优化算法是训练神经网络时至关重要的一部分。 优化算法的目标是最小化&#xff08;或最大化&#xff09;一个损失函数&#xff0c;通常通过调整神经网络的参数来实现。 这个过程可以通过梯度下降法来完成&#xff0c;其中梯度指的是损失函数…

【Unity】位图字体制作工具:蒲公英

一般来讲&#xff0c;如果需要制作位图字体&#xff0c;一般是使用 BMFont 这种第三方工具&#xff1a;BMFont - AngelCode.comhttp://www.angelcode.com/products/bmfont/ 然而这个工具对于非程序员来说&#xff0c;操作起来较为繁琐困难。每次美术修改了字体之后&…

【短剧在线表格搜索-附模板】

短剧在线表格搜索-附模板 介绍电脑界面手机界面送附加功能&#xff1a;反馈缺失短剧送&#xff1a;资源更新源头获取 介绍 你好&#xff01; 这是你第一次使用 金山在线文档 所生成的短剧搜索表格&#xff0c;支持批量导入自己转存的短剧名字和链接&#xff0c;实现在线搜索&a…

【AI】openai-quickstart 运行Jupyter Lab

openai-quickstart/openai_api /README-CN.md 【AI】指定python3.10安装Jupyter Lab 可以安装3.10版本的jupyter lab 但是直接输入命令无法启动 突然发现自己电脑2023年安装过anaconda3 C:\ProgramData\anaconda3\python.exe C:\ProgramData\anaconda3\cwp.py C:\ProgramData…

一款开源的原神工具箱,专为现代化 Windows 平台设计,旨在改善桌面端玩家的游戏体验

Snap.Hutao 胡桃工具箱是一款以 MIT 协议开源的原神工具箱&#xff0c;专为现代化 Windows 平台设计&#xff0c;旨在改善桌面端玩家的游戏体验。通过将既有的官方资源与开发团队设计的全新功能相结合&#xff0c;提供了一套完整且实用的工具集&#xff0c;且无需依赖任何移动设…

WordPress MasterStudy LMS插件 SQL注入漏洞复现(CVE-2024-1512)

0x01 产品简介 WordPress和WordPress plugin都是WordPress基金会的产品。WordPress是一套使用PHP语言开发的博客平台。该平台支持在PHP和MySQL的服务器上架设个人博客网站。WordPress plugin是一个应用插件。 0x02 漏洞概述 WordPress Plugin MasterStudy LMS 3.2.5 版本及之…