图的关键路径(含多支交叉路径分离输出)

news2024/12/24 11:31:24

文章目录

    • 关键路径的理解
    • 关键路径求解的图解与分析
    • 关键路径查找的代码实现
    • 多支交叉路径的分离输出
    • 总结

此文代码均可在Windows与Linux操作系统下的常用编译器上运行,例如:vs、vscode、Dev-C++等等。

关键路径的理解

图的关键路径一般是在求从一个顶点到另一个顶点的最长路径,这个是建立在图的拓扑序列(传送门)之上进行的。所谓的关键路径,就是同时从一个顶点出发,无论其他路径走的怎么样,最后都得等待关键路径走完,才能到达最后的顶点。也就整个图的所有顶点访问完所需要的时间只由关键路径决定,这也是它为什么叫关键路径的原因。

就比如:

image-20221201162546280

明明走1->3->4->5更快一点,但是要想访问顶点4,得先把顶点2和顶点3访问完(拓扑序列决定的)。因此虽然另一条路很快也很急,但是先别急,急也没用,等着1->2->4进行完了之后才能一起往下走4->5。为了方便理解,我们后面还是将顶点抽象为事件,弧抽象为活动,这样更好理解一点。

关键路径求解的图解与分析

源点:整个图开始的事件

汇点:整个图结束的事件

关键路径:路径耗时最长的路径就是关键路径。

现在要求关键路径怎么求呢?首先我们先要求解事件最早时间与最晚时间,然后再求活动的最早和最晚时间。至于原因嘛,这里我先提几句:关键路径耗时最长,轮不到它等其它路径,意味着它的每项活动与事件都没有空余的时间来耽误,所以最早时间与最晚时间肯定会相同啦。详细解说以及细节的求解过程都在下面:

首先,每个事件最早发生的时间初始化为0,意味着现在所有的事件都可能一下子从源点走到各个点(这种极端情况是会存在的,并且源点到源点的时间就是0,因此在初始化的阶段直接处理掉以上情况)。
然后从源点开始计算每个事件的最早时间。
事件最早时间:从源点事件到某个事件V每一步都按规划时间走所需要的最短时间。由于事件V的发生需要之前所有从源点到V的路径上的所有事件都要发生(每条路径的事件都为V事件的发生提供基础,这是拓扑排序的特点的另一体现。拓扑序列就是入度为0的事件才能发生,意味着之前的事件都必须发生它才能发生!)因此,最短的时间反而是源点事件到V事件的所有路径中的所需要的最长时间。(有点类似于集体活动的出发的最早时间不是最早到的人决定的,而是最晚到的人决定的,最晚的人来的早了,集体活动出发的时间就早了。)

这也是从源点事件开始计算最早时间的原因了。

从源点开始,检测V事件的所有邻接点事件的最早时间是否大于V事件的最早时间+活动所需要的时间(权值),如果是的话,就说明临界点事件的最早时间被V事件给推迟了(事件V之前的事件与活动都可能有责任嘞,但是并不用关心责任哥是谁,反正是推迟了,找到是谁也没有意义),因此得更新一下此时该临界点的最早时间。

假如有n个事件,那么就进行n次更新,将第i个事件(1≤i≤n)的邻接点的最早时间都进行一次判断并决定是否更新(更新的顺序走的是拓扑排序的路径,原因还是为了保持入度为0的事件才能发生的条件。)

image-20221201155846967

事件最晚时间:从源点到某个事件V所能拖延的最长时间。为什么这么理解呢?假设最终汇点的事件被完成了,从源点出发有那么多路径,其中肯定有一条路径是花费时间最长的那个,也就是最终这一套事件结束所需要的时间。那么除了这条路径,其他的路径如果是按照规定的时间走的话,肯定要早到,等着那条时间最长的关键路径,这就给其他路径传递了一个信息:你不用每个事件都走那么快,中间浪费拖延一点时间也不影响,但是也不能浪费太多时间,否则的话就会使得其时间超过了关键路径的时间,最终导致整套事件得完成时间变长了。这就是过分拖延影响其他路径了。因此拖延的时间得有个度,那么肯定有个最大极限,使得非关键路径的时间总和变为关键路径的时间总和。这就意味着,在一条非关键路径中,假设有一个事件V,从V到汇点事件都按规划时间走,不拖延时间,然后从源点事件到V事件最多也就能拖延到: 最长时间(关键路径的时间) 减去 V事件到汇点事件的规划时间 。因为V事件后面都按规划时间走了,那么此时计算出来的时间差值不就是V事件被完成的最晚的时间吗?(并不关心源点事件到V事件到底是谁在拖延时间,反正到V事件时已经是最后的时间期限了。)

