C++ 图的遍历

news2024/12/28 5:36:38

1. 图的遍历

给定一个图 G 和其中任意一个顶点 v0 ,从 v0 出发,沿着图中各边访问图中的所有顶点,且每个顶
点仅被遍历一次 " 遍历 " 即对结点进行某种操作的意思
请思考树以前是怎么遍历的,此处可以直接用来遍历图吗?为什么?

1.1 图的广度优先遍历

 

 

 

 

问题:如何防止节点被重复遍历
#include <iostream>
#include <vector>
#include<queue>
#include <map>
#include <algorithm>
using namespace std;

namespace LinkTable
{

    template <class W>
    struct LinkEgde
    {
        int _srcIndex;
        int _dstIndex;
        W _w;
        LinkEgde<W> *_next;

        LinkEgde(const W &w) : _srcIndex(-1), _dstIndex(-1), _w(w), _next(nullptr) {}
    };

    template <class V, class W, bool Direction = false>
    class Graph
    {

        typedef LinkEgde<W> Edge;

    public:
        Graph(const V *vertexs, size_t n)
        {

            _vertexs.reserve(n);
            for (size_t i = 0; i < n; i++)
            {

                _vertexs.push_back(_vertexs[i]);
                _vIndexMap[_vertexs[i]] = i;
            }
            _linkTable.resize(n, nullptr);
        }
        size_t GetVertexIndex(const V &v)
        {

            auto ret = _vIndexMap.find(v);
            if (ret != _vIndexMap.end())
            {
                return ret->second;
            }
            else
            {
                throw invalid_argument("not exist vertex");
                return -1;
            }
        }

        void AddEdge(const V &src, const V &dst, const W &w)
        {

            size_t srcindex = GetVertexIndex(src);
            size_t dstindex = GetVertexIndex(dst);

            Edge *sd_edge = new Edge(w);
            sd_edge->_srcIndex = srcindex;
            sd_edge->_dstIndex = dstindex;
            sd_edge->_next = _linkTable[srcindex];
            _linkTable[srcindex] = sd_edge;

            //如果是无向图

            if (Direction == false)
            {

                Edge *ds_edge = new Edge(w);
                ds_edge->_srcIndex = dstindex;
                ds_edge->_dstIndex = srcindex;
                ds_edge->_next = _linkTable[dstindex];
                _linkTable[dstindex] = ds_edge;
            }
        }

          void BFS(const V & src){

            size_t srcindex=GetVertexIndex(src);
            vector<boo> visited;

            visited.resize(_vertexs.size(),false);
            queue<int> q;

            q.push(srcindex);
            visited[srcindex]=true;

            size_t d=1;
            size_t dSize=1;

            while(!q.empty()){
                printf("%s",src.c_str(),d);
                    while (d--)
            {
                size_t front=q.front();
                q.pop();
                for(size_t i=0;i<_vertexs.size();i++){

                    if(visited[i]!=true&&_matrix[front][i]!=MAX_W)

                    printf("[%d:%s]",i,_vertexs[i].c_str());
                    visited[i]=true;
                    q.push(i);
                }
            }
            cout<<endl;
        }

        
            

            
        }

    private:
        map<string, int> _vIndexMap;
        vector<V> _vertexs;        //顶点集合
        vector<Edge *> _linkTable; //边的集合
    };

    void TestGraph()
    {

        string a[] = {"zhangsan", "lisi", "wangwu", "zhaoliu"};
        Graph<string, int> g1(a, 4);
        g1.AddEdge("zhangsan", "lisi", 100);
        g1.AddEdge("zhangsan", "wangwu", 200);
        g1.AddEdge("wangwu", "zhaoliu", 30);
        g1.BFS("zhangsan");
    }


    

  

}

int main()
{

    LinkTable::TestGraph();

    system("pause");
    return 0;
}

1.2 图的深度优先遍历 

 

 

