【C++ 实现】图论概念,最小生成树,单/多源最短路径实现

news2024/9/29 3:31:01

文章目录

  • 数据结构表示图
  • 最小生成树
    • Kruskal
    • Prim
  • 最短路径
    • Dijkstra
    • Bellman-Ford算法
    • 多源最短路径:FloydWarshall
  • 总结


数据结构表示图

首先节点的存取,V是节点key,vector<pair<V,V>> map;其实已经能表达一个图了,但是这样保存节点对我们使用来说会导致复杂度高。

常用保存节点的方式,有矩阵和邻接表。
矩阵的优点:O(1) 时间找到两点是否相连以及他们的权值。
矩阵的缺点:找一点相邻的所有节点的时候是O(N)的,即会遍历到不相连的两两节点。

图还分有向图和无向图,一般来说有向图存出边即可。

template<class V, class W, bool Direction = false> V表示节点的类型,W表示权值的类型,Direction表示是否是无向图。
图一般提供的函数,构造函数,将图的每一个节点的距离初始化;
AddEdge函数:将两个节点建立联系,附上权值;
GetVertexIndex: 将对应的图的节点转化为下标

template <class V, class W, W MAX_W = INT_MAX, bool Direction = false>
class Graph
{
public:
    typedef Graph<V, W, MAX_W, Direction> Self;
    Graph() = default;
    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;
        }
        // MAX_W 作为不存在边的标识值
        _matrix.resize(n);
        for (auto &e : _matrix)
        {
            e.resize(n, MAX_W);
        }
    }
    size_t GetVertexIndex(const V &v)
    {
        auto ret = _vIndexMap.find(v);
        if (ret != _vIndexMap.end())
        {
            return ret->second;
        }
        else
        {
            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 << _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] << " ";
                else
                    cout << "#"
                         << " ";
            }
            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;
                }
            }
        }
    }
            private : map<V, size_t> _vIndexMap;
    vector<V> _vertexs; // 顶点集合
    vector<vector<W>> _matrix;
      // 存储边集合的矩阵
};
void TestGraph()
{
    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();
}
}
namespace LinkTable
{
    template <class W>
    struct LinkEdge
    {
        int _srcIndex;
        int _dstIndex;
        W _w;
        LinkEdge<W> *_next;
        LinkEdge(const W &w)
            : _srcIndex(-1), _dstIndex(-1), _w(w), _next(nullptr)
        {
        }
    };
    template <class V, class W, bool Direction = false>
    class Graph
    {
        typedef LinkEdge<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("不存在的顶点");
                return -1;
            }
        }
        void AddEdge(const V &src, const V &dst, const W &w)
        {
            size_t srcindex = GetVertexIndex(src);
            size_t dstindex = GetVertexIndex(dst);
            // 0 1
            Edge *sd_edge = new Edge(w);
            sd_edge->_srcIndex = srcindex;
            sd_edge->_dstIndex = dstindex;
            sd_edge->_next = _linkTable[srcindex];
            _linkTable[srcindex] = sd_edge;
            // 1 0
            // 无向图
            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;
            }
        }

    private:
        map<string, int> _vIndexMap;
        vector<V> _vertexs;        // 顶点集合
        vector<Edge *> _linkTable; // 边的集合的临接表
    };
    void TestGraph()
    {
        string a[] = {"张三", "李四", "王五", "赵六"};
        Graph<string, int> g1(a, 4);
        g1.AddEdge("张三", "李四", 100);
        g1.AddEdge("张三", "王五", 200);
        g1.AddEdge("王五", "赵六", 30);
    }
}

最小生成树


什么是最小生成树
在给定一张无向图,如果在它的子图中,任意两个顶点都是互相连通,并且是一个树结构,那么这棵树叫做生成树。当连接顶点之间的图有权重时,权重之和最小的树结构为最小生成树!

Kruskal

  • 选最短的边,且不能构成回路,会用到并查集。因为需要找两个点是否已经存在路径当中。
  • AddEdge可以重载,但是由于V可能就是int,就会造成错误,所以采用换个函数名的方式达到复用的效果。
  • 总之细节很多,实现完看看代码。有不是连通图的情况也需要判断。