image-20221201161549296

由于关键路径就是每一步都按规划时间走还需要最长时间,也就意味着每个事件的最早时间也是按规划时间走的结果,每个事件的最晚时间也是按规划时间走的结果,那么就意味着最早时间和最晚时间是一样的,此时关键路径的特点不就出来了吗?–>每个事件的最早时间与最晚时间相等。

上面关键事件的顺序只是刚好与路径的一样,假如事件映射的下标稍微变一下就会出现顺序完全紊乱的情况,例如:

image-20221201204820104

因此,我们要找关键路径,除了要知道各个事件,一般都要知道事件之间的先后顺序,而由事件找它们在图中的顺序是不好找的,由弧找顶点与它们在图中的顺序更方便一点,顺便还能把弧的权值给整出来。所以这里我们就从找关键事件又转换到了找关键活动的求解,不过不用担心,这个过程还是比较容易完成的。

活动的最早开始时间:在事件A与事件B之间,有一个活动<A,B>,活动<A,B>最早的开始时间是由A决定的,只有A事件完成了,活动<A,B>才能开始,因此活动<A,B>的最早开始时间是事件A的最早时间。

image-20221201164950322

活动的最晚开始时间:活动<A,B>的最晚时间不是由事件A来控制的,而是由事件B来控制的,因为活动<A,B>的完成时间就是是事件B的时间,也就意味着事件B的最晚时间要想不超时就得规定活动<A,B>的开始时间,这就是活动<A,B>的最晚时间。

image-20221201165012603

此时再次理解关键路径,就会发现关键路径上的所有活动也都是按照规定的时间走的,那么路径上的每个活动的最早时间与最晚时间肯定相等,这个时候也能通过活动的最早时间与最晚时间来判定关键路径。

image-20221201172850410

关键路径查找的代码实现

实现之前老规矩,还是将图的一些基本结构与功能给附加上。

#include <iostream>
#include <vector>
#include <map>
#include <stack>
#include <string>
#include <cstring>
using namespace std;
template <class W>
struct Edge //边(弧)的结构体
{
    size_t _srci; //起始顶点下标
    size_t _dsti; //终端顶点下标
    W _w;         //边的权值
    Edge(const size_t srci, const size_t dsti, const W &w)
        : _srci(srci), _dsti(dsti), _w(w)
        {}
    bool operator>(const Edge &e) const //两条边的比较(权值的大小比较)
    {
        return _w > e._w;
    }
};
   //V:顶点的数据类型。 W:权值类型。 权值的最大值默认为INT32_MAX,即∞    默认为无向图
template <class V, class W, W MAX_W = INT32_MAX, bool Direction = false>
class Graph//图的类框架
{
public:
    typedef Graph<V, W, MAX_W, Direction> Self;
    typedef struct Edge<W> Edge;
    Graph() = default;//默认构造
    //初始化图
    Graph(const V *vertexs, int n)//顶点数组与个数传参
    {
        _vertexs.reserve(n);//初始化顶点数组与下标集
        for (int i = 0; i < n; ++i)
        {
            _vertexs.push_back(vertexs[i]);
            _indexMap[vertexs[i]] = i;
        }
        _matrix.resize(n);
        for (int i = 0; i < n; ++i)
        {
            _matrix[i].resize(n, MAX_W);//初始化邻接矩阵,每个元素默认赋值为∞
            _matrix[i][i]=W();//对角线上的权值为0
        }
    }
    size_t GetVertexIndex(const V& x)//获得顶点在顶点数组中的下标
    {
        auto it = _indexMap.find(x);
        if (it != _indexMap.end())//找到的话返回下标
        {
            return it->second;
        }
        else
        {
            throw invalid_argument("顶点不存在");//找不到的话就抛异常
            return -1;
        }
    }
    //添加关系(边)
    void _AddEdge(const size_t srci, const size_t dsti, const W& wight)
    {
        _matrix[srci][dsti] = wight;//两个顶点之间的权值进行赋值
        if (Direction == false)     //是无向图的话就在矩阵的对应位置也对边的权值进行赋值
        {
            _matrix[dsti][srci] = wight;
        }
    }
    //       参数:  起始顶点     终端顶点          权值
    void AddEdge(const V& src, const V& dst, const W& wight)
    {
        int srci = GetVertexIndex(src);//拿到起始顶点映射的下标
        int dsti = GetVertexIndex(dst);//拿到终端顶点映射的下标
        _AddEdge(srci, dsti, wight);   //嵌套一下用下标的方式添加边,方便在之后处理矩阵的特定情境通过下标加边。
    }
    //找所有顶点的入度
    void FindInDegree(vector<int> &indegree) // indegree存的是所有顶点的入度
    {
        size_t n = _vertexs.size();    // n为顶点的个数
        indegree.resize(n, 0);         //入度初始化为0
        for (size_t i = 0; i < n; ++i) //对n个顶点进行各自的入度累计
        {
            for (size_t j = 0; j < n; ++j)
            {
                if (_matrix[i][j] != MAX_W && _matrix[i][j] != W()) //有顶点i指向顶点j,顶点j的入度就自加1。
                {
                    indegree[j]++;
                }
            }
        }
    }