void _DFS(int index, vector<bool>& visited)
{
 if(!visited[index])
 {
 cout<<_v[index]<<" ";
 visited[index] = true;
 LinkEdge* pCur = _linkEdges[index];
 while(pCur)
 {
 _DFS(pCur->_dst, visited);
 pCur = pCur->_pNext;
 }
 }
}
void DFS(const V& v)
{
 cout<<"DFS:";
 vector<bool> visited(_v.size(), false);
 _DFS(GetIndexOfV(v), visited);
 for(size_t index = 0; index < _v.size(); ++index)
 _DFS(index, visited);
 cout<<endl;
}
void TestGraphDBFS()
{
string a[] = { "张三", "李四", "王五", "赵六", "周七" };
Graph<string, int> g1(a, sizeof(a)/sizeof(string));
g1.AddEdge("张三", "李四", 100);
g1.AddEdge("张三", "王五", 200);
g1.AddEdge("王五", "赵六", 30);
g1.AddEdge("王五", "周七", 30);
g1.BFS("张三");
g1.DFS("张三");
}

4. 最小生成树

连通图中的每一棵生成树,都是原图的一个极大无环子图,即: 从其中删去任何一条边,生成树
就不在连通;反之,在其中引入任何一条新边,都会形成一条回路
若连通图由 n 个顶点组成,则其生成树必含 n 个顶点和 n-1 条边 。因此构造最小生成树的准则有三
条:
1. 只能使用图中的边来构造最小生成树
2. 只能使用恰好 n-1 条边来连接图中的 n 个顶点
3. 选用的 n-1 条边不能构成回路
构造最小生成树的方法: Kruskal 算法 Prim 算法 。这两个算法都采用了 逐步求解的贪心策略
贪心算法:是指在问题求解时,总是做出当前看起来最好的选择。也就是说贪心算法做出的不是
整体 最优的的选择,而是某种意义上的局部最优解。贪心算法不是对所有的问题都能得到整体最优解

4.1 Kruskal算法

        给一个有n个顶点的连通网络N={V,E}首先构造一个由这n个顶点组成、不含任何边的图G={V,NULL},其中每个顶点自成一个连通分量,其次不断从E中取出权值最小的一条边(若有多条任取其一),若该边的两个顶点来自不同的连通分量,则将此边加入到G。如此重复,直到所有顶点在同一个连通分量上为止。核心:每次迭代时,选出一条具有最小权值,且两端点不在同一连通分量上的边,加入生成树。

W Kruskal(Self& minTree)
{
     minTree._vertexs = _vertexs;
     minTree._vIndexMap = _vIndexMap;
     minTree._matrix.resize(_vertexs.size());
     for (auto& e : minTree._matrix)
 {
         e.resize(_vertexs.size(), MAX_W);
     }
     priority_queue<Edge, vector<Edge>, greater<Edge>> pq;
     for (size_t i = 0; i < _matrix.size(); ++i)
     {
         for (size_t j = 0; j < _matrix[i].size(); ++j)
         {
             if (i < j && _matrix[i][j] != MAX_W)
             {
                 pq.push(Edge(i, j, _matrix[i][j]));
             }
         }
     }
     W total = W();
     // 贪心算法,从最小的边开始选
     size_t i = 1;
     UnionFindSet ufs(_vertexs.size());
     while (i < _vertexs.size() && !pq.empty())
     {
         Edge min = pq.top();
         pq.pop();
         // 边不在一个集合,说明不会构成环,则添加到最小生成树
         if (ufs.FindRoot(min._srci) != ufs.FindRoot(min._dsti))
         {
             //cout << _vertexs[min._srci] << "-" << _vertexs[min._dsti] << 
":" << _matrix[min._srci][min._dsti] << endl;
             minTree._AddEdge(min._srci, min._dsti, min._w);
             total += min._w;
             ufs.Union(min._srci, min._dsti);
             ++i;
         }
     }
     if (i == _vertexs.size())
     {
         return total;
     }
     else
     {
         return W();
     }
}
void TestGraphMinTree()
{
     const char* str = "abcdefghi";
     Graph<char, int> g(str, strlen(str));
     g.AddEdge('a', 'b', 4);
     g.AddEdge('a', 'h', 8);
     //g.AddEdge('a', 'h', 9);
     g.AddEdge('b', 'c', 8);
     g.AddEdge('b', 'h', 11);
 g.AddEdge('c', 'i', 2);
     g.AddEdge('c', 'f', 4);
     g.AddEdge('c', 'd', 7);
     g.AddEdge('d', 'f', 14);
     g.AddEdge('d', 'e', 9);
     g.AddEdge('e', 'f', 10);
     g.AddEdge('f', 'g', 2);
     g.AddEdge('g', 'h', 1);
     g.AddEdge('g', 'i', 6);
     g.AddEdge('h', 'i', 7);
     Graph<char, int> kminTree;
     cout << "Kruskal:" << g.Kruskal(kminTree) << endl;
     kminTree.Print();
     Graph<char, int> pminTree;
     cout << "Prim:" << g.Prim(pminTree, 'a') << endl;
     pminTree.Print();
}

 4.2 Prim算法

 

