高阶数据结构学习 —— 图(1)

文章目录

  • 1、并查集
  • 2、了解图
  • 3、邻接矩阵
  • 4、压缩路径
  • 5、基本概念
  • 6、邻接表


1、并查集

并查集是一个森林,是由多棵树组成的。这相当于整套数据,分成多个集合。查找有交集的集合们,会把它们合并起来,所以叫并查集。

一开始拿到的是一堆数据,我们给它们编号,然后放到一个个集合中。如何做编号?如何做集合?我们先建立两个文件,Graph.cpp和UnionFindSet.h。

#include <iostream>
using namespace std;int main()
{string a[] = { "张三", "李四", "王五", "赵六" };UnionFindSet<string> ufs(a, 4);return 0;
}
#pragma once
#include <vector>
#include <map>template <class T>
class UnionFindSet
{
public:UnionFindSet(const T* a, size_t n){}
private:vector<T> _a;};

把接收到的a保存到_a,这样每个人编号就是下标了,但这不是好办法。

class UnionFindSet
{
public:UnionFindSet(const T* a, size_t n)//建立映射关系{for (size_t i = 0; i < n; ++i){_a.push_back(a[i]);_indexMap[a[i]] = i;}}
private:vector<T> _a;//下标就是编号,编号找人map<T, int> _indexMap//但通过人找编号更合理,这样不需要每次都得遍历才能找到一个人
};

通过这样的代码可以简单理解一下编号是怎么回事,但代码不是这样写的。接下来看看集合。集合本质就是树,选择一个根节点,然后几个编号作为它的子节点,但该选哪几个编号作为一个树呢?这里的用的树是双亲表示法,每个孩子都存储它的双亲节点。还是用数组来表示,最一开始都初始化为-1,意思就是每个编号单独成一个集合

在这里插入图片描述
构成集合后会是这样
在这里插入图片描述
0和678作为一棵树,0作为根,6是孩子节点,把6的-1加过去,0位置变成-2,6位置存上双亲节点0,然后是7和8。这样的规则下,数组的下标就是集合中元素的编号,比如arr[0],就是编号为0的数据;每个位置存储的数字,如果为负数,说明当前编号是根,数字的绝对值就是这棵树的节点数量;如果数字为非负数,那么当前位置存储的就是这个编号的双亲编号。比如arr[6] = 0,6这个编号的双亲编号就是0,arr[0] = -4,0编号是根,节点数量是4。

假设4和8建立了联系,这两个集合要合并。这里要如何合并?根节点是谁?根节点是谁都可以,既可以是0也可以是1,选一个就好。假如选0做根节点,那么0就要加上1位置的数,1的位置存储0,4和9不变。

在这里插入图片描述

下面是完善后的并查集类

#pragma once
#include <vector>
#include <iostream>
using namespace std;class UnionFindSet
{
public:UnionFindSet(size_t n)//给编号数量:_ufs(n, -1){}void Union(int x1, int x2)//给两个编号,合并{//得先判断这两个编号能不能合并int root1 = FindRoot(x1);int root2 = FindRoot(x2);//本身在一个集合,就不需要合并if (root1 == root2) return;//可以合并的话就自己做规定怎么合并,这里就让小的编号做根if (root1 > root2) swap(root1, root2);_ufs[root1] += _ufs[root2];_ufs[root2] = root1;}int FindRoot(int x)//找一个编号的根{int parent = x;while (_ufs[parent] >= 0)//此时说明它本身不是根,就看存的值,往上找父节点,因为父节点可能不是根,所以需要while{parent = _ufs[parent];//对照上面的图来看}return parent;//小于0说明自己就是根,直接返回}bool InSameSet(int x1, int x2)//看看编号在不在一个集合里{return FindRoot(x1) == FindRoot(x2);}size_t SetSize(){size_t size = 0;for (size_t i = 0; i < _ufs.size(); i++){if (_ufs[i] < 0) ++size;}return size;}
private:vector<int> _ufs;
};