    //拓扑排序         输出型参数 拓扑序列放在topo中
    void TopologicalSort(vector<int> &topo)
    {
        size_t n = _vertexs.size();     // n为顶点的个数
        topo.resize(n, 0);              //给topo开辟n个空间
        int index = 0;                  //拓扑序列的实时下标,初始为0
        stack<int> st;                  //存放入度为零的顶点
        vector<int> indegree;           //所有顶点的初始入度
        vector<bool> visited(n, false); //标记映射的顶点是否被访问过
        FindInDegree(indegree);         //查找所有顶点的入度
        for (size_t i = 0; i < n; ++i)  // n个节点n次循环
        {
            for (size_t j = 0; j < n; ++j) //找未被访问且入度为零的顶点,将本次循环所有入度为0的顶点入栈
            {
                if (visited[j] == false && indegree[j] == 0)
                {
                    st.push(j);
                    visited[j] = true; //只要进栈,就意味着一定会被访问,直接先标记为已访问,避免重复检查
                }
            }
            int v = st.top();              //取栈顶的入度为0的顶点
            topo[index] = v;               //将该顶点存在拓扑序列中
            st.pop();                      //将该顶点出栈
            for (size_t j = 0; j < n; ++j) //已访问的顶点的所有未被访问的
            {
                if (visited[j] == false && _matrix[v][j] != MAX_W && _matrix[v][j] != W())
                {
                    indegree[j]--; //更新已访问的顶点的邻接点的入度
                }
            }
            index++; //更新拓扑序列的实时下标
        }
    }
private:
    vector<V> _vertexs;        //顶点
    map<V, int> _indexMap;     //顶点对应的下标
    vector<vector<W>> _matrix; //矩阵
};

接下来才是真正的关键路径查找