W Prim(Self& minTree, const V& src)
{
 minTree._vertexs = _vertexs;
 minTree._vIndexMap = _vIndexMap;
 minTree._matrix.resize(_vertexs.size());
 for (auto& e : minTree._matrix)
 {
 e.resize(_vertexs.size(), MAX_W);
 }
 size_t srci = GetVertexIndex(src);
set<size_t> inSet;
 inSet.insert(srci);
 priority_queue<Edge, vector<Edge>, greater<Edge>> pq;
 for (size_t i = 0; i < _vertexs.size(); ++i)
 {
 if (_matrix[srci][i] != MAX_W)
 {
 pq.push(Edge(srci, i, _matrix[srci][i]));
 }
 }
 W total = W();
 while (inSet.size() < _vertexs.size() && !pq.empty())
 {
 Edge min = pq.top();
 pq.pop();
 // 防止环的问题
 if (inSet.find(min._srci) == inSet.end() ||
inSet.find(min._dsti) == inSet.end())
 {
 //cout << _vertexs[min._srci] << "-" << 
_vertexs[min._dsti] << ":" << _matrix[min._srci][min._dsti] << endl;
 minTree._AddEdge(min._srci, min._dsti, min._w);
 total += min._w;
 // 新入顶点的连接边进入队列
 for (size_t i = 0; i < _vertexs.size(); ++i)
 {
 if (_matrix[min._dsti][i] != MAX_W && inSet.find(i) 
== inSet.end())
 {
 pq.push(Edge(min._dsti, i, _matrix[min._dsti]
[i]));
 }
 }
 inSet.insert(min._dsti);
 }
 }
 if (inSet.size() == _vertexs.size())
 {
 return total;
 }
 else
 {
 return W();
 }
}
void TestGraphMinTree()
{
     const char* str = "abcdefghi";
     Graph<char, int> g(str, strlen(str));
     g.AddEdge('a', 'b', 4);
     g.AddEdge('a', 'h', 8);
//g.AddEdge('a', 'h', 9);
     g.AddEdge('b', 'c', 8);
     g.AddEdge('b', 'h', 11);
     g.AddEdge('c', 'i', 2);
     g.AddEdge('c', 'f', 4);
     g.AddEdge('c', 'd', 7);
     g.AddEdge('d', 'f', 14);
     g.AddEdge('d', 'e', 9);
     g.AddEdge('e', 'f', 10);
     g.AddEdge('f', 'g', 2);
     g.AddEdge('g', 'h', 1);
     g.AddEdge('g', 'i', 6);
     g.AddEdge('h', 'i', 7);
     Graph<char, int> kminTree;
     cout << "Kruskal:" << g.Kruskal(kminTree) << endl;
     kminTree.Print();
     Graph<char, int> pminTree;
     cout << "Prim:" << g.Prim(pminTree, 'a') << endl;
     pminTree.Print();
}

5. 最短路径

最短路径问题:从在带权有向图 G 中的某一顶点出发,找出一条通往另一顶点的最短路径,最短也就是沿路径各边的权值总和达到最小。

5.1单源最短路径--Dijkstra算法