//传入参数为一个图,是一个输入型参数,最终图的数值会保存在该图当中。
//返回一个最终的权值总和
int Kruskal(Self& minTree)
{
	//首先初始化最小生成树
	minTree._vertex = _vertex;
	minTree._vertex2index = _vertex2index;
	size_t len = _weightmatrix.size();
	minTree._weightmatrix.resize(len, vector<W>(len, MaxSize));
	//Kruskal算法关注的是边的关系,需要每次取出权重最小的边
	priority_queue<Edge,vector<Edge>,greater<Edge>> pq;
	//初始化优先级队列
	for (int i = 0; i < len; ++i)
	{
		for (int j = 0; j < len; ++j)
		{
			//这个点存在有效权值则入pq,注意这里只用入矩阵的一半即可
			if (i < j && _weightmatrix[i][j] != MaxSize)
			{
				pq.push(Edge(i, j, _weightmatrix[i][j]));
			}
		}
	}
	//此时就有了一个优先级队列保存最小的节点。
	//需要拿出优先级队列的节点并且需要是不能构成环的
	//全局贪心,每次找最小的边进行合并,需要注意不能在一个集合当中,所以可以使用并查集来,且需要保存的是点
	//将所有找到的边合并起来就是答案
	UnionFindSet unionset(_vertex.size());
	W weight = W();
	int i = 1;//边是比点少一条的。
	while (!pq.empty())
	{
		Edge edge = pq.top();
		pq.pop();
		if (unionset.FindRoot(edge._srci) == unionset.FindRoot(edge._desti))
		{
			cout << "构成环: " << _vertex[edge._srci] << "->" << _vertex[edge._desti] << endl;
			continue;
		}
		i++;
		cout << "Add:" << edge._srci << "-> " << edge._desti << endl;
		minTree.Add2Weight(_vertex[edge._srci], _vertex[edge._desti], edge._weight);
		unionset.Union(edge._srci, edge._desti);
		cout << _vertex[edge._srci] << "->" << _vertex[edge._desti] << endl;
		weight += edge._weight;
	}
	if (i != _vertex.size())
	{
		//这个时候表示不是一个连通图
		cout << "不是连通图" << endl;
	}
	return weight;
}

Prim


在这里插入图片描述
步骤:

  • 先加入const V& v四周围的边入优先级队列(小堆),每次从头部获取一个最短的边,记录为访问,添加新增节点的四周围的边。
  • 为了防止添加的边有重复,我们采用vector<bool>来记录每一个顶点,记录每一个添加进来的顶点。

当然选的边不同,结果也有可能相同。
在这里插入图片描述

//Prim算法是基于局部的贪心
int Prim(Self& minTree, const V& v)
{
	//初始化最小生成树
	minTree._vertex = _vertex;
	minTree._vertex2index = _vertex2index;
	int len = _vertex.size();
	minTree._weightmatrix.resize(len, vector<W>(len, MaxSize));

	//初始化优先级队列,只不过入一个节点周围的
	priority_queue<Edge,vector<Edge>,greater<Edge>> pq;
	int srci = VertexToIndex(v);
	for (int i = 0; i < len; ++i)
	{
		if (_weightmatrix[srci][i] != MaxSize)
			pq.push(Edge(srci, i, _weightmatrix[srci][i]));
	}
	//初始化访问的列表,这个可以用作区分集合,出边不在visited就不会构成环
	vector<bool> visited(len, false);
	visited[srci] = true;
	W weight = W();//默认权值

	int size = 0;//记录边数
	//从优先级队列取出一个点作为最小的值,这个位置被认为是总体权值最小所加的一条边。
	while (!pq.empty())
	{
		Edge indexEdge = pq.top();
		pq.pop();
		//如果该边终点已经访问过,那么就一定会构成环。
		if (visited[indexEdge._desti])
		{
			cout << "形成回路: " << _vertex[indexEdge._srci] << "->" << _vertex[indexEdge._desti] << endl;
			//该点若已经访问过,说明是入边,此时加入该点会造成环
			continue;
		}
		cout << "加入集合: " << _vertex[indexEdge._srci] << "->" << _vertex[indexEdge._desti] << endl;
		weight += _weightmatrix[indexEdge._srci][indexEdge._desti];
		//入四周围的边
		for (int i = 0; i < _weightmatrix[indexEdge._desti].size(); ++i)
		{
			//该点没有访问过并且是可以到达的则入集合
			if (_weightmatrix[indexEdge._desti][i] != MaxSize && visited[i] == false)
			{
				pq.push(Edge(indexEdge._desti, i, _weightmatrix[indexEdge._desti][i]));
				cout << "加入" << _vertex[indexEdge._desti] << " 后新增了" << _vertex[i] << endl;
			}
		}
		minTree.Add2Weight(_vertex[indexEdge._srci], _vertex[indexEdge._desti], indexEdge._weight);
		visited[indexEdge._desti] = true;
		size++;
	}
	//若size最终为边数说明边都入进来了。
	if (size == _vertex.size()-1)
	{
		//表明了到达这里的时候没有找到一个点
		cout << "是连通图" << endl;
		return weight;
	}
	else
	{
		cout << "不是联通图" << endl;
		return W();
	}

	return weight;
}