看一道题,剑指offer的。

LCR 116. 省份数量

在这里插入图片描述

把我们写好的并查集类拿过来使用

class UnionFindSet
{
public:UnionFindSet(size_t n):_ufs(n, -1){}void Union(int x1, int x2){int root1 = FindRoot(x1);int root2 = FindRoot(x2);if (root1 == root2) return;if (root1 > root2) swap(root1, root2);_ufs[root1] += _ufs[root2];_ufs[root2] = root1;}int FindRoot(int x){int parent = x;while (_ufs[parent] >= 0){parent = _ufs[parent];}return parent;}bool InSameSet(int x1, int x2){return FindRoot(x1) == FindRoot(x2);}size_t SetSize(){size_t size = 0;for (size_t i = 0; i < _ufs.size(); i++){if (_ufs[i] < 0) ++size;}return size;}
private:vector<int> _ufs;
};class Solution {
public:int findCircleNum(vector<vector<int>>& isConnected) {UnionFindSet ufs(isConnected.size());for(size_t i = 0; i < isConnected.size(); ++i){for(size_t j = 0; j < isConnected[i].size(); ++j){if(isConnected[i][j] == 1)//是1说明两个城市相连,那就可以进入一个集合,所以用Unionufs.Union(i, j);}}return ufs.SetSize();}
};

如果不用并查集,应该怎么写?那就现场写几个用到的函数

    int findCircleNum(vector<vector<int>>& isConnected) {vector<int> ufs(isConnected.size(), -1);auto findRoot = [&ufs](int x){while(ufs[x] >= 0)x = ufs[x];return x;};for(size_t i = 0; i < isConnected.size(); ++i){for(size_t j = 0; j < isConnected[i].size(); ++j){if(isConnected[i][j] == 1){//合并集合int root1 = findRoot(i);int root2 = findRoot(j);if(root1 != root2){ufs[root1] += ufs[root2];ufs[root2] = root1;}}}}int count = 0;for(auto e : ufs){if(e < 0) ++count;}return count;}

压缩路径:
当数据量比较大时会出现一棵树下来连接了很多层子树,这时候如果每次都要一点点往上找根,效率就比较低。这时候就要用压缩路径,压缩路径在findRoot函数中使用。查找一次比较深的节点的根后,我们发现需要减少一下层数,这个可以用一个变量来记录循环次数,当大于某个数时就得做压缩了。假设查找的是x节点的根,查找完后,就把x节点位置存储的值变成根节点的编号,这样就是把这个节点直接调为根节点的孩子,并且把x节点原父节点也调成根节点的孩子,但x节点的原兄弟节点不动。如果从根到x有3层,调整完后就变成了2层,等到再查找x节点原兄弟节点时,再把这个兄弟节点调成根的孩子,而这个兄弟节点的父节点就还是根的孩子。通过不断地调整,要做到的结果就是原本3层,变成1层,都变成根的孩子节点,这些节点的父节点都是根。

练题时,如果超时,估计就要用压缩路径了。

另一个题

990. 等式方程的可满足性

在这里插入图片描述

我们可以先遍历一遍,为等号的都放到一个集合里,再去看不相等的符号的,如果不相等的两个在一个集合里,那就false,不是那就继续。

    bool equationsPossible(vector<string>& equations) {vector<int> ufs(26, -1);auto findRoot = [&ufs](int x){while(ufs[x] >= 0)x = ufs[x];return x;};//第一次遍历,把相等的值放到一个集合中for(auto& str : equations){if(str[1] == '='){int root1 = findRoot(str[0] - 'a');int root2 = findRoot(str[3] - 'a');if(root1 != root2){ufs[root1] += ufs[root2];ufs[root2] = root1;}}}//第二遍,判断不相等的在不在一个集合,在就falsefor(auto& str : equations){if(str[1] == '!'){int root1 = findRoot(str[0] - 'a');int root2 = findRoot(str[3] - 'a');if(root1 == root2) return false;}}return true;}

2、了解图

图是一种数据结构,图由顶点集合和边的集合构成,通常都会用G(V, E)来表示,V就是顶点集合,E就是边集合,所以G(V, E)的意思就是图由顶点(集合)和边(集合)组成。常见的图

在这里插入图片描述

树是一种特殊的图,无环,连通。树关注节点(顶点)中存的值,图关注的是顶点和边的权值。边如何表示?边连接两个点,还有权值,所以它是一个二维数组,e[i][j]就表示顶点i和j之间的边,这个位置的值就是权值。图分为有向图和无向图,也就是有方向箭头和没有方向箭头,无向图的两个顶点可以互相连接,有向图只能按照既定方向去走。

边的含义可以有很多,它不止是表示距离,也可以表示两个城市(顶点)之前坐高铁的时间,两个顶点的关系等。针对图有一些种类的问题,深度/广度优先,最小生成树,最短路径等,这些后面再写。

微信,qq是经典的无向图,无向图通常用来表示强社交关系,微博,bzhan等就是有向图,是一种弱社交关系,媒体社交。qq中有亲密度,亲密度就是权值。短视频的推荐算法也和图有关。

图有两个结构,邻接矩阵和邻接表。邻接矩阵存边的集合。每个顶点先假设下标,比如ABCD就是0123,这样一个二维数组的邻接矩阵,每个位置的值就表示权值,[i][j],ij相等,那就存0,表示是同一顶点,ij不同,如果之间没有关系,那就填上无穷这样的数字,也可以填别的,有向图可能可以从2到3,但不能从3到2,那么[3][2]就会是无穷。如果权值表示两个点之间的关系,那么就填为1或0。当然,规则由自己决定。

邻接矩阵存储方式非常适合稠密图,因为无论稠密还是稀疏,邻接矩阵都是每个位置都有值;邻接矩阵可以判断两个顶点的连接关系,并取到权值。但相对而言,邻接矩阵不适合查找一个顶点连接的所有边,它需要O(N),比如找顶点4连接的点,它就得遍历一下,每个边都得看一下那个位置的值。

邻接表

在这里插入图片描述

它是一个指针数组,把自己连接的所有点都挂在下面。它也能表示有无向。

邻接表适合存储稀疏图,适合查找一个顶点连接出去的边,因为只需要遍历连接的顶点的数,但不适合确定两个顶点是否相连和权值。

有向图情况下,会有入边表和出边表,就是A挂着的顶点B,入边表意思就是从B到A,出边表意思就是从A到B。一般只需要一个出边表即可。

邻接矩阵和邻接表是相辅相成,各有缺点的互补结构。

3、邻接矩阵

我们创建Graph.h文件,先写一个框架

#pragma once
#include <vector>
#include <map>
#include <iostream>
using namespace std;namespace matrix
{template<class V, class W, W MAX_W = INT_MAX, bool Direction = false>//W权值,Direction表示有无向,false表示无向//中间加的MAX_W,其实就是非类型模版参数,是用来初始化邻接矩阵的,也可以传其它值class Graph{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;}_matrix.resize(n);for (size_t i = 0; i < _matrix.size(); ++i){_matrix[i].resize(n, MAX_W);}}size_t GetVertexIndex(const V& v)//确定顶点下标,虽然都在map里,但还是要有一个函数{}void AddEdge(const V& src, const V& dst, const W& w)//增加边的话,应当传源点,目标点,权值{}private:vector<V> _vertexs;//顶点集合,比如ABCD//有稀疏图,稠密图的叫法,就是在说边的多少map<V, int> _indexMap;//顶点映射下标,比如ABCD变为1234vector<vector<W>> _matrix;//邻接矩阵,展示顶点之间的关系和权值};
};

Graph构造函数那里就创建了一个图,三个成员都被初始化完成,传进来的就是一个数组a和顶点个数。接下来要开始添加边,在这之前先确定好顶点下标。

讲解在代码中

		size_t GetVertexIndex(const V& v)//确定顶点下标,虽然都在map里,但还是要有一个函数{auto it = _indexMap.find(v);if (it != _indexMap.end()){return it->second;}else{throw invalid_argument("顶点不存在");//非法参数异常return -1;//抛异常是运行逻辑,程序走到这里会抛,但编译器不管运行逻辑,还是要检查这个函数是否正确结束,所以要返回。}}void AddEdge(const V& src, const V& dst, const W& w)//增加边的话,应当传源点,目标点,权值{//即使抛异常程序还能执行,但程序员已经知道程序错误了,最后程序正常地异常结束//assert则是暴力退出,且只在debug模式下才有assert,release模式下就屏蔽assert了size_t srci = GetVertexIndex(src);size_t dsti = GetVertexIndex(dst);//有向图和无向图区分_matrix[srci][dsti] = w;if (Direction == false)_matrix[dsti][srci] = w;}

打印函数

		void Print(){// 打印顶点和下标映射关系for (size_t i = 0; i < _vertexs.size(); ++i){cout << _vertexs[i] << "-" << i << " ";}cout << endl << endl;cout << "  ";for (size_t i = 0; i < _vertexs.size(); ++i){cout << i << " ";}cout << endl;// 打印矩阵for (size_t i = 0; i < _matrix.size(); ++i){cout << i << " ";for (size_t j = 0; j < _matrix[i].size(); ++j){if (_matrix[i][j] != MAX_W)cout << _matrix[i][j] << " ";elsecout << "*" << " ";}cout << endl;}cout << endl << endl;// 打印所有的边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){cout << _vertexs[i] << "-" << _vertexs[j] << ":" << _matrix[i][j] << endl;}}}}

测试代码

	void TestGraph1()//放在matrix命名空间里{Graph<char, int, INT_MAX, 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();}int main()
{matrix::TestGraph1();return 0;
}

到现在为止,代码没有问题,我们来改进一下功能实现。

合并集合的时候,是让数量小的集合合并到大的,还是大的合并到小的,这里小的合并到大的为好。默认root1为数量大的

	void Union(int x1, int x2)//给两个编号,合并{//得先判断这两个编号能不能合并int root1 = FindRoot(x1);int root2 = FindRoot(x2);//本身在一个集合,就不需要合并if (root1 == root2) return;//可以合并的话就自己做规定怎么合并,这里就让小的编号做根if (abs(_ufs[root1]) < abs(_ufs[root2])) swap(root1, root2);_ufs[root1] += _ufs[root2];_ufs[root2] = root1;}

4、压缩路径

不用递归写,毕竟是在压缩深度深的树。用非递归写。

	int FindRoot(int x)//找一个编号的根{int root = x;while (_ufs[root] >= 0)//此时说明它本身不是根,就看存的值,往上找父节点,因为父节点可能不是根,所以需要while{root = _ufs[root];//对照上面的图来看}//压缩路径while (_ufs[x] >= 0){int parent = _ufs[x];_ufs[x] = root;x = parent;}return root;//小于0说明自己就是根,直接返回}

这里就是一次把当前节点到根节点之间的所有节点都变成根的孩子。

5、基本概念

完全图是最稠密的图,任意两个顶点之间都直接相连 。在有n个顶点的无向图中,若有n × (n-1)/2条边,即任意两个顶点之间有且仅有一条边,
则称此图为无向完全图;在n个顶点的有向图中,若有n × (n-1)条边,即任意两个顶点之间有且仅有方向相反的边,则称此图为有向完全图。

邻接顶点。就是两个顶点之间有边,就是两个邻接顶点。在无向图中G中,若(u, v)是E(G)中的一条边,则称u和v互为邻接顶点,并且称边(u,v)依附于顶点u和v;在有向图G中,若<u, v>是E(G)中的一条边,则称顶点u邻接到v,顶点v邻接自顶点u,并且称边<u, v>与顶点u和顶点v相关联。

顶点的度指的是与这个顶点相连的边的数量,记作deg(v)。有向图有入度和出度,也就是1这个顶点,1到3有1条边,2到1有2条边,所以1的入度是2,出度是1。

路径则好理解,从一个顶点到另一个顶点都可以走哪些边,这些边就组成了路径。路径可以有很多条,也就出现了最短路径问题。

路径长度。对于不带权的图,一条路径的路径长度是指该路径上的边的条数;对于带权的图,一条路径的路径长度是指该路径上各个边权值的总和。

简单路径与回路。若路径上各顶点v1,v2,v3,…,vm均不重复,则称这样的路径为简单路径。若路径上第一个顶点v1和最后一个顶点vm重合,则称这样的路径为回路或环。

子图。子图的顶点和边都是原图的一部分。

连通图。在无向图中,若从顶点v1到顶点v2有路径,则称顶点v1与顶点v2是连通的。如果图中任意一对顶点都是连通的,则称此图为连通图。

强连通图:在有向图中,若在每一对顶点vi和vj之间都存在一条从vi到vj的路径,也存在一条从vj到vi的路径,则称此图是强连通图。

任意点之间都有路径到达,无向图中叫连通图,有向图中叫强连通图。

生成树:一个连通图的最小连通子图称作该图的生成树。有n个顶点的连通图的生成树有n个顶点和n-1条边。注意这是无向图中的概念。

6、邻接表

邻接表要用到哈希。用上面邻接矩阵的代码做一下改动。

	template <class W>struct Edge{int _dsti;//目标点的下标W _w;};template<class V, class W, bool Direction = false>//这里就不需要INT_MAX了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;}}size_t GetVertexIndex(const V& v){auto it = _indexMap.find(v);if (it != _indexMap.end()){return it->second;}else{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);}void Print(){size_t n = _vertexs.size();for (size_t i = 0; i < n; ++i){cout << "[" << i << "]" << "->" << _vertexs[i] << endl;}cout << endl;}private:vector<V> _vertexs;//顶点集合map<V, int> _indexMap;//顶点映射下标vector<Edge*> _tables;//邻接表};

然后完善,在命名空间里添加一个结构体,类里再使用它

	template <class W>struct Edge{size_t _dsti;//目标点的下标W _w;Edge<W>* _next;Edge(size_t dsti, const W& w):_dsti(dsti), _w(w), _next(nullptr){}};template<class V, class W, bool Direction = false>//这里就不需要INT_MAX了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);}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(){size_t n = _vertexs.size();for (size_t i = 0; i < n; ++i){cout << "[" << i << "]" << "->" << _vertexs[i] << endl;}cout << endl;for (size_t i = 0; i < n; ++i){cout << endl;cout << _vertexs[i] << "[" << i << "]";Edge* cur = _tables[i];while (cur){printf(" - %-3d -> ", cur->_w);cout << _vertexs[cur->_dsti] << "[" << cur->_dsti << "]";cur = cur->_next;}cout << " -> nullptr" << endl;}}

测试

	void TestGraph2(){string a[] = { "张三", "李四", "王五", "赵六" };Graph<string, int> g1(a, 4);g1.AddEdge("张三", "李四", 100);g1.AddEdge("张三", "王五", 200);g1.AddEdge("王五", "赵六", 30);g1.Print();}

本篇gitee

下一篇开始写图的算法。

结束。

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

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

电脑突然提示找不到msvcp140.dll怎么办,解决msvcp140.dll丢失的办法

当我们在电脑上运行某些软件或游戏时&#xff0c;可能会遇到一个常见的错误消息&#xff1a;“找不到msvcp140.dll”。出现这样的情况通常意味着系统缺少一个重要的动态链接库文件&#xff0c;而这可能会导致程序无法正常启动。如果你现在遇到了这个问题&#xff0c;哪有可以用…

3D LUT 滤镜 shader 源码分析

最近在做滤镜相关的渲染学习&#xff0c;目前大部分 LUT 滤镜代码实现都是参考由 GPUImage 提供的 LookupFilter 的逻辑&#xff0c;整个代码实现不多。参考网上的博文也有各种解释&#xff0c;参考了大量博文之后终于理解了&#xff0c;所以自己重新整理了一份&#xff0c;方便…

最新Microsoft Edge浏览器如何使用圆角

引入 最近我看了edge官方的文档&#xff0c;里面宣传了edge的最新UI设计&#xff0c;也就是圆角&#xff0c;但是我发现我的浏览器在升级至最新版本之后&#xff0c;却没有圆角 网上有很多人说靠实验性功能即可解锁&#xff0c;但是指令我都试过了&#xff0c;每次都是搜索无结…

OpenLayers入门,OpenLayers从vue的assets资源路径加载TopoJson文件并解析数据叠加到地图上,以加载世界各国边界为例

专栏目录: OpenLayers入门教程汇总目录 前言 本章以加载世界各国边界的TopoJson格式数据为例,讲解如何使用OpenLayers从vue的assets资源路径加载TopoJson文件并解析数据叠加到地图上。 GeoJson介绍 GEOJSON是gis地图中常用的数据格式,制作地图时用于存储各种地理数据,使…

一站式解决安全问题

端玛科技致力于攻克困难的应用软件安全问题&#xff0c;我们的解决方案以安全标准、安全教育和安全风险评估三大支柱为安全SDLC的基础&#xff0c;这三大支柱相互依存&#xff0c;创建了一个可重复的、安全的软件开发生态系统。 主要业务范围&#xff1a;关注整个软件开发过程…

基于SpringBoot的在线笔记系统

技术介绍 &#x1f525;采用技术&#xff1a;SpringSpringMVCMyBatisJSPMaven &#x1f525;开发语言&#xff1a;Java &#x1f525;JDK版本&#xff1a;JDK1.8 &#x1f525;服务器&#xff1a;tomcat &#x1f525;数据库&#xff1a;mysql &#x1f525;数据库开发工具&…

七、【图像添加水印】

文章目录 一、制作水印1、先新建图层2、新建文字图层并调好水印文字的大小与角度3、添加图层样式4、添加定义图案 二、添加水印 一、制作水印 1、先新建图层 2、新建文字图层并调好水印文字的大小与角度 3、添加图层样式 1、打开“描边” 2、选择“颜色” 4、添加定义图案 二…

spring-代理模式

代理模式 一、概念1.静态代理2.动态代理 一、概念 ①介绍 二十三种设计模式中的一种&#xff0c;属于结构型模式。它的作用就是通过提供一个代理类&#xff0c;让我们在调用目标 方法的时候&#xff0c;不再是直接对目标方法进行调用&#xff0c;而是通过代理类间接调用。让不…

Java SE 学习笔记(十八)—— 注解、动态代理

目录 1 注解1.1 注解概述1.2 自定义注解1.3 元注解1.4 注解解析1.5 注解应用于 junit 框架 2 动态代理2.1 问题引入2.2 动态代理实现 1 注解 1.1 注解概述 Java 注解&#xff08;Annotation&#xff09;又称Java标注&#xff0c;是JDK 5.0引入的一种注释机制&#xff0c;Java语…

C语言 指针进阶笔记

p和*p: 如图&#xff0c;p是指针&#xff0c;指针存放着地址&#xff0c;打印出来应该是数组的值 *p是指针里里面的元素 #include<stdio.h> int main() {int a1;int b2;int c3;int p[3]{a,b,c};printf("%d",*p); return 0; } 那么现在的打印结果应该为数组的…

2023年阿里云双11有什么优惠活动?详细攻略来了!

随着双十一的临近&#xff0c;阿里云也正式开启了双11大促&#xff0c;推出了“金秋云创季”活动&#xff0c;那么&#xff0c;2023年阿里云双11的优惠活动究竟有哪些呢&#xff1f;本文将为大家详细介绍。 一、阿里云双11活动时间 1、2023年10月27日-2023年10月31日&#xff…

java 版本企业招标投标管理系统源码+多个行业+tbms+及时准确+全程电子化

项目说明 随着公司的快速发展&#xff0c;企业人员和经营规模不断壮大&#xff0c;公司对内部招采管理的提升提出了更高的要求。在企业里建立一个公平、公开、公正的采购环境&#xff0c;最大限度控制采购成本至关重要。符合国家电子招投标法律法规及相关规范&#xff0c;以及审…

Android Studio 导出 jar

AS版本&#xff1a;Android Studio Giraffe | 2022.3.1 Patch 1 1、File——New Module——Android Library 2、mylibrary——main——新建功能类 3、mylibrary——build.gradle——android {}内复制以下代码——Sync Now //Copy类型 tasks.register(makeJar, Copy) { //删…

科普|电源自动测试系统测试的项目都有哪些?

电源自动测试系统是一种用于电源性能自动测试的集成系统&#xff0c;它可以自动检测电源模块或开关电源的输入、输出、保护等各个方面。该系统通常由数据软件和各类硬件测试仪器共同组成&#xff0c;利用通讯总线、测试夹具以及其它线缆等将仪器进行连接组成整体的系统结构&…

十大排序算法(C语言)

参考文献 https://zhuanlan.zhihu.com/p/449501682 https://blog.csdn.net/mwj327720862/article/details/80498455?ops_request_misc%257B%2522request%255Fid%2522%253A%2522169837129516800222848165%2522%252C%2522scm%2522%253A%252220140713.130102334…%2522%257D&…

Openssl数据安全传输平台017:Linux客户端代码的编译与调试-Bug记录

文章目录 1 在windows上先预编译2 Centos上进入项目文件夹进行编译2.0 最终的编译指令2.1 找不到lprotobuf&#xff0c;找不到protobuf的google文件夹2.1.1 编译指令及提示2.1.2 问题分析2.1.3 解决办法 2.2 json类中方法unreference2.2.1 编译指令及提示2.2.2 问题分析 *** 最…

【Java 进阶篇】Java登录案例详解

登录是Web应用程序中常见的功能&#xff0c;它允许用户提供凭证&#xff08;通常是用户名和密码&#xff09;以验证其身份。本文将详细介绍如何使用Java创建一个简单的登录功能&#xff0c;并解释登录的工作原理。我们将覆盖以下内容&#xff1a; 登录的基本概念创建一个简单的…

CSS宽度100%和宽度100vw之间有什么不同?

vw和vh分别代表视口宽度和视口高度。 使用width: 100vw代替的区别在于width: 100%&#xff0c;虽然100%将使元素适合所有可用空间&#xff0c;但视口宽度具有特定的度量&#xff0c;在这种情况下&#xff0c;可用屏幕的宽度 。 如果设置样式body { margin: 0 }&#xff0c;则1…

vue源码分析(一)——源码目录说明

文章目录 一、如何下载源码&#xff08;可忽略&#xff09;&#xff08;1&#xff09;打开地址&#xff08;2&#xff09;复制链接&#xff08;3&#xff09;git clone 链接 二、源码目录说明1.可以根据你下载的源码通过package.json文件查看vue版本2.源码目录说明 一、如何下载…

亚信科技发布“电信级”核心交易数据库AntDB7.0,助力政企“信”创未来!

昨日&#xff0c;亚信科技AntDB数据库 7.0产品线上发布会成功举办&#xff0c;数千位关注亚信科技、关注国产数据库&#xff0c;致力于推动数据库行业变革的专家、客户热情参与&#xff0c;并对发布会及产品给予高度评价。 新增两大技术特性 作为我国最早一批独立研发的通用型…