单源最短路径问题:给定一个图 G = ( V E ) G=(V E)G=(V E) ,求源结点 s V s Vs V 到图
中每个结点 v V v Vv V 的最短路径。 Dijkstra 算法就适用于解决带权重的有向图上的单源最短
路径问题, 同时算法要求图中所有边的权重非负 。一般在求解最短路径的时候都是已知一个起点
和一个终点,所以使用 Dijkstra 算法求解过后也就得到了所需起点到终点的最短路径。
针对一个带权有向图 G ,将所有结点分为两组 S Q S 是已经确定最短路径的结点集合,在初始时
为空(初始时就可以将源节点 s 放入,毕竟源节点到自己的代价是 0 ), Q 为其余未确定最短路径
的结点集合, 每次从 Q 中找出一个起点到该结点代价最小的结点 u ,将 u Q 中移出,并放入 S
中,对 u 的每一个相邻结点 v 进行松弛操作 。松弛即对每一个相邻结点 v ,判断源节点 s 到结点 u
的代价与 u v 的代价之和是否比原来 s v 的代价更小,若代价比原来小则要将 s v 的代价更新
s u u v 的代价之和,否则维持原样。如此一直循环直至集合 Q 为空,即所有节点都已经
查找过一遍并确定了最短路径,至于一些起点到达不了的结点在算法循环后其代价仍为初始设定
的值,不发生变化。 Dijkstra 算法每次都是选择 V-S 中最小的路径节点来进行更新,并加入 S 中,所
以该算法使用的是贪心策略。
Dijkstra 算法存在的问题是不支持图中带负权路径,如果带有负权路径,则可能会找不到一些路
径的最短路径。
void Dijkstra(const V& src, vector<W>& dist, vector<int>& parentPath)
{
size_t N = _vertexs.size();
size_t srci = GetVertexIndex(src);
// vector<W> dist,记录srci-其他顶点最短路径权值数组
dist.resize(N, MAX_W);
// vector<int> parentPath 记录srci-其他顶点最短路径父顶点数组
parentPath.resize(N, -1);
// 标记是否找到最短路径的顶点集合S
vector<bool> S;
S.resize(N, false);
// srci的权值给一个最小值,方便贪心第一次找到这个节点
dist[srci] = W(); 
// N个顶点更新N次
for (size_t i = 0; i < N; ++i)
 {
     // 贪心算法:srci到不在S中路径最短的那个顶点u
     W min = MAX_W;
     size_t u = srci;
     for (size_t j = 0; j < N; ++j)
     {
         if (S[j] == false && dist[j] < min)
         {
             min = dist[j];
             u = j;
         }
     }
     S[u] = true;
     // 松弛算法:更新一遍u连接的所有边,看是否能更新出更短连接路径
     for (size_t k = 0; k < N; ++k)
     {
// 如果srci->u + u->k 比 srci->k更短 则进行更新
         if (S[k] == false && _matrix[u][k] != MAX_W
             && dist[u] + _matrix[u][k] < dist[k])
         {
             dist[k] = dist[u] + _matrix[u][k];
             parentPath[k] = u;
         }
     }
 }
}
// 打印最短路径的逻辑算法
void PrinrtShotPath(const V& src, const vector<W>& dist, const vector<int>&
parentPath)
{
 size_t N = _vertexs.size();
 size_t srci = GetVertexIndex(src);
 for (size_t i = 0; i < N; ++i)
 {
 if (i == srci)
 continue;
 
 vector<int> path;
 int parenti = i;
 while (parenti != srci)
 {
 path.push_back(parenti);
 parenti = parentPath[parenti];
 }
 path.push_back(srci);
 reverse(path.begin(), path.end());
 for (auto pos : path)
 {
 cout << _vertexs[pos] << "->";
 }
 cout << dist[i] << endl;
 }
}
void TestGraphDijkstra()
{
const char* str = "syztx";
Graph<char, int, INT_MAX, true> g(str, strlen(str));
g.AddEdge('s', 't', 10);
g.AddEdge('s', 'y', 5);
g.AddEdge('y', 't', 3);
g.AddEdge('y', 'x', 9);
g.AddEdge('y', 'z', 2);
g.AddEdge('z', 's', 7);
g.AddEdge('z', 'x', 6);
g.AddEdge('t', 'y', 2);
g.AddEdge('t', 'x', 1);
g.AddEdge('x', 'z', 4);
vector<int> dist;
vector<int> parentPath;
g.Dijkstra('s', dist, parentPath);
g.PrinrtShotPath('s', dist, parentPath);
// 图中带有负权路径时,贪心策略则失效了。
// 测试结果可以看到s->t->y之间的最短路径没更新出来
/*const char* str = "sytx";
 Graph<char, int, INT_MAX, true> g(str, strlen(str));
 g.AddEdge('s', 't', 10);
 g.AddEdge('s', 'y', 5);
 g.AddEdge('t', 'y', -7);
 g.AddEdge('y', 'x', 3);
 vector<int> dist;
 vector<int> parentPath;
 g.Dijkstra('s', dist, parentPath);
 g.PrinrtShotPath('s', dist, parentPath);*/
}

 5.2 单源最短路径--Bellman-Ford算法