//关键路径的查找    cPath用来存放关键活动(弧)  返回值是关键路径的权值
W CriticalPathFind(vector<Edge> &cPath)
{
    size_t n = _vertexs.size();
    vector<int> topo;      //存放拓扑序列
    TopologicalSort(topo); //拓扑排序
    vector<int> etv, ltv;  // etv存放每个事件的最早时间,ltv存放每个事件的最晚时间

    //事件的最早时间求解
    etv.resize(n, 0); //每个事件的最早时间初始化为0
    for (size_t k = 0; k < n; ++k)
    {
        int i = topo[k];               //拓扑序列的第k个数据所指向的顶点i
        for (size_t j = 0; j < n; ++j) //遍历顶点i的邻接点j  i-->j
        {
            if (_matrix[i][j] != MAX_W && _matrix[i][j] != W())
            {
                if (etv[j] < etv[i] + _matrix[i][j]) //事件j的最早时间比(事件i的最早时间 + i->j的活动事件)小
                {
                    etv[j] = etv[i] + _matrix[i][j]; //更新事件j的最早时间
                }
            }
        }
    }

    //事件的最晚时间求解
    ltv.resize(n, etv[topo[n - 1]]); //每个事件的最晚时间初始化为最后一个事件的最早时间
    for (int k = n - 1; k >= 0; --k)
    {
        int i = topo[k];               //事件i
        for (size_t j = 0; j < n; ++j) //找到事件i的邻接点j  i-->j
        {
            if (_matrix[i][j] != MAX_W && _matrix[i][j] != W())
            {
                if (ltv[i] + _matrix[i][j] > ltv[j]) //(事件i的最晚时间 + i->j的活动事件)大于事件j的最晚时间,说明事件i要提早一点
                {
                    ltv[i] = ltv[j] - _matrix[i][j]; //更新事件i的最晚时间。
                }
            }
        }
    }

    //活动的最早与最晚开始时间求解,并找到关键路径。
    int ete, lte;
    for (size_t i = 0; i < n; ++i) //对n个事件各自所关联的活动进行检测
    {
        for (size_t j = 0; j < n; ++j) //找事件i的邻接事件j之间的活动
        {
            if (_matrix[i][j] != MAX_W && _matrix[i][j] != W())
            {
                ete = etv[i];                 //活动的最早开始时间为事件i的最早时间
                lte = ltv[j] - _matrix[i][j]; //活动的最晚开始时间为事件j的最晚时间减去活动本身的时间
                if (ete == lte)               //活动的最早开始时间与最晚开始时间相等,那么就表示找到了关键活动
                {
                    //由于是依次遍历二维矩阵,所以活动的顺序极有可能是混乱的,输出关键路径的话要做专门的处理,这里就先保存起来
                    cPath.push_back(Edge(i, j, _matrix[i][j]));
                }
            }
        }
    }
    return ltv[topo[n - 1]]; //拓扑排序的最后一个事件的最晚时间就是关键路径的时间
}

多支交叉路径的分离输出

上面的代码中提到了关键活动的先存起来而不输出的问题,这是由于关键活动的遍历顺序极有可能不是关键路径的顺序,并且还有可能出现多个关键路径以及路径分叉与合叉的情况。说实话,将这些路径剥离开并单独输出来是有一点复杂的,但是可以做一些尝试去梳理。具体的思路就是先根据关键活动建立一个新的图,我称之为关键路径图。它的特点就是所有的路径都是关键路径,在此基础之上再进行深度优先遍历(传送门),由于每个路径都要单独输出,因此在递归语句之后还要判断一个事件是否还有剩余的邻接事件,有的话,一旦当前事件递归结束,就先输出当前事件之前访问的事件,再进行深度遍历输出,那么这条新的路径递归到最后给人的感觉就又是一条单独的路径了。

文字描述还是有点复杂难懂,我们先看代码,再结合流程图会更好理解上面话的意思。

//参数:CPG:关键路径所构成的图(关键图)  cPath:关键路径上活动(弧)的集合 visitedP:已访问过的路径  
void CriticalPathPrint(Self &CPG, vector<Edge> &cPath, vector<int> &visitedP)
{
    size_t n = _vertexs.size();
    CPG._vertexs = _vertexs; //首先把原图的所有顶点拷贝过来,关键路径不一定会用到所有的顶点,但是没关系,空间稍稍浪费一点也没问题(待优化)
    CPG._indexMap = _indexMap;
    CPG._matrix.resize(n); //将关键图的矩阵先初始化
    for (size_t i = 0; i < n; ++i)
    {
        CPG._matrix[i].resize(n, MAX_W);
        CPG._matrix[i][i] = W();
    }
    for (auto &e : cPath) //将各个关键边放进关键图中,造图
    {
        CPG._AddEdge(e._srci, e._dsti, e._w);
    }
    //寻找最开始的活动
    size_t srci;
    for (size_t i = 0; i < cPath.size(); ++i)
    {
        int flag = 1;
        for (size_t j = 0; j < cPath.size(); ++j) //只要活动i满足不是其他活动j之后,就说明活动i是初始活动
        {
            if (cPath[i]._srci == cPath[j]._dsti) //活动i的起始事件不能是活动j的结束事件,否则的话就说明i不是初始活动
            {
                flag = 0;
                break;
            }
        }
        if (flag)
        {
            srci = cPath[i]._srci; //此时说明走了一个循环所有的关键活动都没指向关键活动i,关键活动i就是
        }
    }
    _CriticalPathPrint(CPG, srci, visitedP); //递归深度遍历
}


