数据结构——图(遍历,最小生成树,最短路径)
目录
一.图的基本概念
二.图的存储结构
1.邻接矩阵
2.邻接表
三.图的遍历
1.图的广度优先遍历
2.图的深度优先遍历
四.最小生成树
1.Kruskal算法
2.Prim算法
五.最短路径
1.单源最短路径--Dijkstra算法
2.单源最短路径--Bellman-Ford算法
3.多源最短路径--Floyd-Warshall算法
六.整体实现
1.UnionFindSet.h
2.Graph.h
3.test.cpp
一.图的基本概念
图是由顶点集合及顶点间的关系组成的一种数据结构:G = (V, E),其中:
顶点集合V = {x|x属于某个数据对象集}是有穷非空集合;
E = {(x,y)|x,y属于V}或者E = {|x,y属于V && Path(x, y)}是顶点间关系的有穷集合,也叫做边的集合
(x, y)表示x到y的一条双向通路,即(x, y)是无方向的;Path(x, y)表示从x到y的一条单向通路,即Path(x, y)是有方向的
顶点和边:图中结点称为顶点,第i个顶点记作vi。两个顶点vi和vj相关联称作顶点vi和顶点vj之间有一条边,图中的第k条边记作ek,ek = (vi,vj)或<vi,vj>
有向图和无向图:在有向图中,顶点对是有序的,顶点对<x,y>称为顶点x到顶点y的一条边(弧),和是两条不同的边,比如下图G3和G4为有向图。在无向图中,顶点对(x, y) 是无序的,顶点对(x,y)称为顶点x和顶点y相关联的一条边,这条边没有特定方向,(x, y)和(y,x) 是同一条边,比如下图G1和G2为无向图。注意:无向边(x, y)等于有向边和
完全图:在有n个顶点的无向图中,若有n * (n-1)/2条边,即任意两个顶点之间有且仅有一条边, 则称此图为无向完全图,比如上图G1;在n个顶点的有向图中,若有n * (n-1)条边,即任意两个顶点之间有且仅有方向相反的边,则称此图为有向完全图,比如上图G4
邻接顶点:在无向图中G中,若(u, v)是E(G)中的一条边,则称u和v互为邻接顶点,并称边(u,v)依附于顶点u和v;在有向图G中,若是E(G)中的一条边,则称顶点u邻接到v,顶点v邻接自顶 点u,并称边与顶点u和顶点v相关联
顶点的度:顶点v的度是指与它相关联的边的条数,记作deg(v)。在有向图中,顶点的度等于该顶点的入度与出度之和,其中顶点v的入度是以v为终点的有向边的条数,记作indev(v);顶点v的出度是以v为起始点的有向边的条数,记作outdev(v)。因此:dev(v) = indev(v) + outdev(v)。注 意:对于无向图,顶点的度等于该顶点的入度和出度,即dev(v) = indev(v) = outdev(v)
路径:在图G = (V, E)中,若从顶点vi出发有一组边使其可到达顶点vj,则称顶点vi到顶点vj的顶点序列为从顶点vi到顶点vj的路径
路径长度:对于不带权的图,一条路径的路径长度是指该路径上的边的条数;对于带权的图,一 条路径的路径长度是指该路径上各个边权值的总和
简单路径与回路:若路径上各顶点v1,v2,v3,…,vm均不重复,则称这样的路径为简单路 径。若路径上第一个顶点v1和最后一个顶点vm重合,则称这样的路径为回路或环
子图:设图G = {V, E}和图G1 = {V1,E1},若V1属于V且E1属于E,则称G1是G的子图
连通图:在无向图中,若从顶点v1到顶点v2有路径,则称顶点v1与顶点v2是连通的。如果图中任意一 对顶点都是连通的,则称此图为连通图
强连通图:在有向图中,若在每一对顶点vi和vj之间都存在一条从vi到vj的路径,也存在一条从vj到vi的路径,则称此图是强连通图
生成树:一个连通图的最小连通子图称作该图的生成树。有n个顶点的连通图的生成树有n个顶点和n1条边
二.图的存储结构
1.邻接矩阵
因为节点与节点之间的关系就是连通与否,即为0或者1,因此邻接矩阵(二维数组)即是:先用一 个数组将定点保存,然后采用矩阵来表示节点与节点之间的关系
注意:
- 无向图的邻接矩阵是对称的,第i行(列)元素之和,就是顶点i的度。有向图的邻接矩阵则不一 定是对称的,第i行(列)元素之后就是顶点i的出(入)度
- 如果边带有权值,并且两个节点之间是连通的,上图中的边的关系就用权值代替,如果两个 顶点不通,则使用无穷大代替
- 用邻接矩阵存储图的有点是能够快速知道两个顶点是否连通,缺陷是如果顶点比较多,边比 较少时,矩阵中存储了大量的0成为系数矩阵,比较浪费空间,并且要求两个节点之间的路 径不是很好求
namespace matrix
{template<class V, class W, W MAX_W = INT_MAX, bool Direction = false>class Graph{typedef Graph<V, W, MAX_W, Direction> Self;public://图的创建//1.IO输入->不方便测试,oj中更适合//2.图结构关系写到文件,读取文件//3.手动添加边Graph() = default;Graph(const V* a, size_t n){_vertexs.reserve(n);for (size_t i = 0; i < n; ++i){_vertexs.push_back(a[i]);_indexMap[a[i]] = i;}_matrix.resize(n);for (size_t i = 0; i < _matrix.size(); ++i){_matrix[i].resize(n, MAX_W);}}size_t GetVertexIndex(const V& v){auto it = _indexMap.find(v);if (it != _indexMap.end()){return it->second;}else{//assert(false);throw invalid_argument("顶点不存在");return -1;}}void _AddEdge(size_t srci, size_t dsti, const W& w){_matrix[srci][dsti] = w;//无向图if (Direction == false){_matrix[dsti][srci] = w;}}void AddEdge(const V& src, const V& dst, const W& w){size_t srci = GetVertexIndex(src);size_t dsti = GetVertexIndex(dst);_AddEdge(srci, dsti, w);}void Print(){//顶点for (size_t i = 0; i < _vertexs.size(); ++i){cout << "[" << i << "]" << "->" << _vertexs[i] << endl;}cout << endl;//矩阵//横下标cout << " ";for (size_t i = 0; i < _matrix.size(); ++i){//cout << i << " ";printf("%4d", i);}cout << endl;for (size_t i = 0; i < _matrix.size(); ++i){cout << i << " ";//竖下标for (size_t j = 0; j < _matrix[i].size(); ++j){//cout << _matrix[i][j] << " ";if (_matrix[i][j] == MAX_W){//cout << "* ";printf("%4c", '*');}else{//cout << _matrix[i][j] << " ";printf("%4d", _matrix[i][j]);}}cout << endl;}cout << endl;}private:vector<V> _vertexs; //顶点集合map<V, int> _indexMap; //顶点映射下标vector<vector<W>> _matrix; //邻接矩阵};
}
void TestGraph1()
{Graph<char, int> g("0123", 4);//Graph<char, int, true> g("0123", 4);g.AddEdge('0', '1', 1);g.AddEdge('0', '3', 4);g.AddEdge('1', '3', 2);g.AddEdge('1', '2', 9);g.AddEdge('2', '3', 8);g.AddEdge('2', '1', 5);g.AddEdge('2', '0', 3);g.AddEdge('3', '2', 6);g.Print();
}
邻接矩阵总结:
- 邻接矩阵存储方式非常适合稠密图
- 邻接矩阵O(1)判断两个顶点的连接关系并取到权值
- 相对而言不适合查找一个顶点连接所有边----O(N)
2.邻接表
邻接表:使用数组表示顶点的集合,使用链表表示边的关系
1.无向图邻接表存储
注意:无向图中同一条边在邻接表中出现了两次。如果想知道顶点vi的度,只需要知道顶点vi边链表集合中结点的数目即可
2.有向图邻接表存储
注意:有向图中每条边在邻接表中只出现一次,与顶点vi对应的邻接表所含结点的个数,就是该顶点的出度,也称出度表,要得到vi顶点的入度,必须检测其他所有顶点对应的边链表,看有多少边顶点的dst取值是i
namespace link_table
{template<class W>struct Edge{//int _srci;int _dsti; //目标点的下标W _w; //权值Edge<W>* _next;Edge(int dsti, const W& w) :_dsti(dsti), _w(w), _next(nullptr){ }};template<class V, class W, bool Direction = false>class Graph{typedef Edge<W> Edge;public:Graph(const V* a, size_t n){_vertexs.reserve(n);for (size_t i = 0; i < n; ++i){_vertexs.push_back(a[i]);_indexMap[a[i]] = i;}_tables.resize(n, nullptr);}size_t GetVertexIndex(const V& v){auto it = _indexMap.find(v);if (it != _indexMap.end()){return it->second;}else{//assert(false);throw invalid_argument("顶点不存在");return -1;}}void AddEdge(const V& src, const V& dst, const W& w){size_t srci = GetVertexIndex(src);size_t dsti = GetVertexIndex(dst);Edge* eg = new Edge(dsti, w);eg->_next = _tables[srci];_tables[srci] = eg;if (Direction == false){Edge* eg = new Edge(srci, w);eg->_next = _tables[dsti];_tables[dsti] = eg;}}void Print(){//顶点for (size_t i = 0; i < _vertexs.size(); ++i){cout << "[" << i << "]" << "->" << _vertexs[i] << endl;}cout << endl;for (size_t i = 0; i < _tables.size(); ++i){cout << _vertexs[i] << "[" << i << "]->";Edge* cur = _tables[i];while (cur){cout << "[" << _vertexs[cur->_dsti] << ":" << cur->_dsti << ":" << cur->_w << "]->";cur = cur->_next;}cout << "nullptr" << endl;}}private:vector<V> _vertexs; //顶点集合map<V, int> _indexMap; //顶点映射下标vector<Edge*> _tables; //邻接表};
}
void TestGraph2()
{string a[] = { "张三", "李四", "王五", "赵六" };//Graph<string, int, true> g1(a, 4);Graph<string, int> g1(a, 4);g1.AddEdge("张三", "李四", 100);g1.AddEdge("张三", "王五", 200);g1.AddEdge("王五", "赵六", 30);g1.Print();
}
邻接表总结:
- 适合存储稀疏图
- 适合查找一个顶点连接出去的边
- 不适合确定两个顶点是否相连及权值
三.图的遍历
1.图的广度优先遍历
void BFS(const V& src)
{size_t srci = GetVertexIndex(src);//队列和标记数组queue<int> q;vector<bool> visited(_vertexs.size(), false);q.push(srci);visited[srci] = true;int levelSize = 1;size_t n = _vertexs.size();while (!q.empty()){//一层一层出for (int i = 0; i < levelSize; ++i){int front = q.front();q.pop();cout << front << ":" << _vertexs[front] << " ";//把front顶点的邻接顶点入队列for (size_t i = 0; i < n; ++i){if (_matrix[front][i] != MAX_W){if (visited[i] == false){q.push(i);visited[i] = true;}}}}cout << endl;levelSize = q.size();}cout << endl;
}
void TestBDFS()
{string a[] = { "张三", "李四", "王五", "赵六", "周七" };Graph<string, int> g1(a, sizeof(a) / sizeof(string));g1.AddEdge("张三", "李四", 100);g1.AddEdge("张三", "王五", 200);g1.AddEdge("王五", "赵六", 30);g1.AddEdge("王五", "周七", 30);g1.Print();g1.BFS("张三");
}
2.图的深度优先遍历
void _DFS(size_t srci, vector<bool>& visited)
{cout << srci << ":" << _vertexs[srci] << endl;visited[srci] = true;//找一个和srci相邻的没有访问过的点,去深度遍历for (size_t i = 0; i < _vertexs.size(); ++i){if (_matrix[srci][i] != MAX_W && visited[i] == false){_DFS(i, visited);}}
}void DFS(const V& src)
{size_t srci = GetVertexIndex(src);vector<bool> visited(_vertexs.size(), false);_DFS(srci, visited);
}
void TestBDFS()
{string a[] = { "张三", "李四", "王五", "赵六", "周七" };Graph<string, int> g1(a, sizeof(a) / sizeof(string));g1.AddEdge("张三", "李四", 100);g1.AddEdge("张三", "王五", 200);g1.AddEdge("王五", "赵六", 30);g1.AddEdge("王五", "周七", 30);g1.Print();g1.DFS("张三");
}
四.最小生成树
连通图中的每一棵生成树,都是原图的一个极大无环子图,即:从其中删去任何一条边,生成树就不在连通;反之,在其中引入任何一条新边,都会形成一条回路
若连通图由n个顶点组成,则其生成树必含n个顶点和n-1条边。因此构造最小生成树的准则有三条:
- 只能使用图中的边来构造最小生成树
- 只能使用恰好n-1条边来连接图中的n个顶点
- 选用的n-1条边不能构成回路
1.Kruskal算法
Kruskal算法(克鲁斯卡尔算法)任给一个有n个顶点的连通网络N={V,E}, 首先构造一个由这n个顶点组成、不含任何边的图G={V,NULL},其中每个顶点自成一个连通分量, 其次不断从E中取出权值最小的一条边(若有多条任取其一),若该边的两个顶点来自不同的连通分量,则将此边加入到G中。如此重复,直到所有顶点在同一个连通分量上为止
核心:每次迭代时,选出一条具有最小权值,且两端点不在同一连通分量上的边,加入生成树
struct Edge
{size_t _srci;size_t _dsti;W _w;Edge(size_t srci, size_t dsti, const W& w) :_srci(srci), _dsti(dsti), _w(w){ }bool operator>(const Edge& e) const{return _w > e._w;}
};W Kruskal(Self& minTree)
{size_t n = _vertexs.size();minTree._vertexs = _vertexs;minTree._indexMap = _indexMap;minTree._matrix.resize(n);for (size_t i = 0; i < n; ++i){minTree._matrix[i].resize(n, MAX_W);}priority_queue<Edge, vector<Edge>, greater<Edge>> minque;for (size_t i = 0; i < n; ++i){for (size_t j = 0; j < n; ++j){if (i < j && _matrix[i][j] != MAX_W){minque.push(Edge(i, j, _matrix[i][j]));}}}cout << "Kruskal开始选边:" << endl;//选出n-1条边int size = 0;W totalW = W();UnionFindSet ufs(n);while (!minque.empty()){Edge min = minque.top();minque.pop();if (!ufs.InSet(min._srci, min._dsti)){cout << _vertexs[min._srci] << "->" << _vertexs[min._dsti] << ":" << min._w << endl;minTree._AddEdge(min._srci, min._dsti, min._w);ufs.Union(min._srci, min._dsti);++size;totalW += min._w;}else{cout << "构成环";cout << _vertexs[min._srci] << "->" << _vertexs[min._dsti] << ":" << min._w << endl;}}cout << endl;if (size == n - 1){return totalW;}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();cout << endl;
}
2.Prim算法
Prim算法(普里姆算法)
W Prim(Self& minTree, const W& src)
{size_t srci = GetVertexIndex(src);size_t n = _vertexs.size();minTree._vertexs = _vertexs;minTree._indexMap = _indexMap;minTree._matrix.resize(n);for (size_t i = 0; i < n; ++i){minTree._matrix[i].resize(n, MAX_W);}vector<bool> X(n, false);vector<bool> Y(n, true);X[srci] = true;Y[srci] = false;//从X到Y集合相连接的边中选出值最小的边priority_queue<Edge, vector<Edge>, greater<Edge>> minq;//将srci连接的边添加到队列中for (size_t i = 0; i < n; ++i){if (_matrix[srci][i] != MAX_W){minq.push(Edge(srci, i, _matrix[srci][i]));}}cout << "Prim开始选边:" << endl;int size = 0;W totalW = W();while (!minq.empty()){Edge min = minq.top();minq.pop();if (X[min._dsti]){cout << "构成环";cout << _vertexs[min._srci] << "->" << _vertexs[min._dsti] << ":" << min._w << endl;}else{minTree._AddEdge(min._srci, min._dsti, min._w);cout << _vertexs[min._srci] << "->" << _vertexs[min._dsti] << ":" << min._w << endl;X[min._dsti] = true;Y[min._dsti] = false;++size;totalW += min._w;if (size == n - 1)break;for (size_t i = 0; i < n; ++i){if (_matrix[min._dsti][i] != MAX_W && Y[i]){minq.push(Edge(min._dsti, i, _matrix[min._dsti][i]));}}}}cout << endl;if (size == n - 1){return totalW;}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> pminTree;cout << "Prim:" << g.Prim(pminTree, 'a') << endl;pminTree.Print();
}
五.最短路径
最短路径问题:从在带权有向图G中的某一顶点出发,找出一条通往另一顶点的最短路径,最短也就是沿路径各边的权值总和达到最小
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算法(迪杰斯特拉算法)存在的问题是不支持图中带负权路径,如果带有负权路径,则可能会找不到一些路径的最短路径
//时间复杂度:O(N^2),空间复杂度:O(N)
void Dijkstra(const V& src, vector<W>& dist, vector<int>& pPath)
{size_t srci = GetVertexIndex(src);size_t n = _vertexs.size();dist.resize(n, MAX_W);pPath.resize(n, -1);dist[srci] = 0;pPath[srci] = srci;//已经确定最短路径的顶点集合vector<bool> S(n, false);for (size_t j = 0; j < n; ++j){//选出最短路径顶点且不在S更新其他路径int u = 0;W min = MAX_W;for (size_t i = 0; i < n; ++i){if (S[i] == false && dist[i] < min){u = i;min = dist[i];}}S[u] = true;//松弛更新for (size_t v = 0; v < n; ++v){if (S[v] == false && _matrix[u][v] != MAX_W && dist[u] + _matrix[u][v] < dist[v]){dist[v] = dist[u] + _matrix[u][v];pPath[v] = u;}}}
}
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.PrintShortPath('s', dist, parentPath);
}
2.单源最短路径--Bellman-Ford算法
Dijkstra算法只能用来解决正权图的单源最短路径问题,但有些题目会出现负权图。这时这个算法就不能帮助我们解决问题了,而bellman—ford算法(贝尔曼-福特算法)可以解决负权图的单源最短路径问题。它的优点是可以解决有负权边的单源最短路径问题,而且可以用来判断是否有负权回路。它也有明显的缺点,它的时间复杂度 O(N*E) (N是点数,E是边数)普遍是要高于Dijkstra算法O(N²)的。像这里 如果我们使用邻接矩阵实现,那么遍历所有边的数量的时间复杂度就是O(N^3),这里也可以看出来Bellman-Ford就是一种暴力求解更新
//时间复杂度:O(N^3),空间复杂度:O(N)
bool BellmanFord(const V& src, vector<W>& dist, vector<int>& pPath)
{size_t srci = GetVertexIndex(src);size_t n = _vertexs.size();// vector<W> dist,记录srci-其他顶点最短路径权值数组dist.resize(n, MAX_W);// vector<int> pPath 记录srci-其他顶点最短路径父顶点数组pPath.resize(n, -1);// 先更新srci->srci为最小值dist[srci] = W();for (size_t k = 0; k < n; ++k){bool updata = false;cout << "更新第" << k << "轮:" << endl;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]){updata = true;cout << _vertexs[i] << "->" << _vertexs[j] << ":" << _matrix[i][j] << endl;dist[j] = dist[i] + _matrix[i][j];pPath[j] = i;}}}//如果这个轮次没有更新出最短路径,后续轮次就不需要再走if (updata == 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('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', '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.PrintShortPath('s', dist, parentPath);}else{cout << "存在负权回路" << endl;}
}
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>>& vvpPath)
{size_t n = _vertexs.size();vvDist.resize(n);vvpPath.resize(n);for (size_t i = 0; i < n; ++i){vvDist[i].resize(n, MAX_W);vvpPath[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];vvpPath[i][j] = i;}if (i == j){vvDist[i][j] = 0;}}}//最短路径的更新for (size_t k = 0; k < n; ++k){for (size_t i = 0; i < n; ++i){for (size_t j = 0; j < n; ++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];vvpPath[i][j] = vvpPath[k][j];}}}}
}
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.PrintShortPath(str[i], vvDist[i], vvParentPath[i]);cout << endl;}
}
六.整体实现
1.UnionFindSet.h
#pragma once
#include<vector>class UnionFindSet
{
public:UnionFindSet(size_t n) :_ufs(n, -1){ }int FindRoot(int x){int root = x;while (_ufs[root] >= 0)root = _ufs[root];//路径压缩while (_ufs[x] >= 0){int parent = _ufs[x];_ufs[x] = root;x = parent;}return root;}bool Union(int x1, int x2){int root1 = FindRoot(x1);int root2 = FindRoot(x2);if (root1 == root2)//x1和x2本来就在一个集合中return false;//数据量小的向大的合并if (abs(_ufs[root1]) < abs(_ufs[root2]))swap(root1, root2);_ufs[root1] += _ufs[root2];_ufs[root2] = root1;return true;}bool InSet(int x1, int x2){return FindRoot(x1) == FindRoot(x2);}size_t SetSize(){size_t n = 0;for (auto& e : _ufs){if (e < 0)++n;}return n;}
private:vector<int> _ufs;
};void TestUoionFindSet()
{UnionFindSet ufs(10);ufs.Union(8, 9);ufs.Union(7, 8);ufs.Union(6, 7);ufs.Union(5, 6);ufs.Union(4, 5);
}
2.Graph.h
#pragma once
#include<vector>
#include<string>
#include<map>
#include<queue>
#include<set>
#include<functional>namespace matrix
{template<class V, class W, W MAX_W = INT_MAX, bool Direction = false>class Graph{typedef Graph<V, W, MAX_W, Direction> Self;public://图的创建//1.IO输入->不方便测试,oj中更适合//2.图结构关系写到文件,读取文件//3.手动添加边Graph() = default;Graph(const V* a, size_t n){_vertexs.reserve(n);for (size_t i = 0; i < n; ++i){_vertexs.push_back(a[i]);_indexMap[a[i]] = i;}_matrix.resize(n);for (size_t i = 0; i < _matrix.size(); ++i){_matrix[i].resize(n, MAX_W);}}size_t GetVertexIndex(const V& v){auto it = _indexMap.find(v);if (it != _indexMap.end()){return it->second;}else{//assert(false);throw invalid_argument("顶点不存在");return -1;}}void _AddEdge(size_t srci, size_t dsti, const W& w){_matrix[srci][dsti] = w;//无向图if (Direction == false){_matrix[dsti][srci] = w;}}void AddEdge(const V& src, const V& dst, const W& w){size_t srci = GetVertexIndex(src);size_t dsti = GetVertexIndex(dst);_AddEdge(srci, dsti, w);}void Print(){//顶点for (size_t i = 0; i < _vertexs.size(); ++i){cout << "[" << i << "]" << "->" << _vertexs[i] << endl;}cout << endl;//矩阵//横下标cout << " ";for (size_t i = 0; i < _matrix.size(); ++i){//cout << i << " ";printf("%4d", i);}cout << endl;for (size_t i = 0; i < _matrix.size(); ++i){cout << i << " ";//竖下标for (size_t j = 0; j < _matrix[i].size(); ++j){//cout << _matrix[i][j] << " ";if (_matrix[i][j] == MAX_W){//cout << "* ";printf("%4c", '*');}else{//cout << _matrix[i][j] << " ";printf("%4d", _matrix[i][j]);}}cout << endl;}cout << endl;}void BFS(const V& src){size_t srci = GetVertexIndex(src);//队列和标记数组queue<int> q;vector<bool> visited(_vertexs.size(), false);q.push(srci);visited[srci] = true;int levelSize = 1;size_t n = _vertexs.size();while (!q.empty()){//一层一层出for (int i = 0; i < levelSize; ++i){int front = q.front();q.pop();cout << front << ":" << _vertexs[front] << " ";//把front顶点的邻接顶点入队列for (size_t i = 0; i < n; ++i){if (_matrix[front][i] != MAX_W){if (visited[i] == false){q.push(i);visited[i] = true;}}}}cout << endl;levelSize = q.size();}cout << endl;}void _DFS(size_t srci, vector<bool>& visited){cout << srci << ":" << _vertexs[srci] << endl;visited[srci] = true;//找一个和srci相邻的没有访问过的点,去深度遍历for (size_t i = 0; i < _vertexs.size(); ++i){if (_matrix[srci][i] != MAX_W && visited[i] == false){_DFS(i, visited);}}}void DFS(const V& src){size_t srci = GetVertexIndex(src);vector<bool> visited(_vertexs.size(), false);_DFS(srci, visited);}struct Edge{size_t _srci;size_t _dsti;W _w;Edge(size_t srci, size_t dsti, const W& w) :_srci(srci), _dsti(dsti), _w(w){ }bool operator>(const Edge& e) const{return _w > e._w;}};W Kruskal(Self& minTree){size_t n = _vertexs.size();minTree._vertexs = _vertexs;minTree._indexMap = _indexMap;minTree._matrix.resize(n);for (size_t i = 0; i < n; ++i){minTree._matrix[i].resize(n, MAX_W);}priority_queue<Edge, vector<Edge>, greater<Edge>> minque;for (size_t i = 0; i < n; ++i){for (size_t j = 0; j < n; ++j){if (i < j && _matrix[i][j] != MAX_W){minque.push(Edge(i, j, _matrix[i][j]));}}}cout << "Kruskal开始选边:" << endl;//选出n-1条边int size = 0;W totalW = W();UnionFindSet ufs(n);while (!minque.empty()){Edge min = minque.top();minque.pop();if (!ufs.InSet(min._srci, min._dsti)){cout << _vertexs[min._srci] << "->" << _vertexs[min._dsti] << ":" << min._w << endl;minTree._AddEdge(min._srci, min._dsti, min._w);ufs.Union(min._srci, min._dsti);++size;totalW += min._w;}else{cout << "构成环";cout << _vertexs[min._srci] << "->" << _vertexs[min._dsti] << ":" << min._w << endl;}}cout << endl;if (size == n - 1){return totalW;}else{return W();}}W Prim(Self& minTree, const W& src){size_t srci = GetVertexIndex(src);size_t n = _vertexs.size();minTree._vertexs = _vertexs;minTree._indexMap = _indexMap;minTree._matrix.resize(n);for (size_t i = 0; i < n; ++i){minTree._matrix[i].resize(n, MAX_W);}vector<bool> X(n, false);vector<bool> Y(n, true);X[srci] = true;Y[srci] = false;//从X到Y集合相连接的边中选出值最小的边priority_queue<Edge, vector<Edge>, greater<Edge>> minq;//将srci连接的边添加到队列中for (size_t i = 0; i < n; ++i){if (_matrix[srci][i] != MAX_W){minq.push(Edge(srci, i, _matrix[srci][i]));}}cout << "Prim开始选边:" << endl;int size = 0;W totalW = W();while (!minq.empty()){Edge min = minq.top();minq.pop();if (X[min._dsti]){cout << "构成环";cout << _vertexs[min._srci] << "->" << _vertexs[min._dsti] << ":" << min._w << endl;}else{minTree._AddEdge(min._srci, min._dsti, min._w);cout << _vertexs[min._srci] << "->" << _vertexs[min._dsti] << ":" << min._w << endl;X[min._dsti] = true;Y[min._dsti] = false;++size;totalW += min._w;if (size == n - 1)break;for (size_t i = 0; i < n; ++i){if (_matrix[min._dsti][i] != MAX_W && Y[i]){minq.push(Edge(min._dsti, i, _matrix[min._dsti][i]));}}}}cout << endl;if (size == n - 1){return totalW;}else{return W();}}void PrintShortPath(const V& src, const vector<W>& dist, const vector<int>& pPath){size_t srci = GetVertexIndex(src);size_t n = _vertexs.size();for (size_t i = 0; i < n; ++i){if (i != srci){//找出i顶点的路径vector<int> path;size_t parenti = i;while (parenti != srci){path.push_back(parenti);parenti = pPath[parenti];}path.push_back(srci);reverse(path.begin(), path.end());for (auto index : path){cout << _vertexs[index] << "->";}cout << "权值和: " << dist[i] << endl;}}}//时间复杂度:O(N^2),空间复杂度:O(N)void Dijkstra(const V& src, vector<W>& dist, vector<int>& pPath){size_t srci = GetVertexIndex(src);size_t n = _vertexs.size();dist.resize(n, MAX_W);pPath.resize(n, -1);dist[srci] = 0;pPath[srci] = srci;//已经确定最短路径的顶点集合vector<bool> S(n, false);for (size_t j = 0; j < n; ++j){//选出最短路径顶点且不在S更新其他路径int u = 0;W min = MAX_W;for (size_t i = 0; i < n; ++i){if (S[i] == false && dist[i] < min){u = i;min = dist[i];}}S[u] = true;//松弛更新for (size_t v = 0; v < n; ++v){if (S[v] == false && _matrix[u][v] != MAX_W && dist[u] + _matrix[u][v] < dist[v]){dist[v] = dist[u] + _matrix[u][v];pPath[v] = u;}}}}//时间复杂度:O(N^3),空间复杂度:O(N)bool BellmanFord(const V& src, vector<W>& dist, vector<int>& pPath){size_t srci = GetVertexIndex(src);size_t n = _vertexs.size();// vector<W> dist,记录srci-其他顶点最短路径权值数组dist.resize(n, MAX_W);// vector<int> pPath 记录srci-其他顶点最短路径父顶点数组pPath.resize(n, -1);// 先更新srci->srci为最小值dist[srci] = W();for (size_t k = 0; k < n; ++k){bool updata = false;cout << "更新第" << k << "轮:" << endl;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]){updata = true;cout << _vertexs[i] << "->" << _vertexs[j] << ":" << _matrix[i][j] << endl;dist[j] = dist[i] + _matrix[i][j];pPath[j] = i;}}}//如果这个轮次没有更新出最短路径,后续轮次就不需要再走if (updata == 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 FloydWarShall(vector<vector<W>>& vvDist, vector<vector<int>>& vvpPath){size_t n = _vertexs.size();vvDist.resize(n);vvpPath.resize(n);for (size_t i = 0; i < n; ++i){vvDist[i].resize(n, MAX_W);vvpPath[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];vvpPath[i][j] = i;}if (i == j){vvDist[i][j] = 0;}}}//最短路径的更新for (size_t k = 0; k < n; ++k){for (size_t i = 0; i < n; ++i){for (size_t j = 0; j < n; ++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];vvpPath[i][j] = vvpPath[k][j];}}}}}private:vector<V> _vertexs; //顶点集合map<V, int> _indexMap; //顶点映射下标vector<vector<W>> _matrix; //邻接矩阵};void TestGraph1(){Graph<char, int> g("0123", 4);//Graph<char, int, true> g("0123", 4);g.AddEdge('0', '1', 1);g.AddEdge('0', '3', 4);g.AddEdge('1', '3', 2);g.AddEdge('1', '2', 9);g.AddEdge('2', '3', 8);g.AddEdge('2', '1', 5);g.AddEdge('2', '0', 3);g.AddEdge('3', '2', 6);g.Print();}void TestBDFS(){string a[] = { "张三", "李四", "王五", "赵六", "周七" };Graph<string, int> g1(a, sizeof(a) / sizeof(string));g1.AddEdge("张三", "李四", 100);g1.AddEdge("张三", "王五", 200);g1.AddEdge("王五", "赵六", 30);g1.AddEdge("王五", "周七", 30);g1.Print();g1.BFS("张三");g1.DFS("张三");}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();cout << endl;Graph<char, int> pminTree;cout << "Prim:" << g.Prim(pminTree, 'a') << endl;pminTree.Print();}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.PrintShortPath('s', dist, parentPath);}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('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', '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.PrintShortPath('s', dist, parentPath);}else{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.PrintShortPath(str[i], vvDist[i], vvParentPath[i]);cout << endl;}}
}namespace link_table
{template<class W>struct Edge{//int _srci;int _dsti; //目标点的下标W _w; //权值Edge<W>* _next;Edge(int dsti, const W& w) :_dsti(dsti), _w(w), _next(nullptr){ }};template<class V, class W, bool Direction = false>class Graph{typedef Edge<W> Edge;public:Graph(const V* a, size_t n){_vertexs.reserve(n);for (size_t i = 0; i < n; ++i){_vertexs.push_back(a[i]);_indexMap[a[i]] = i;}_tables.resize(n, nullptr);}size_t GetVertexIndex(const V& v){auto it = _indexMap.find(v);if (it != _indexMap.end()){return it->second;}else{//assert(false);throw invalid_argument("顶点不存在");return -1;}}void AddEdge(const V& src, const V& dst, const W& w){size_t srci = GetVertexIndex(src);size_t dsti = GetVertexIndex(dst);Edge* eg = new Edge(dsti, w);eg->_next = _tables[srci];_tables[srci] = eg;if (Direction == false){Edge* eg = new Edge(srci, w);eg->_next = _tables[dsti];_tables[dsti] = eg;}}void Print(){//顶点for (size_t i = 0; i < _vertexs.size(); ++i){cout << "[" << i << "]" << "->" << _vertexs[i] << endl;}cout << endl;for (size_t i = 0; i < _tables.size(); ++i){cout << _vertexs[i] << "[" << i << "]->";Edge* cur = _tables[i];while (cur){cout << "[" << _vertexs[cur->_dsti] << ":" << cur->_dsti << ":" << cur->_w << "]->";cur = cur->_next;}cout << "nullptr" << endl;}}private:vector<V> _vertexs; //顶点集合map<V, int> _indexMap; //顶点映射下标vector<Edge*> _tables; //邻接表};void TestGraph2(){string a[] = { "张三", "李四", "王五", "赵六" };//Graph<string, int, true> g1(a, 4);Graph<string, int> g1(a, 4);g1.AddEdge("张三", "李四", 100);g1.AddEdge("张三", "王五", 200);g1.AddEdge("王五", "赵六", 30);g1.Print();}
}
3.test.cpp
#include<iostream>
using namespace std;
#include"UnionFindSet.h"
#include"Graph.h"int main()
{//TestUoionFindSet();//matrix::TestGraph1();//matrix::TestBDFS();//matrix::TestGraphMinTree();//matrix::TestGraphDijkstra();//matrix::TestGraphBellmanFord();matrix::TestFloydWarShall();//link_table::TestGraph2();return 0;
}
相关文章:
数据结构——图(遍历,最小生成树,最短路径)
目录 一.图的基本概念 二.图的存储结构 1.邻接矩阵 2.邻接表 三.图的遍历 1.图的广度优先遍历 2.图的深度优先遍历 四.最小生成树 1.Kruskal算法 2.Prim算法 五.最短路径 1.单源最短路径--Dijkstra算法 2.单源最短路径--Bellman-Ford算法 3.多源最短路径--Floyd-…...
Android APK打包流程
文章目录 前言1. 资源打包(通过 AAPT)2. 处理 AIDL 文件3. Java 源代码编译4. Dex 转换5. APK 打包6. APK 签名7. APK 对齐总结 前言 Android APK 打包过程包括多个关键步骤,每个环节都提供了不同的操作机会。开发者可以在这些步骤中进行自定…...
《Liunx系统》之基础操作命令
目录 简介 1. 文件和目录操作 1.1 查看当前目录 1.2 列出目录内容 1.3 切换目录 1.4 创建目录 1.5 删除目录 1.6 创建文件 1.7 删除文件 2. 文件内容操作 2.1 查看文件内容 2.2 搜索文件内容 2.3 复制文件 2.4 移动文件 3. 系统信息和进程管理 3.1 查看系统信息…...
linux 编译、交叉编译 opencv+ffmpeg 为动态库
文章目录 x86 编译先编译 ffmpeg再编译 opencv验证 opencv 的安装是否链接了 ffmpeg 交叉编译(目标系统 armv8 即 arrch64)准备交叉编译工具链(arm 版 gcc、g)先编译 ffmpeg再编译 opencv验证 opencv 的安装是否链接了 ffmpeg 参考…...
Robust Univariate Mean Estimation算法简介
Robust Univariate Mean Estimation 是一种统计算法,主要用于在单变量场景中估计样本的均值,同时对异常值(outliers)具有鲁棒性。传统的均值估计使用样本的算术平均值,但它对异常值高度敏感。为了缓解这个问题…...
区块链智能合约( solidity) 安全编程
引言:本文由天玄链开源开发者提供,欢迎报名公益天玄链训练营 https://blockchain.163.com/trainingCamp 一、重入和竞态 重入和竞态在solidity 编程安全中会多次提及,历史上也造成了重大的损失。 1.1 问题分析 竞态的描述不严格…...
断点续传【授权访问】
本文介绍如何使用STS以及签名URL临时授权访问OSS资源。 授权方式 OSS支持多种授权方式进行客户端授权,以下提供了三种不同授权方式的简要说明,并提供了使用相应授权方式实现简单上传的代码示例,您可以根据使用场景对认证和授权的要求&#…...
中华国际游艇会出席第六届地博会助世界酒中国菜地理标志走向全球
中华国际游艇会积极参与第六届知交会暨地博会,助力世界酒中国菜地理标志产品走向全球 第六届粤港澳大湾区知识产权交易博览会暨国际地理标志产品交易博览会于2024年12月9日至11日在中新广州知识城盛大举行。此次盛会汇聚了众多行业精英和企业代表,共同探…...
Python爬虫——HTML中Xpath定位
Xpath是一种路径查询语言。利用一个路径表达式从html文档中找到我们需要的数据位置,进而将其写入到本地或者数据库中。 学习Xpath爬虫,我们首先学习一下python中lxml库 关于库 lxml 终端下载Xpath需要用到的模块 pip install lxml 关于HTML 超文本标…...
Ubuntu防火墙管理(六)——ARP防火墙过滤防御自定义系统服务
起因 在ubuntu24.04中检查arp表,输出异常 arp -a? (10.162.242.142) 位于 74:3a:20:b9:e8:02 [ether] 在 wlp2s0 ? (10.162.0.1) 位于 在 wlp2s0 ubuntu环境中,这是否表示ARP攻击,本地网关为10.162.0.1,可用arptables防御吗&a…...
Halcon_数据类型_ROI_仿射变换_投影变换
文章目录 算子快捷键一、Halcon数据类型Iconic (图标)Control (控制)Tuple (数组) 二、ROI(区域)1.代码创建ROI2.手动创建ROI 三、图形预处理1.图像的变换与矫正平移 -hom_mat2d_translate旋转缩放-HomMat2D:输入的仿射…...
java+ssm+mysql高校学籍管理系统
项目介绍: 使用javassmmysql开发的高校学籍管理系统,系统包含超级管理员,系统管理员、教师、学生角色,功能如下: 超级管理员:管理员管理(可以新增管理员);专业管理&…...
多表设计-一对多一对多-外键
一.多表设计概述: 二.一对多: 1.需求: 根据 页面原型 及 需求文档,完成部门及员工模块的表结构设计 -->部门和员工就是一对多,因为一个部门下会有多个员工,但一个员工只归属一个部门 2.页面原型&…...
Scala的正则表达式(1)
package hfd //正则表达式的应用场景 //1.查找 findAllin //2.验证 matches //3.替换//验证用户名十分合法 //规则: //1.长度在6-12之间 //2.不能数字开头 //3.只能包含数字,大小写字母,下划线 object Test36 {def main(args: Array[String])…...
uniapp扭蛋机组件
做了一个uniapp的扭蛋机组件,可以前往下载地址下载 仅测试了vue2、3、h5页面微信小程序,理论支持全平台 使用方法简单,具有待机动效、抽奖中动效、掉落奖品动效,可以替换奖品图片,足以满足大部分抽奖页面需求。 示例图…...
并发背后的技术与原理
一个Java Web项目能够同时支持多个用户请求,而不会导致数据混乱,这主要得益于Java平台的多线程处理机制、Web容器的请求处理模型以及良好的编程实践。 Java的多线程处理: Java是一种支持多线程的编程语言。在Java Web应用中,每个用…...
HarmonyOS(63) ArkUI 自定义占位组件NodeContainer
NodeContainer 1、前言2、NodeContainer和NodeController3、示例代码3.1、创建@Builder3.2、 创建NodeController3.3、 使用NodeCtroller4、NodeContainer的作用5、FrameNode简介6、BuilderNode简介7、参考资料1、前言 在HarmonyOS(62) ArkUI @Reusable组件复用原理讲了组件复…...
机器学习实战学习笔记:前言与准备
个人学习介绍 该专栏作为《机器学习实战(原书第三版)》的读书笔记,涉及对书本内容的理解和书本内容原有的示例和部分原文。略懂一点点python语法编程环境选用python 3.12.1 ,IDE为 DataSpell (主要)&#…...
Linux应用开发————多线程的互斥与同步——同步
同步和互斥 互斥:是指某一资源同时只允许一个访问者对其进行访问,具有唯一性和排它性。但互斥无法限制访问者对资源的访问顺序,即访问是无序的。 同步:是指在互斥的基础上(大多数情况),通过其它机制实现访问者对资源的有序访问。 条件变量机制…...
【人工智能】深度解剖利用人工智能MSA模型
目录 情感分析的应用一、概述二、研究背景三、主要贡献四、模型结构和代码五、数据集介绍六、性能展示七、复现过程 情感分析的应用 近年来社交媒体的空前发展以及配备高质量摄像头的智能手机的出现,我们见证了多模态数据的爆炸性增长,如电影、短视频等…...
Java 环境配置 + IntelliJ IDEA 使用指南
文章目录 一、Java 程序的运行必须经过3 个步骤:编写、编译、运行(1)Java 和 JavaScript 的区别(2)JDK、JRE、JVM 的关系(3)是否需要 Maven? 二、软件下载2.1、JDK下载与安装 —— 是…...
兴业周报|楼市新政效果显著~单周成交破5亿~
香山清琴山庄丙19号(独栋别墅) 稀缺房源:标的物为京城少有的独栋别墅,在连续的 “限墅令” 及相关容积L限制政策下,市场上独栋别墅的新增供应不断减少。 环境优美:香山清琴山庄依山而筑,错落有…...
学习笔记063——通过使用 aspose-words 将 Word 转 PDF 时,遇到的字体改变以及乱码问题
文章目录 1、问题描述:2、解决方法: 1、问题描述: Java项目中,有个需要将word转pdf的需求。本人通过使用aspose-words来转换的。在Windows中,转换是完全正常的。但是当部署到服务器时,会出现转换生成的pdf…...
人工智能导论学习笔记
目录 一、概要 二、人工智能基础知识 智能 人工智能 人工智能三要素 人工智能发展历程 人工智能的三次浪潮 人工智能行业发展现状 人工智能技术水平现状 人工智能技术层级 人工智能应用开发周期 机器学习的流程 一、概要 《人工智能导论(通识版)》张大斌 田恒义 许…...
FCOS: Fully Convolutional One-Stage Object Detection——全卷积一阶段目标检测
FCOS(Fully Convolutional One-Stage Object Detector)是一种全卷积的单阶段目标检测器,旨在通过消除锚点(anchor)的使用,简化目标检测的流程。以下是FCOS的主要特点和组成部分: 1. 无锚点设计…...
《Java核心技术I》映射条目的原子更新
映射条目的原子更新 ConcurrentHashMap只有部分原子更新。 JavaAPI提供了一些新方法,例如:compute方法可以提供一个键和一个计算新值的函数。 map.compute(word,(k,v)->v null ? 1 : v1) 注释:ConcurrentHashMap中不允许有null值。很…...
微信小程序介绍-以及写项目流程(重要)
前言:本篇文章介绍微信小程序以及项目介绍: 文章介绍:介绍了微信小程序常用的指令、组件、api。tips:最好按照官方文档来进行学习,大致可以我的目录来学习,对于写项目是没有问题的 微信小程序官方文档https…...
241207-通过Docker部署Wiki.JS并设置ElasticSearch进行中文搜索
A. 最终效果 B. 配置文件 version: "3" services:wiki:image: ghcr.io/requarks/wiki:2container_name: wikijsports:- "3000:3000"volumes:- /home/lgk/Projects/WikiJS/config:/configenvironment:- DB_TYPEpostgres- DB_HOSTdatabase- DB_PORT5432- DB…...
yum 离线软件安装
适用范围 支持YUM软件管理的操作系统: 银河麒麟 服务器操作系统V10统信服务器操作系统V20CentOS 系列 准备 准备一台可以连接互联网并且与离线安装的操作系统相同版本的操作系统,包括指令集类型相同。 安装下载工具 查询是否已经安装下载工具 yum…...
【jvm】垃圾回收的优点和原理
目录 1. 说明2. 优点3. 原理3.1 发现无用对象3.2 回收无用对象所占用的内存 4. 回收算法4.1 标记-清除算法4.2 复制算法4.3 标记-整理算法4.4 分代收集算法 1. 说明 1.JVM(Java虚拟机)垃圾回收是Java语言的一大特性,它自动管理内存ÿ…...
LeetCode322. 零钱兑换(2024冬季每日一题 28)
给你一个整数数组 coins ,表示不同面额的硬币;以及一个整数 amount ,表示总金额。 计算并返回可以凑成总金额所需的 最少的硬币个数 。如果没有任何一种硬币组合能组成总金额,返回 -1 。 你可以认为每种硬币的数量是无限的。 示…...
LearnOpenGL学习(高级OpenGL --> 帧缓冲,立方体贴图,高级数据)
完整代码见:zaizai77/Cherno-OpenGL: OpenGL 小白学习之路 帧缓冲 帧缓冲(FrameBuffer)是所有屏幕缓冲(包括颜色缓冲,深度缓冲,模板缓冲)的集合。它被存储在GPU内存中,我们可以定义…...
mysql笔记——索引
索引 InnoDB采用了B树索引结构。 相比于二叉树,层级更少,搜索效率高。 B树中叶子节点和非叶节点都会存储数据,导致段页式存储中一页存储的键值减少,指针也会减少,要同样保存大量数据,只能增加树的高度&a…...
React第十五节useReducer使用详解差异
useReducer() 的用法注意事项 1、 概述: useReducer() 常用于管理复杂的状态更新逻辑,特别是在状态更新依赖于多个条件或动作时,useReducer 提供了一种更加结构化和可维护的方式来处理状态。可以将更新函数写在组件外面 它与 useState() 相…...
高效备考 Oracle 19c OCM 的建议
满足报考条件 考生需要先获得 19c OCP(Oracle Certified Professional)认证,并完成 Oracle 官方认可的 OCP 培训课程 制定学习计划 规划学习时间:根据考试时间和自己的日常安排,制定详细的学习计划,合理分配…...
01-Chromedriver下载与配置(mac)
下载地址: 这里我用的最后一个,根据自己chrome浏览器选择相应的版本号即可 ChromeDriver官网下载地址:https://sites.google.com/chromium.org/driver/downloads ChromeDriver官网最新版下载地址:https://googlechromelabs.git…...
网站流量和用户行为深度分析
关于网站流量数据集的探索 import pandas as pd import plotly.express as px import plotly.graph_objects as go import plotly.subplots as sp import matplotlib.pyplot as plt import seaborn as sns file_path /home/mw/input/webs4651/website_wata.csv data pd.rea…...
centOS7如何配置阿里云或者腾讯云yum源
众所周知,CentOS很多版本目前已经不再维护了,原本的在线yum源已经无法使用,所以需要我们配置其他的yum源。目前腾讯云或者阿里云的yum源都可以正常使用,所以本文教大家如何配置阿里云/腾讯云在线yum源。 阿里云yum源配置…...
洗鞋小程序(源码+文档+部署+讲解)
本文将深入解析“洗鞋小程序”的项目,探究其架构、功能以及技术栈,并分享获取完整源码的途径。 系统概述 为洗鞋提供服务,包含小程序和管理端。 本项目名称为洗鞋小程序,是一个基于小程序的在线洗鞋平台。该系统提供下单、订单管…...
MySQL|通过JSON_UNQUOTE实现MySQL中JSON数据的干净提取
文章目录 语法使用示例注意事项 JSON_UNQUOTE() 是 MySQL 中用于处理 JSON 数据类型的一个函数。它的主要作用是从 JSON 字符串中移除最外层的引号,这对于从 JSON 对象或数组中提取字符串值特别有用。 语法 JSON_UNQUOTE(json_string)json_string: 这是你想要去掉引…...
动态规划part01
文章参考来源代码随想录 理论基础 适用范围: 如果某一问题有很多重叠子问题,使用动态规划是最有效的。 与贪心算法的区别: 动态规划中每一个状态一定是由上一个状态推导出来的,这一点就区分于贪心,贪心没有状态推…...
生活大爆炸版石头剪刀布(洛谷P1328)
生活大爆炸版石头剪刀布(洛谷P1328) [NOIP2014 提高组] 前言: 由于洛谷发布题解有限制,所以在CSDN上发布洛谷题解。 所有题解均是Java语言, 但是思路是相同的 每篇都是刷题日常,尽量讲清楚算法逻辑。 希望有问题还请大佬们指导! …...
【GOOD】DeGEM
ICLR2025 under review 看到不错的想法,学习一下 Decoupled Graph Energy-based Model for Node Out-of-Distribution Detection on Heterophilic Graphs 🐱🐶图上的OOD检测的工作是比较少的,相比于图像数据,图结构数…...
jenkins邮件的配置详解
Jenkins邮件的配置涉及多个步骤和细节,以下是详细的配置指南: 一、前期准备 确定邮件服务:明确Jenkins将要使用的邮件服务,如QQ邮箱、163邮箱、公司邮箱(基于Microsoft 365或Exchange Server)等。获取SMTP配置信息:根据邮件服务类型,获取相应的SMTP服务器地址、端口号…...
flask-socketio相关总结
flask-socketio是一个为flask应用程序添加的实时双向通信功能的扩展库,有了这个库,就可以在flask应用中应用websocket协议,帮助flask实现低延迟、双向的客户端、服务端通信。客户端通过任何SocketIO官方库,都能与服务器建立长连接…...
每日一题 LCR 097. 不同的子序列
LCR 097. 不同的子序列 使用动态规划就可以解决,重点是知道 动态规划的状态是如何转移的 class Solution { public:int numDistinct(string s, string t) {int ns s.size();int nt t.size();vector<vector<long>> dp(ns1,vector<long>(nt1,0)…...
合并区间C和C++的区别、布尔、整型、浮点、指针类型和0做比较、malloc、calloc、realloc的区别
56. 合并区间 class Solution { public:vector<vector<int>> merge(vector<vector<int>>& intervals) {//先按照每个区间的左元素排序,这样每个区间的左边界就固定了,所以之后考虑相邻的//区间是否是相交的就行 类似与栈的…...
设计模式-外观模式
背景 有一个家庭影院,有DVD播放器,投影仪,屏幕,音响,爆米花机,每一个设备都有一个遥控器。 传统思路: 创建一个客户端类,在这个类中创建所有设备的相关对象(遥控器&am…...
嵌入式驱动开发详解14(SPI驱动架构实现)
文章目录 前言SPI简介SPI介绍SPI工作模式SPI特点 驱动开发驱动架构SPI控制器驱动SPI设备驱动SPI 设备和驱动匹配过程SPI其他相关API函数 参考文献 前言 SPI 是很常用的串行通信协议,可以通过 SPI 来连接众多的传感器,相比 I2C 接 口,SPI 接口…...
【保姆级系列:思科模拟器安装下载汉化教程大全】
文章目录 概述Packet Tracer下载Packet Tracer安装Packet Tracer使用EVE-NG下载:EVE-NG安装:EVE-NG使用: 概述 思科在网络界的地位是众所周知的。如果说在中美科技战中,华为代表CHN,那么思科就代表US,依然…...