Dijkstra 算法只能用来解决正权图的单源最短路径问题,但有些题目会出现负权图。这时这个算法
就不能帮助我们解决问题了,而 bellman—ford 算法可以解决负权图的单源最短路径问题 。它的
优点是可以解决有负权边的单源最短路径问题,而且可以用来判断是否有负权回路。它也有明显
的缺点,它的时间复杂度 O(N*E) (N 是点数, E 是边数 ) 普遍是要高于 Dijkstra 算法 O(N²) 的。像这里
如果我们使用邻接矩阵实现,那么遍历所有边的数量的时间复杂度就是 O(N^3) ,这里也可以看出
Bellman-Ford 就是一种暴力求解更新。

 

bool BellmanFord(const V& src, vector<W>& dist, vector<int>& parentPath)
{
 size_t N = _vertexs.size();
 size_t srci = GetVertexIndex(src);
 // vector<W> dist,记录srci-其他顶点最短路径权值数组
 dist.resize(N, MAX_W);
// vector<int> parentPath 记录srci-其他顶点最短路径父顶点数组
 parentPath.resize(N, -1);
 // 先更新srci->srci为最小值
 dist[srci] = W();
 for (size_t k = 0; k < N - 1; ++k)
 {
 bool exchange = false;
 for (size_t i = 0; i < N; ++i)
 {
 for (size_t j = 0; j < N; ++j)
 {
 // srci->i + i->j < srci->j 则更新路径及权值
 if (_matrix[i][j] != MAX_W
 && dist[i] + _matrix[i][j] < dist[j])
 {
 dist[j] = dist[i] + _matrix[i][j];
 parentPath[j] = i;
 exchange = true;
 }
 }
 }
 if (exchange == false)
 break;
 }
 for (size_t i = 0; i < N; ++i)
 {
 for (size_t j = 0; j < N; ++j)
 {
 // 检查有没有负权回路
 if (_matrix[i][j] != MAX_W
 && dist[i] + _matrix[i][j] < dist[j])
 {
 return false;
 }
 }
 }
 return true;
}
void TestGraphBellmanFord()
{
 const char* str = "syztx";
 Graph<char, int, INT_MAX, true> g(str, strlen(str));
 g.AddEdge('s', 't', 6);
 g.AddEdge('s', 'y', 7);
 g.AddEdge('y', 'z', 9);
 g.AddEdge('y', 'x', -3);
 g.AddEdge('z', 's', 2);
 g.AddEdge('z', 'x', 7);
 g.AddEdge('t', 'x', 5);
 g.AddEdge('t', 'y', 8);
 g.AddEdge('t', 'z', -4);
g.AddEdge('x', 't', -2);
 vector<int> dist;
 vector<int> parentPath;
 if (g.BellmanFord('s', dist, parentPath))
 {
 g.PrinrtShotPath('s', dist, parentPath);
 }
 else
 {
 cout << "存在负权回路" << endl;
 }
 // 微调图结构,带有负权回路的测试
 //const char* str = "syztx";
 //Graph<char, int, INT_MAX, true> g(str, strlen(str));
 //g.AddEdge('s', 't', 6);
 //g.AddEdge('s', 'y', 7);
 //g.AddEdge('y', 'x', -3);
 //g.AddEdge('y', 'z', 9);
 //g.AddEdge('y', 'x', -3);
 //g.AddEdge('y', 's', 1); // 新增
 //g.AddEdge('z', 's', 2);
 //g.AddEdge('z', 'x', 7);
 //g.AddEdge('t', 'x', 5);
 //g.AddEdge('t', 'y', -8); // 更改
 //g.AddEdge('t', 'z', -4);
 //g.AddEdge('x', 't', -2);
 //vector<int> dist;
 //vector<int> parentPath;
 //if (g.BellmanFord('s', dist, parentPath))
 //{
 // g.PrinrtShotPath('s', dist, parentPath);
 //}
 //else
 //{
 // cout << "存在负权回路" << endl;
 //}
}