最短路径


从A点到其他点的最短距离。
Dijkstra无法解决带负权值的,后续的算法无法解决带负权回路。

Dijkstra

找的起始边,用邻接表和邻接矩阵的有各自的优缺点。

每次用最短的路径更新周围的节点进行松弛操作。
每一次选取一个点,作为到达该点的最短点,由该点更新周围其他点。
缺点:有负权边用Dijkstra会出错。
在这里插入图片描述

//Dist数组就是告知从src到对应下标所要的代价(权值),path就是该节点父亲节点的下标
void Dijkstra(const V& src, vector<int>& dist, vector<int>& path)
{
	int index = VertexToIndex(src);
	size_t n = _vertex.size();
	dist.resize(n, MaxSize);
	path.resize(n, -1);
	path[index] = index;//自己就是自己的父节点
	dist[index] = 0;
	int len = path.size();
	vector<bool> visited(len, false);
	int size = len;
	while (size--)
	{
		//用index去跟新其他节点的路径
		for (int i = 0; i < n; ++i)
		{
			if (_weightmatrix[index][i] != MaxSize && dist[i] > dist[index] + _weightmatrix[index][i])
			{
				//更新其他路径需要保证更新完更小
				dist[i] = dist[index] + _weightmatrix[index][i];
				//更新父亲
				path[i] = index;
			}
		}
		//更新index
		visited[index] = true;
		int minNum = INT_MAX;

		for (int i = 0; i < n; ++i)
		{
			if (!visited[i] && dist[i] < minNum)
			{
				index = i;
				minNum = dist[i];
			}
		}
		if (minNum == INT_MAX)
		{
			break;
		}
	}
	if (size != 0)
	{
		cout << "不是连通图" << endl;
	}
}

Bellman-Ford算法

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

  • 优化:第一个轮次进行更新的边才会影响其他路径,只需要拿这些边去更新其他边(SPFA优化,队列优化)。但是对于时间复杂度并没有得出一个结论。最坏情况O(N^3),最好情况O(N^2)
  • 循环提前跳出优化
    可以用SPFA优化,采用队列优化。
    在这里插入图片描述
    用 (i,j)存在权值的情况去更新其他边。由于每次遍历整个邻接矩阵就好了。
    但是需要遍历n回,n是点的个数。
    注意每一个点dist[到自己] = 0,一开始可以设置为无穷大,根据给的顶点进行更新就可以了。dist[index] = W();//自己到自己的权值为0
    a->b假设需要更新,最多用len条边更新。
// 时间复杂度:O(N^3) 空间复杂度:O(N)
bool BellmanFord(const V& src, vector<W>& dist, vector<int>& pPath)
{
	//从src更新其他节点,首先看src能够更新哪些节点
	int len = _vertex.size();
	dist.resize(len, MaxSize);
	pPath.resize(len,-1);

	int index = VertexToIndex(src);
	dist[index] = W();//自己到自己的权值为0


	//如果只更新一次,可能出现后续的节点影响了前面的节点的情况,一个节点最多经过k-2个节点,需要更新k次
	for (int k = 0; k < len; ++k)
	{
		bool flag = true;//如果不需要跟新就可以跳出来
		cout << "更新第:" << k << "轮" << endl;
		for (int i = 0; i < len; ++i)
		{
			//更新已经能够到达的路径周围的节点
			if (dist[i] == MaxSize)
				continue;

			//这里的i就是需要更新周围节点
			for (int j = 0; j < len; ++j)
			{
				if (i != j && _weightmatrix[i][j] != MaxSize)
				{
					//表示这个点是有联系的
					if (dist[j] > dist[i] + _weightmatrix[i][j])
					{
						cout << _vertex[i] << "->" << _vertex[j] << ":" << _weightmatrix[i][j] << endl;
						flag = false;
						//就可以更新
						dist[j] = dist[i] + _weightmatrix[i][j];
						//更新父路径实际上是从i这个节点的父路径来的,观察这里
						pPath[j] = i;
					}
				}
			}
		}
		if (flag)
			break;
	}
	//检测是否有负权回路,检测方法为再更新一次即可。
	bool flag = false;
	for (int i = 0; i < len; ++i)
	{
		//更新已经能够到达的路径周围的节点
		if (dist[i] == MaxSize)
			continue;

		//这里的i就是需要更新周围节点
		for (int j = 0; j < len; ++j)
		{
			if (i != j && _weightmatrix[i][j] != MaxSize)
			{
				//表示这个点是有联系的
				if (dist[j] > dist[i] + _weightmatrix[i][j])
				{
					flag = true;
					break;
				}
			}
		}
		if (flag)
			return false;//有负权回路

		return true;
	}

}