//参数:CPG:关键路径所构成的图(关键图)  srci:要访问的事件  visitedP:已访问过的路径 
//传参的时候已经访问过的路径要用传值传参,原因在于递归式的访问岔路时,每次已访问过的路径是与当前事件相关的。
void _CriticalPathPrint(Self &CPG, size_t srci, vector<int> visitedP)
{
    cout << "->" << CPG._vertexs[srci];//访问事件,并且将其归到已访问的路径中去。
    visitedP.push_back(srci);
    for (size_t i = 0; i < CPG._vertexs.size(); ++i)//找该事件的所有邻接事件
    {
        if (CPG._matrix[srci][i] != MAX_W && CPG._matrix[srci][i] != W())//找到了就递归式访问下去。
        {
            _CriticalPathPrint(CPG, i, visitedP);
            //运行到这里说明其中有一条路径已经被访问到底了,那么就要检测此时的事件除了已经访问过的邻接事件是否还有没访问过的。
            for (size_t j = i + 1; j < CPG._vertexs.size(); ++j)
            {
                if (CPG._matrix[srci][j] != MAX_W && CPG._matrix[srci][j] != W())
                {
                    cout << " | ";//与上一条路径分隔开
                    for (size_t j = 0; j < visitedP.size(); ++j)//将已访问过的事件输出
                    {
                        cout << "->" << CPG._vertexs[visitedP[j]];
                    }
                    break;//输出一条路径后一定要跳出循环,保证只输出一条分叉路经的已访问事件。
                }
            }
        }
    }
}

共使用两个图例测试

先整一个比较特殊的图来说明一下:

image-20221202153347435

image-20221202153529323

测试代码:

image-20221202154024842

void TestCriticalPathFind()
{
    const char* str = "1234567";
    Graph<char, int, INT32_MAX, true> g(str, strlen(str));
    Graph<char, int, INT32_MAX, true> CPG;
    //造图
    g.AddEdge('1', '2', 1);
    g.AddEdge('1', '3', 1);
    g.AddEdge('2', '4', 1);
    g.AddEdge('3', '4', 1);
    g.AddEdge('4', '5', 1);
    g.AddEdge('4', '6', 1);
    g.AddEdge('5', '7', 1);
    g.AddEdge('6', '7', 1);
    
    vector<Edge<int>> cPath;
    int totalW = g.CriticalPathFind(cPath);
    vector<int> visitedP;
    g.CriticalPathPrint(CPG, cPath, visitedP);//关键路径的输出
    cout << " | totalW: " << totalW << endl;
}
int main()
{
	TestCriticalPathFind();
    return 0;
}

测试结果:

image-20221202154152361

与预想中的一致。

🔺再测试一个各个教材比较喜欢举例的图:

image-20221202154340996

测试代码:

void TestCriticalPathFind()
{
    const char* str = "123456789";
    Graph<char, int, INT32_MAX, true> g(str, strlen(str));
    Graph<char, int, INT32_MAX, true> CPG;
	//造图
    g.AddEdge('1', '2', 6);
    g.AddEdge('1', '3', 4);
    g.AddEdge('1', '4', 5);
    g.AddEdge('2', '5', 1);
    g.AddEdge('3', '5', 1);
    g.AddEdge('5', '7', 9);
    g.AddEdge('5', '8', 7);
    g.AddEdge('7', '9', 2);
    g.AddEdge('8', '9', 4);
    g.AddEdge('4', '6', 2);
    g.AddEdge('6', '8', 4);

    vector<Edge<int>> cPath;
    int totalW = g.CriticalPathFind(cPath);
    vector<int> visitedP;
    g.CriticalPathPrint(CPG, cPath, visitedP);
    cout << " | totalW: " << totalW << endl;
}
int main()
{
	TestCriticalPathFind();
    return 0;
}

测试结果:

image-20221202154644921

关键路径确实与教材上一致。

总结

关键路径的难点在于理解最早与最晚时间的求解与它们之间的关系,看不懂的话建议自己动手画一画。后面的多支交叉路径的分离输出其实真正考的还是深度遍历的另类扩展应用,也是比较难的,在递归中处理问题特别不好控制,很多时候并不作强制要求,因此实在不懂的话可以直接输出各个活动就可以了。我在写的时候,最开始用的也不是递归写法,只解决了一部分特殊情况,多条路径分叉与合叉的情况太过于复杂也不行。递归输出就怕层数太深,导致栈溢出问题。并且由于是每次都传值传参,有点浪费空间。总的来说,性价比不高,大家如果有什么更好的思路可以在评论区留言哦🐾~

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

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