5.3 多源最短路径--Floyd-Warshall算法

Floyd-Warshall 算法是解决任意两点间的最短路径的一种算法。Floyd算法考虑的是一条最短路径的中间节点,即简单路径 p={v1,v2,…,vn} 上除 v1 vn 的任意节点。
k p 的一个中间节点,那么从 i j 的最短路径 p 就被分成 i k k j 的两段最短路径 p1 p2 p1
是从 i k 且中间节点属于 {1 2 k-1} 取得的一条最短路径。 p2 是从 k j 且中间节点属于 {1
2 k-1} 取得的一条最短路径。

 

Floyd 算法本质是三维动态规划, D[i][j][k] 表示从点 i 到点 j 只经过 0 k 个点最短路径,然后建立
起转移方程,然后通过空间优化,优化掉最后一维度,变成一个最短路径的迭代算法,最后即得
到所以点的最短路。

 

void FloydWarShall(vector<vector<W>>& vvDist, vector<vector<int>>&
vvParentPath)
{
 size_t N = _vertexs.size();
 vvDist.resize(N);
 vvParentPath.resize(N);
// 初始化权值和路径矩阵
 for (size_t i = 0; i < N; ++i)
 {
 vvDist[i].resize(N, MAX_W);
 vvParentPath[i].resize(N, -1);
 }
 // 将直接相连的路径初始化
 for (size_t i = 0; i < N; ++i)
 {
 for (size_t j = 0; j < N; ++j)
 {
 if (_matrix[i][j] != MAX_W)
 { 
 vvDist[i][j] = _matrix[i][j];
 vvParentPath[i][j] = i;
 }
 else
 {
 vvParentPath[i][j] = -1;
 }
 if (i == j)
 {
 vvDist[i][j] = 0;
 vvParentPath[i][j] = -1;
 }
 }
 }
 // 依次用顶点k作为中转点更新最短路径
 for (size_t k = 0; k < N; ++k)
 {
 for (size_t i = 0; i < N; ++i)
 {
 for (size_t j = 0; j < N; ++j)
 {
 // i->k + k->j 比 i->j前面更新的距离更短,则更新
 if (vvDist[i][k] != MAX_W && vvDist[k][j] != MAX_W
 && vvDist[i][k] + vvDist[k][j] < vvDist[i][j])
 {
 vvDist[i][j] = vvDist[i][k] + vvDist[k][j];
 vvParentPath[i][j] = vvParentPath[k][j];
 }
 }
 }
 // 打印权值和路径矩阵观察数据
 //for (size_t i = 0; i < N; ++i)
 //{
 // for (size_t j = 0; j < N; ++j)
 // {
 // if (vvDist[i][j] == MAX_W)
 // {
 // //cout << "*" << " ";
 // printf("%3c", '*');
 // }
 // else
// {
 // //cout << vvDist[i][j] << " ";
 // printf("%3d", vvDist[i][j]);
 // }
 // }
 // cout << endl;
 //}
 //cout << endl;
 //for (size_t i = 0; i < N; ++i)
 //{
 // for (size_t j = 0; j < N; ++j)
 // {
 // //cout << vvParentPath[i][j] << " ";
 // printf("%3d", vvParentPath[i][j]);
 // }
 // cout << endl;
 //}
 //cout << "=================================" << endl;
 }
}
void TestFloydWarShall()
{
 const char* str = "12345";
 Graph<char, int, INT_MAX, true> g(str, strlen(str));
 g.AddEdge('1', '2', 3);
 g.AddEdge('1', '3', 8);
 g.AddEdge('1', '5', -4);
 g.AddEdge('2', '4', 1);
 g.AddEdge('2', '5', 7);
 g.AddEdge('3', '2', 4);
 g.AddEdge('4', '1', 2);
 g.AddEdge('4', '3', -5);
 g.AddEdge('5', '4', 6);
 vector<vector<int>> vvDist;
 vector<vector<int>> vvParentPath;
 g.FloydWarShall(vvDist, vvParentPath);
 // 打印任意两点之间的最短路径
 for (size_t i = 0; i < strlen(str); ++i)
 {
 g.PrinrtShotPath(str[i], vvDist[i], vvParentPath[i]);
 cout << endl;
 }
}

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

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