多源最短路径:FloydWarshall


dist数组和pPath数组得是二维的,就能记录任意两个点。结构和前面的有所不同。 距离矩阵能存储任意两个点的距离。
dist数组在前面都是以一个特定的点出发,是我们传参决定的。
FloydWarshall 是取中间点进行更新。本质是用了动态规划。
不需要像Dijstra,Bellman-Ford一个在距离矩阵取,一个在邻接矩阵取。而是都在距离矩阵dist取即可。
在这里插入图片描述
三维当中的k是经过多少个点的意思。情况就是上述的两种。他虽然是三维空间来表示,但是实现的时候由于中间节点k也可以查二维矩阵就可以了,所以就用二维的矩阵表示。

若是不带负权回路,用Dijstra遍历每一个顶点也是O(N^3),但是FloydWarshall是可以解决负权回路的。
而Bellman-Ford算法的话效率太低了。所以多源最短路径横空出世。

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}取得的一条最短路径。
在这里插入图片描述

在这里插入图片描述
左边是距离矩阵,右边是父路径矩阵。

  • 初始化
  • 直接相连的边更新,自己到自己初始化成0
  • 注意父路径vvpPath[i][j] 若是经过了k,则是k,所以需要改变,并且即使是vvDist[i][j] + vvDist[k][j]途中经过了其他的节点,因为我们要找的与j相连的上一个邻接点。k->j不一定是直接相连,若是则是k,否则就是其他。
  • 由于起始点和终点都有可能变,所以中间节点都是需要遍历的,也就是for(int k = 0;k < len;++k)如同i->j最多经过n-2个点。
void FloydWarshall(vector<vector<W>>& vvDist, vector<vector<int>>& vvpPath)
		{
			vvDist = _weightmatrix;
			int len = vvDist.size();
			//vvDist是权值矩阵
			//vvpPath用来表示到达i,j位置的父亲
			vvpPath.resize(len, vector<int>(len, -1));
			// 直接相连的边更新一下
			for (size_t i = 0; i < len; ++i)
			{
				for (size_t j = 0; j < len; ++j)
				{
					if (_weightmatrix[i][j] != MaxSize)
					{
						vvpPath[i][j] = i;
					}
					if (i == j)
					{
						vvDist[i][j] = W();
					}
				}
			}
			for (int k = 0; k < len; ++k)
			{
				for (int i = 0; i < len; ++i)
				{
					//每次更新从i出去的点
					for (int j = 0; j < len; ++j)
					{
					
							// k 作为的中间点尝试去更新i->j的路径
						if (vvDist[i][k] != MaxSize && vvDist[k][j] != MaxSize
							&& vvDist[i][k] + vvDist[k][j] < vvDist[i][j])
						{
							vvDist[i][j] = vvDist[i][k] + vvDist[k][j];
							vvpPath[i][j] = vvpPath[k][j];
						}
					}
				}
			}
			// 打印权值和路径矩阵观察数据
			for (size_t i = 0; i < len; ++i)
			{
				for (size_t j = 0; j < len; ++j)
				{
					if (vvDist[i][j] == MaxSize)
					{
						//cout << "*" << " ";
						printf("%3c", '*');
					}
					else
					{
						//cout << vvDist[i][j] << " ";
						printf("%3d", vvDist[i][j]);
					}
				}
				cout << endl;
			}
			cout << endl;

			for (size_t i = 0; i < len; ++i)
			{
				for (size_t j = 0; j < len; ++j)
				{
					//cout << vvParentPath[i][j] << " ";
					printf("%3d", vvpPath[i][j]);
				}
				cout << endl;
			}
			cout << "=================================" << endl;		
		}