相关文章

RocketMQ-RocketMQ部署(Linux、docker)

文章目录一、Linux1、单机部署RocketMQ> 前置条件第一步、官网下载 并 上传至服务器第二步、配置jdk环境第三步、修改初始内存第四步、启动 NameServer第五步、启动 Broker第六步、关闭RocketMQDemo&#xff1a;发送与接收消息测试 (Linux端)2、部署可视化管理工具—rocketm…

tictoc 例子理解 13-15

tictoc13-tictoc13 子类化cMessage生成消息&#xff0c;随机目标地址tictoc 14 在13的基础上增加两变量显示于仿真界面tictoc 15 模型数据输出为直方图tictoc13 子类化cMessage生成消息&#xff0c;随机目标地址 在这一步中&#xff0c;目标地址不再是节点2——我们绘制了一个…

[附源码]计算机毕业设计springboot现代诗歌交流平台

项目运行 环境配置&#xff1a; Jdk1.8 Tomcat7.0 Mysql HBuilderX&#xff08;Webstorm也行&#xff09; Eclispe&#xff08;IntelliJ IDEA,Eclispe,MyEclispe,Sts都支持&#xff09;。 项目技术&#xff1a; SSM mybatis Maven Vue 等等组成&#xff0c;B/S模式 M…

MySQL存储过程

目录 存储过程 1、存储过程的概念 2、存储过程的优点 3、创建存储过程 格式&#xff1a; 4、调用存储过程 格式 5、查看存储过程 格式&#xff1a; 6、存储过程的参数 7、删除存储过程 格式&#xff1a; 8、存储过程的控制语句 准备a表 &#xff08;1&#xff09;条…

Spring基础篇:注入

第一章&#xff1a;注入 一&#xff1a;什么是注入 &#xff08;Injection&#xff09;注入就是通过Spring的工厂类和spring的配置文件&#xff0c;对spring所创建的对象进行赋值&#xff0c;为成员变量进行赋值 二&#xff1a;为什么注入 为什么需要Spring工厂创建对象的时…

[附源码]Python计算机毕业设计SSM开放式在线课程教学与辅助平台(程序+LW)

项目运行 环境配置&#xff1a; Jdk1.8 Tomcat7.0 Mysql HBuilderX&#xff08;Webstorm也行&#xff09; Eclispe&#xff08;IntelliJ IDEA,Eclispe,MyEclispe,Sts都支持&#xff09;。 项目技术&#xff1a; SSM mybatis Maven Vue 等等组成&#xff0c;B/S模式 M…

[附源码]计算机毕业设计JAVA校园闲置物品租赁系统

[附源码]计算机毕业设计JAVA校园闲置物品租赁系统 项目运行 环境配置&#xff1a; Jdk1.8 Tomcat7.0 Mysql HBuilderX&#xff08;Webstorm也行&#xff09; Eclispe&#xff08;IntelliJ IDEA,Eclispe,MyEclispe,Sts都支持&#xff09;。 项目技术&#xff1a; SSM my…

Maven使用指南(超详细)

Maven高级 目标 理解并实现分模块开发能够使用聚合工程快速构建项目能够使用继承简化项目配置能够根据需求配置生成、开发、测试环境&#xff0c;并在各个环境间切换运行了解Maven的私服 1&#xff0c;分模块开发 1.1 分模块开发设计 (1)按照功能拆分 我们现在的项目都是在…

Delay Penalty for RNN-T and CTC

1. 背景 之前介绍了如何在 RNN-T 流式模型上应用时延正则&#xff0c;以及在 Conformer 和 LSTM 上的实验结果。 本期公众号重点带大家回顾下具体的思路&#xff0c;以及如何类似地在 CTC 流式模型上应用时延正则。 有些内容可能有所重复&#xff0c;读者可适当跳过。2. Dela…

iwebsec靶场 SQL注入漏洞通关笔记12-等价函数替换绕过

系列文章目录 iwebsec靶场 SQL注入漏洞通关笔记1- 数字型注入_mooyuan的博客-CSDN博客 iwebsec靶场 SQL注入漏洞通关笔记2- 字符型注入&#xff08;宽字节注入&#xff09;_mooyuan的博客-CSDN博客 iwebsec靶场 SQL注入漏洞通关笔记3- bool注入&#xff08;布尔型盲注&#…