相关文章

手搓GPT系列之 - 通过理解LSTM的反向传播过程,理解LSTM解决梯度消失的原理 - 逐条解释LSTM创始论文全部推导公式,配超多图帮助理解(下篇)

本文承接上篇上篇在此和中篇中篇在此&#xff0c;继续就Sepp Hochreiter 1997年的开山大作 Long Short-term Memory 中APPENDIX A.1和A.2所载的数学推导过程进行详细解读。希望可以帮助大家理解了这个推导过程&#xff0c;进而能顺利理解为什么那几个门的设置可以解决RNN里的梯…

Spring中@NotEmpty、@NotBlank、@NotNull 的区别和使用

1、引入依赖 <dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId><version>2.0.5.RELEASE</version> </dependency>NotEmpty、NotBlank、NotNull 包的位置&#xff1…

i春秋,春秋云镜系列

目录 先提神CVE-2022-32991靶标介绍&#xff1a;复现&#xff1a; CVE-2022-30887靶标介绍&#xff1a;复现 先提神 CVE-2022-32991 靶标介绍&#xff1a; 该CMS的welcome.php中存在SQL注入攻击。 复现&#xff1a; 打开靶场地址&#xff0c;三个随便选一个进去&#xff0c…

Gof23设计模式之适配器模式

1.定义 将一个类的接口转换成客户希望的另外一个接口&#xff0c;使得原本由于接口不兼容而不能一起工作的那些类能一起工作。 适配器模式分为类适配器模式和对象适配器模式&#xff0c;前者类之间的耦合度比后者高&#xff0c;且要求程序员了解现有组件库中的相关组件的内部结…

国产划片机开创了半导体芯片切割的新工艺时代

国产划片机确实开创了半导体芯片切割的新工艺时代。划片机是一种用于切割和划分半导体芯片的设备&#xff0c;它是半导体制造过程中非常重要的一环。在过去&#xff0c;划片机技术一直被国外厂商所垄断&#xff0c;国内半导体制造企业不得不依赖进口设备。 然而&#xff0c;随着…

QT学习笔记6--信号之间的连接

连接 仍然使用connect函数&#xff0c;但是和函数重载类似&#xff0c;需要用到函数指针。如下所示 void (teacher:: *teachersignals)(void) &teacher::hungery;void (student:: *studentslots)(void) &student::treat;connect(zt,teachersignals,st,studentslots)…

水利三类人员专职安全生产管理人员c证安全生产法律法规考试题库

​本题库是根据最新考试大纲要求&#xff0c;结合近年来考试真题的重难点进行汇编整理组成的全真模拟试题&#xff0c;考生们可以进行专项训练&#xff0c;查漏补缺巩固知识点。本题库对热点考题和重难点题目都进行了仔细的整理和编辑&#xff0c;相信考生在经过了针对性的刷题…

浏览器使用Notification桌面通知消息推送

什么是 Notification&#xff1f; Notification 是浏览器最小化后在桌面显示消息的一种方法类似于 360 等流氓软件在桌面右下角的弹窗广告它与浏览器是脱离的&#xff0c;消息是置顶的 一、弹窗授权 授权当前页面允许通知可以通过检查只读属性 Notification.permission 的值来…

基于Springboot+Html的健身房管理系统

✌全网粉丝20W,csdn特邀作者、博客专家、CSDN新星计划导师、java领域优质创作者,博客之星、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java技术领域和毕业项目实战✌ &#x1f345;文末获取项目下载方式&#x1f345; 一、项目背景介绍&#xff1a; 随着现代生活方式的改…

基于改进莱维飞行和混沌映射的金鹰优化算法(10种混沌映射随意切换),附matlab代码