总结


图论到此为止~

  • 喜欢就收藏
  • 认同就点赞
  • 支持就关注
  • 疑问就评论

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

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

相关文章

MWCS 2023,到底有些啥?(上篇)

大家好&#xff0c;MWCS 2023&#xff08;世界移动通信大会上海展&#xff09;已经结束了。按照老规矩&#xff0c;我来给大家汇报一下现场情况。 █ 展会总结 今年是MWC在中国的第十届&#xff0c;也是疫情管控解除后的第一届。从总体情况来看&#xff0c;人气非常火爆。展区人…

ElasticSearch - 根据经纬度,简单搜索指定距离范围内的数据

ES的地图检索方式 ES支持的地图检索方式有以下几种&#xff1b; geo_distance geo_bounding_box geo_polygon 1、geo_distance&#xff1a;直线距离检索&#xff0c;如给定点A&#xff0c;要求返回地图上距离点A三千米的商家&#xff08;点外卖场景&#xff09; 2、查找索引…

下半年就该这么干!

阅读本文大概需要 0.99 分钟。 周末一般不更新&#xff0c;不过今天有点特殊。 是这样的&#xff0c;前几天不是通知说我们 7 月 8 日会在杭州举办线下大会么&#xff1f;完了之后发现后台很多私信问怎么参加的&#xff0c;这才想起我这个公众号还没发让大家进预约群的入口。。…

前端使用mysql记录。以本机作为服务器,安装mySql。Dbeaver的基本使用

安装 Mac 上如何安装Mysql&#xff1f; Mac 上如何安装Mysql&#xff1f; Mysql 配置 在mac终端打开文件&#xff1a;vi ~/.bash_profile 加入语句&#xff1a;PATH$PATH:/usr/local/mysql/bin 使配置的语句生效&#xff1a;source ~/.bash_profile 使用命令行 sudo vi /et…

【读书笔记】《月亮与六便士》- [英] 威廉·萨默塞特·毛姆 - 1919年出版

不停的阅读&#xff0c;然后形成自己的知识体系。 2023.07.03 读 一直听说毛姆的大名&#xff0c;却一直没有拜读。记得《小王子》中有读者提到这本书&#xff0c;看了眼作者竟然发现是毛姆。那么毫不犹豫的&#xff0c;赶紧拜读一番。 文章目录 作家榜推荐词第一章第二章第三…

Spring核心原理解析