Ajax学习:同源策略(与跨域相关)ajax默认遵循同源策略

同源策略&#xff1a;是浏览器的一种安全策略 同源意味着&#xff1a;协议、域名、端口号必须相同 违背同源便是跨域 当前网页的url和ajax请求的目标资源的url必须协议、域名、端口号必须相同 比如&#xff1a;当前网页&#xff1a;协议http 域名 a.com 端口号8000 目标请求…

python——spark入门

Hadoop是对大数据集进行分布式计算的标准工具&#xff0c;这也是为什么当你穿过机场时能看到”大数据(Big Data)”广告的原因。它已经成为大数据的操作系统&#xff0c;提供了包括工具和技巧在内的丰富生态系统&#xff0c;允许使用相对便宜的商业硬件集群进行超级计算机级别的…

Android Poco初始化时,不大起眼但可能存在坑点的参数们

1. 前言 进行Android poco初始化的时候&#xff0c;可能大多数同学都是直接在Poco辅助窗里选择Android模式&#xff0c;然后选择自动帮我们补充poco的初始化脚本&#xff1a; 这种情况下&#xff0c;我们大多数都不会关注初始化的参数。但如果我们不了解这些参数的含义&#x…

Spring之@RequestMapping、@GetMapping、 @PostMapping 三者的区别

我的理解&#xff1a;其实RequestMapping、GetMapping、 PostMapping 三者就是父类和子类的区别&#xff0c;RequestMapping是父类&#xff0c;GetMapping、 PostMapping为子类集成了RequestMapping更明确了http请求的类型 分析三者的源码&#xff1a; RequestMapping .class&…

C#教务管理大数据平台系统源码

校务管理系统是专门针对幼儿园、培训学校的业务应用而设计研发的一款行业应用软件。校管家校务管理系统融入先进的协同管理理念&#xff0c;运用领先的信息化、网络化处理技术&#xff0c;结合丰富的教育培训行业经验&#xff0c;切实有效的解决幼儿园、培训学校日常工作中的关…

[附源码]计算机毕业设计-菜篮子系统Springboot程序

项目运行 环境配置&#xff1a; Jdk1.8 Tomcat7.0 Mysql HBuilderX&#xff08;Webstorm也行&#xff09; Eclispe&#xff08;IntelliJ IDEA,Eclispe,MyEclispe,Sts都支持&#xff09;。 项目技术&#xff1a; SSM mybatis Maven Vue 等等组成&#xff0c;B/S模式 M…

KVM虚机添加磁盘

KVM虚拟机添加磁盘两种方法&#xff1a; 1、添加虚拟磁盘文件 2、添加物理磁盘 需求 1、/kvm/kvms目录是我们KVM磁盘镜像集中管理的位置&#xff0c;我们需要在/kvm/kvms下给ceph1虚拟机创建名为ceph1-vdb.qcow2的磁盘文件&#xff0c;大小为80G&#xff0c;作为ceph1的vdb磁盘…

Python和labview先学哪个

前言 在这之前&#xff0c;先跟大家分享个身边的故事。 大学同学小宏&#xff0c;在北京一家电子设备公司做运维&#xff0c;上周四刚被升为运维部经理&#xff0c;薪资涨了35%。 但你一定想不到&#xff0c;他平时从不加班&#xff0c;甚至还经常迟到。 &#xff08;文末送…

Qt入门总结

文章目录Qt一、各文件基本概念1、main.cpp文件2、XXX.pro文件3、XXX.h文件二、基本知识1、命名规范2、快捷键三、入门操作1、添加按钮2、重置窗口大小3、设置窗口标题4、设置固定的窗口大小5、对象树6、添加源文件/头文件7、窗口坐标系四、信号与槽1、让按钮附带功能2、自定义信…

浅谈affine_trans_point_2d与affine_trans_pixel

先看下两个坐标图谱&#xff1a; 变换前&#xff1a; 变换后&#xff1a; 我们根据1号点和9号点前后的关系&#xff0c;计算变换后其他点的坐标&#xff1a;这其实就是根据MARK点进行定位的原理 halcon代码&#xff1a; 执行结果&#xff1a; 我们发现&#xff0c;两种变换方…