“ 本篇文章对金鹰优化算法进行改进&#xff0c;首先通过引入混沌映射机制&#xff0c;对其群体进行初始化&#xff0c;增加金鹰个体的多样性&#xff1b;然后在金鹰个体的位置更新公式上引入改进的莱维飞行机制&#xff0c;提高搜索精度&#xff0c;帮助金鹰个体跳出局部最优。…

谈谈NLP中 大语言模型LLM的 思维链 Chain-of-Thought(CoT)

Chain-of-Thought(CoT) 1.介绍 在过去几年的探索中&#xff0c;业界发现了一个现象&#xff0c;在增大模型参数量和训练数据的同时&#xff0c;在多数任务上&#xff0c;模型的表现会越来越好。因而&#xff0c;现有的大模型LLM&#xff0c;最大参数量已经超过了千亿。 然而…

大数据Doris(五十五):BACKUP数据备份案例和注意事项

文章目录 BACKUP数据备份案例和注意事项 一、BACKUP数据备份案例 1、Doris中创建数据库&#xff0c;以及建表插入数据 2、创建远端仓库 3、全量备份指定 Doris 库下所有表所有分区数据 4、查看 backup 作业执行情况 5、查看远端仓库中已备份结果 二、注意事项 BACKUP数…

【花雕】青少年机器人技术等级考试理论综合试卷(一级)2021年9月

随着科技的不断进步&#xff0c;机器人技术已经成为了一个重要的领域。在这个领域中&#xff0c;机械结构是机器人设计中至关重要的一部分&#xff0c;它决定了机器人的形态、运动方式和工作效率。对于青少年机器人爱好者来说&#xff0c;了解机械结构的基础知识&#xff0c;掌…

Drools用户手册翻译——第二章 入门(下)测试和评估

因为篇幅原因&#xff0c;所以分为上下两个部分&#xff0c;主要就是通过一个交通违章项目的例子&#xff0c;带你先粗略感受一下决策模型的使用流程&#xff0c;总体来说有详细&#xff0c;也有没说清的地方&#xff0c;如果想要了解一下决策模型&#xff0c;可以进来了解一下…

[探地雷达]利用Faster RCNN对B-SCAN探地雷达数据进行目标检测

引用量较高的一篇会议论文。 由于真实雷达图像较少&#xff0c;作者采用了GPR工具箱&#xff0c;使用不同配置&#xff0c;合成了部分模拟雷达图。然后采用Cifar-10数据&#xff08;灰度图&#xff09;对Faster RCNN进行预训练&#xff0c;再采用真实和合成数据进行微调。 论…

(0021) H5-Vuejs配合 mint-ui 开发移动端web

mint-ui 初衷 element-ui主打pcweb&#xff0c;导致移动端上UI适配问题突出&#xff0c;趟了很多坑。这次更加理智些&#xff0c;选择了饿了么团队的主打移动端的mint-ui&#xff0c;目前来说体验很好。 认识Mint-ui 首先在手机上体验其demo&#xff0c;扫描链接&#xff1a;…

双层玻璃门碎了一面怎么更换

更换双层玻璃门的碎片需要按照以下步骤进行&#xff1a; 1. 备齐工具和材料&#xff1a;你需要准备以下工具和材料&#xff1a;安全手套、安全护目镜、扁头螺丝刀、绳子、玻璃胶和新的玻璃门。 2. 移除残存玻璃&#xff1a;首先&#xff0c;将门上的残留玻璃及其框架小心地取下…

12给图片加水印(matlab程序)

代码 clear;clc size512; block8; blocknosize/block; LENGTHsize*size/64; Alpha10.02; Alpha20.02; T1100; Izeros(size,size); Dzeros(size,size); BWzeros(size,size); block_dct1zeros(block,block); iimread(watermark64by64.png); %水印原图 markreshape(i…

springboot 2.6.6接入prometheus

springboot是2.6.6版本&#xff0c;刚开始用的是simpleclient客户端 <dependency><groupId>io.prometheus</groupId><artifactId>simpleclient</artifactId><version>0.16.0</version> </dependency>接入后一直报AbstractEnd…

Linux:CentOS安装Git

报错如下&#xff1a;git: 未找到命令... 这是因为没有安装git。 解决方案 使用yum安装git yum -y install git 查看是否安装git,若出现版本号&#xff0c;则代表已经安装了git git --version