1.Bean的生命周期底层原理 AppConfig package com.zhouyu;import com.zhouyu.service.OrderService; import org.springframework.context.annotation.*;ComponentScan("com.zhouyu") public class AppConfig {Beanpublic OrderService orderService1(){return new…

微机实验一:认识实验系统和联机通讯实验

实验目的 讲清楚进行本实验后要学到的知识、掌握的数据结构及共定义和农示方法&#xff0c;讲清楚所采用的算法 wrTPL-486微机原理及接口技术数学实验手统的操u)孰秀TPC-480联机集闯开发调试故件的操作环境3)3俩文丝计体机的基阻成和4、存储器并有接新i的置(了解岗单汇海语谢设…

Nginx【location指令、虚拟主机的分类、Nginx支持三种类型的虚拟主机配置 】(三)-全面详解(学习总结---从入门到深化)

目录 Nginx配置指令详解_location指令 虚拟主机的分类 Nginx支持三种类型的虚拟主机配置 Nginx配置指令详解_location指令 配置location块 语法&#xff1a; URL // http://ip:port/ location [ | ~ | ~* | ^~] uri {... } 示例1 没有修饰符 表示&#xff1a;必须以指…

UE5 MetaHuman SDK插件的使用【二、聊天机器人】

目录 制作&#xff1a; 流程制作【相对复杂但逻辑清楚】&#xff1a; 快速制作【有个函数把上面的流程全部合在了一起&#xff0c;只需要用一个函数即可】 3个结构体的参数 combo后面的逻辑&#xff1a; 效果&#xff1a; ​编辑 代码 在前面的文章中&#xff0c;我们创…

geoserver发布arcgis server离线瓦片

1.使用tif文件也可以发布服务&#xff0c;但是我下载的tif文件发布的服务总数模糊不清&#xff0c;原因可能是地图比例尺问题。 2.仔细研究&#xff0c;发现下载的arcgis server瓦片都是高清的&#xff0c;于是想到直接加载arcgis瓦片&#xff0c;这样图片/坐标系之间问题都完…

Python3 实例(四) | 菜鸟教程(二十二)

目录 一、Python 堆排序 二、Python 计数排序 三、Python 希尔排序 四、Python 拓扑排序 五、Python 简单的银行系统 一、Python 堆排序 &#xff08;一&#xff09;堆排序&#xff08;Heapsort&#xff09;是指利用堆这种数据结构所设计的一种排序算法。 &#xff08;二…

【JUC-6】AQS介绍,基于AQS实现自己的锁

什么是AQS AbstractQueuedSynchronizer(抽象队列同步器&#xff0c;简称AQS)出现在JDK 1.5中。AQS是很多同步器的基础框架&#xff0c;比如ReentrantLock、CountDownLatch和Semaphore等都是基于AQS实现的。除此之外&#xff0c;我们还可以基于AQS&#xff0c;定制出我们所需要…

关于LiveData全面详解(附事件总线)

前言&#xff1a;缤纷色彩闪出的美丽 是因它没有 分开每种色彩 前言 MVVM 架构模式中&#xff0c;ViewModel 是不会持有宿主的信息&#xff0c;业务逻辑在 ViewModels 层中完成&#xff0c;而不是在 Activities 或 Fragments 中。LiveData 在里面担任数据驱动的作用&#xff1…

ElementPlus的Collapse 折叠面板问题

我也不明白为什么会报这个错误&#xff0c;把关于ts的代码改成js的就可以了。。 ERROR in ./src/views/Home.vue?vue&typescript&langts&setuptrue (./node_modules/vue-loader/dist/index.js??ruleSet[0].use[0]!./src/views/Home.vue?vue&typescript&…

大厂经验,基于资产健康度量化的小米数据治理实践

摘要&#xff1a;随着小米公司各项业务的快速发展&#xff0c;数据中的商业价值也愈发突显。而与此同时&#xff0c;各业务团队在数据查询、分析等方面的压力同样正在剧增。小米大数据团队用大数据管理大数据&#xff0c;从存储、计算、规范、质量、安全五方面着手治理&#xf…

java swing实现JTextField文本框的输入提示补全功能,以登陆界面自动提示补全用户账号为例,自动填充账号密码

前言 如果您想了解更多的java项目功能源码,请订阅我的专栏: java项目源码合集100+ 一、实现的功能 在使用java做界面实现输入功能的时候,使用了JTextField文本输入组件,希望能够根据用户的输入,自动提示,之后用户可以选择提示的内容作为文本框的输入内容,效果如下: 当…

Java中的阻塞队列使用以及详解

文章目录 一、Queue接口1. 常见方法以及功能(不具有阻塞队列特性)1.1 add(E e)1.2. offer():1.3. remove()1.4. poll()1.5. element()1.6. peek() 2. add和offer对比&#xff1a;3. remove和poll对比&#xff1a;4. element#peek方法对比&#xff1a; 二、BlockingQueue阻塞队列…

MySQL:子查询(全面详解)

MySQL&#xff1a;子查询 前言一、需求分析与问题解决1、实际问题2、子查询的基本使用3、子查询的分类 二、单行子查询1、单行比较操作符2、代码示例3、HAVING 中的子查询4、CASE中的子查询5、子查询中的空值问题6、非法使用子查询 三、多行子查询1、多行比较操作符2、代码示例…

Unity VR 开发教程 OpenXR+XR Interaction Toolkit(八)手指触控 Poke Interaction

文章目录 &#x1f4d5;教程说明&#x1f4d5;XR Poke Interactor&#x1f4d5;与 UI 进行触控交互⭐添加 Tracked Device Graphic Raycaster 和 XR UI Input Module 让 UI 可被交互 &#x1f4d5;与物体进行交互⭐XR Simple Interactable⭐XR Poke Filter 往期回顾&#xff1a…

偏爱console.log的你,肯定会觉得这个插件泰裤辣!

前言 毋庸置疑&#xff0c;要说前端调试代码用的最多的&#xff0c;肯定是console.log&#xff0c;虽然我现在 debugger 用的比较多&#xff0c;但对于生产环境、小程序真机调试&#xff0c;还是需要用到 log 来查看变量值&#xff0c;比如我下午遇到个场景&#xff1a;选择完…