【高阶数据结构】并查集和图

news2024/12/24 2:21:25

目录

1.数据结构--并查集

2.数据结构--图

1.图的基础概念

2.图的简单实现

2.1.邻接矩阵的图实现

2.2.邻接表的图实现

2.3.图的DFS和BFS

2.4.最小生成树

2.4.1.Kruskal(克鲁斯卡尔算法)

2.4.2.Prim(普里姆算法)

2.5.最短路径

2.5.1.Dijkstra(迪杰斯特拉算法)

2.5.2.Bellman-Ford(贝尔曼-福特算法)

2.5.3.Floyd-Warshall(弗洛伊德算法)


1.数据结构--并查集

概念:将n个不同的元素划分成一些不相交的集合

  1. 开始时,每个元素自成一个单元素集合;初始化
  2. 然后按一定的规律将归于同一组元素的集合合并;合并
  3. 在此过程中要反复用到查询某一个元素归属于那个集合的运算:查询                           适合于描述这类问题的抽象数据类型称为并查集(union-find set)

概念图:

实现:Count:遍历数组元素有多少个负数,就有几个集合

#pragma once
#include<iostream>
#include<vector>
#include<unordered_map>
using namespace std;

template<class T>
class UnionFindSet {
public:
	int Find(T val)
	{
		int index = _find_index[val];
		while (_v[index] >= 0)
			index = _v[index];
		return index;
	}
	bool Union(T x, T y)
	{
		int xi = Find(x);
		int yi = Find(y);
		//是否已经联合了
		if (xi == yi)
			return false;
		_v[xi] += _v[yi];
		_v[yi] = xi;
		return true;
	}
	//有多少个集合
	int Count()
	{
		int count = 0;
		for (auto e : _v)
		{
			if (e < 0)
				count++;
		}
		return count;
	}
public:
	UnionFindSet(const vector<T>& tmp)
	{
		for (int i = 0; i < tmp.size(); i++)
			_find_index[tmp[i]] = i;
		_v.resize(tmp.size(), -1);
	}
	~UnionFindSet()
	{}
private:
	//
	vector<T> _v;
	//哈希数组的数据和下标
	unordered_map<T, int> _find_index;
};

 测试:

#include<iostream>
#include"UnionFindSet.h"
#include<string>

using namespace std;

int main()
{
	vector<int> v{ 1,23,432,5345,8,712,44,534,645,73,862,3 };
	UnionFindSet<int> ufs(v);

	ufs.Union(1, 534);
	ufs.Union(1, 534);
	ufs.Union(73, 534);
	ufs.Union(1, 4);
	ufs.Union(23, 8);
	ufs.Union(862, 73);
	ufs.Union(3,3);
	cout << ufs.Count() << endl;
	return 0;
}

可以试试这个题:使用并查集做 

Leetcode:省份数量

class UnionFindSet{
    public:
    int Find(int x)
    {
        //找到下标
        int index = _find_index[x];
        while(_v[index] >= 0)
        {
            index = _v[index]  ;
        }
        return index;
    }

    bool Union(int x, int y)
    {
        int xi = Find(x);
        int yi = Find(y);
        //已经联合
        if(xi == yi)
            return false;
        _v[xi] += _v[yi];
        _v[yi] = xi;
        return true;
    }
    int Count()
    {
        int count = 0;
        for(auto e : _v)
        {
            if(e < 0)
                count++;
        }
        return count;
    }
    public:
    UnionFindSet(const vector<int>& v)
    {
        _v.resize(v.size(), -1);
        for(int i = 0; i<v.size();i++)
            _find_index[i] = i;
    }
    private:
    vector<int> _v;
    unordered_map<int, int> _find_index;
};

class Solution {
public:
    int findCircleNum(vector<vector<int>>& isConnected) {
        int n = isConnected.size();
        vector<int> v;
        for(int i =0; i<n; i++)
        {
            v.push_back(i);
        }
        UnionFindSet ufs(v);
        for(int i = 0; i<n; i++)
        {
            for(int j=0; j<n; j++)
            {
                if(isConnected[i][j] == 1)
                {
                    ufs.Union(i,j);
                }
            }
        }
        return ufs.Count();
    }
};

2.数据结构--图

1.图的基础概念

图(graph):由顶点(vertex)集合和顶点之间的关系(edge)构成,即G = (V, E); 

  1. 顶点:图内的任意顶点;
  2. 边:连接两个顶点;
  3. 边的权值:连接两个顶点的边的值;

无向图和有向图

  1. 无向图:两个顶点之间的边无方向,两个边的权值用一个数值表示 ; 所以为强关系图:例如:qq和微信的互相为好友关系
  2. 有向图:两个顶点之间的边有方向,代表的是一个顶点到另一个顶点的权值  ; 所以为弱关系图:例如  抖音关注主播,主播不会同时关注你

完全图:有n个顶点的无向图中,若有n * (n-1)/2条边,即任意两个顶点之间有且仅有一条边, 则称此图为无向完全图,比如上图G1在n个顶点的有向图中,若有n * (n-1)条边,即任意两个 顶点之间有且仅有方向相反的边,则称此图为有向完全图,比如上图G4。  

 顶点的度:顶点v的度是指与它相关联的边的条数,记作deg(v);。在有向图中,顶点的度等于该顶点的入度与出度之和,其中顶点v的入度是以v为终点的有向边的条数,记作indev(v);顶点v的出度 是以v为起始点的有向边的条数,记作outdev(v)。因此:dev(v) = indev(v) + outdev(v)。注意:对于无向图,顶点的度等于该顶点的入度和出度,即dev(v) = indev(v) = outdev(v)。

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

生成树:一个连通图的最小连通子图(整个图连通 ,任一顶点都可以找到另一顶点)称作该图的生成树。有n个顶点的连通图的生成树有n个顶点 和n-1条边。 

2.图的简单实现

2.1.邻接矩阵的图实现

优势:使用邻接矩阵可以快速查找(时间复杂度O(1))两个顶点是否有边且边的权值;劣势:查找顶点的度时(时间复杂度为O(N));

设计理念:有n个顶点,创建一个n*n的二维矩阵来放置两个顶点边的权值,为空可以使用一个权值的最大值或者最小值;

#pragma once
#include<iostream>
#include<string>
#include<vector>
#include<unordered_map>
#include<unordered_set>
#include<queue>
#include"UnionFindSet.h"

using namespace std;

//V顶点类型,W权值类型
namespace Matrix {
	template<class V, class W, bool Direction = false, W W_MAX = INT_MAX>
	class Graph {
	public:
		typedef Graph<V, W, Direction, W_MAX> Self;
		int Find(const V& v)
		{
			auto it = _find_index.find(v);
			if (it == _find_index.end())
				return -1;
			return it->second;
		}
		bool AddEdge(const V& src, const V& des, const W& weight)
		{
			int si = Find(src);
			int di = Find(des);
			//错误顶点
			if (si == -1 || di == -1)
				return false;
			_matrix[si][di] = weight;
			if (Direction == false)
				_matrix[di][si] = weight;
			return true;
		}
		void Print()
		{
			for (int i = 0; i < _matrix.size(); i++)
			{
				for (int j = 0; j < _matrix.size(); j++)
				{
					if (_matrix[i][j] == W_MAX)
						printf("%5c", '*');
					else
						printf("%5d", _matrix[i][j]);
				}
				cout << endl;
			}
		}
	public:
		Graph(const vector<V>& v)
		{
			int n = v.size();
			_vertexs.resize(n);
			//初始化顶点集合和  顶点和下标的映射
			for (int i = 0; i < n; i++)
			{
				_vertexs[i] = v[i];
				_find_index[v[i]] = i;
			}
			//初始邻接矩阵
			_matrix.resize(n, vector<W>(n, W_MAX));
		}
		Graph() = default;
		~Graph()
		{}
	private:
		//顶点集合
		vector<V> _vertexs;
		//查找顶点下标
		unordered_map<V, int> _find_index;
		vector<vector<W>> _matrix;

	};
}
void test()
{
    vector<string> a{ "张三", "李四", "王五", "赵六", "周七" };
    Matrix::Graph<string, int, false> g1(a);
    g1.AddEdge("张三", "李四", 100);
    g1.AddEdge("张三", "王五", 200);
    g1.AddEdge("王五", "赵六", 30);
    g1.AddEdge("王五", "周七", 30);
    g1.Print();

    g1.Print();
}
int main()
{
    test();
    return 0 ;
}

2.2.邻接表的图实现

优势:查找顶点的度时(时间复杂度为O(1),添加一个计数),更节省空间;劣势:使用邻接表查找两个顶点是否有边且边的权值(最差情况:时间复杂度O(N)),插入效率低:需要遍历链表,看是否已存在

设计理念:有n个顶点,创建有n个元素的指针矩阵,插入一个新边 遍历链表看是否存在;

namespace List {
	template<class W>
	class Node {
	public:
		int _des;
		//权值
		W _weight;
		Node<W>* _next;
		Node(int des, W w):_des(des),_weight(w),_next(nullptr)
		{}
	};
	template<class V, class W, bool Direction = false>
	class Graph{
	public:
		int Find(const V& v)
		{
			auto it = _find_index.find(v);
			if (it == _find_index.end())
				return -1;
			return it->second;
		}
		bool AddEdge(const V& src, const V& des, const W& weight)
		{
			//获取下标
			int si = Find(src);
			int di = Find(des);
			//不存在元素,fail;
			if (si == -1 || di == -1)
				return false;
			//邻接表添加边
			//数组指针是否为nullptr
			if (_table[si] == nullptr){
				//创建对象
				Node<W>* new_node = new Node<W>(di, weight);
				new_node->_next = _table[si];
				_table[si] = new_node;
				//无向图
				if (Direction == false) {
					Node<W>* new_node = new Node<W>(si, weight);
					new_node->_next = _table[di];
					_table[di] = new_node;
				}

			}
			else {//需要和邻接表内元素比较,是否已添加
				Node<W>* nd = _table[si];
				while (nd)
				{
					if (nd->_des == di)
						return false;
					else
						nd = nd->_next;
				}
				//还未添加过
				Node<W>* new_node = new Node<W>(di, weight);
				new_node->_next = _table[si];
				_table[si] = new_node;
				//无向图
				if (Direction == false) {
					Node<W>* new_node = new Node<W>(si, weight);
					new_node->_next = _table[di];
					_table[di] = new_node;
				}
			}
			return true;

		}
		void Print()
		{
			for (int i = 0; i < _table.size(); i++)
			{
				cout << _vertexs[i] << ": ";
				Node<W>* nd = _table[i];
				while (nd)
				{
					cout << _vertexs[nd->_des] << " " << nd->_weight << "   ";
					nd = nd->_next;
				}
				cout << endl;
			}
		}
	public:
		Graph(const vector<V>& v)
		{
			int n = v.size();
			_vertexs.reserve(n);
			for (int i = 0; i < n; i++)
			{
				_vertexs.push_back(v[i]);
				_find_index[v[i]] = i;
			}
			_table.resize(n, nullptr);
		}
	private:
		//顶点集合
		vector<V> _vertexs;
		//顶点和下标的哈希
		unordered_map<V, int> _find_index;
		//邻接表
		vector<Node<W>*> _table;
	};
}
void test()
{
    vector<string> a{ "张三", "李四", "王五", "赵六", "周七" };
    List::Graph<string, int, false> g1(a);
    g1.AddEdge("张三", "李四", 100);
    g1.AddEdge("张三", "王五", 200);
    g1.AddEdge("王五", "赵六", 30);
    g1.AddEdge("王五", "周七", 30);
    g1.Print();

    g1.Print();
}
int main()
{
    test();
    return 0 ;
}

2.3.图的DFS和BFS

我使用的是邻接表的图,来实现的,邻接矩阵也差不多,把代码插入List::graph类中就可以用了;

		void _DFS(int pos, vector<bool>& visited)
		{
			visited[pos] = true;
			Node<W>* nd = _table[pos];
			while (nd)
			{
				cout << _vertexs[pos] <<  _vertexs[nd->_des] << "  ";
				if (visited[nd->_des] == false)
					_DFS(nd->_des, visited);
				nd = nd->_next;
			}
		}
		void DFS(const V& v)
		{
			int pos = _find_index[v];
			vector<bool> visited(_table.size(), false);
			_DFS(pos, visited);

		}
		void BFS(const V& v)
		{
			int pos = _find_index[v];
			vector<bool> visited(_table.size(), false);
			queue<int> q;
			q.push(pos);
			while (!q.empty())
			{
				int size = q.size();
				while (size--)
				{
					pos = q.front();
					visited[pos] = true;
					q.pop();
					Node<W>* nd = _table[pos];
					while (nd)
					{
						cout << _vertexs[pos] << _vertexs[nd->_des] << "  ";
						if (visited[nd->_des] == false)
							q.push(nd->_des);
						nd = nd->_next;
					}
					
				}
			}
		}

2.4.最小生成树

后续5种算法都使用Matrix::Graph类

最小生成树通常使用的都是无向图;

  • 最小生成树:有n个顶点,使用n-1条边,使用所有顶点连通,那么肯定是不构成回路的(构成回路不可能所有连通);
2.4.1.Kruskal(克鲁斯卡尔算法)

贪心思想:

  1. 将所有边加入优先级队列;
  2. 拿出堆顶边(最小),使用并查集来判断是否构成回路;
  3. 每一条边都有两个顶点,将边的两个顶点使用并查集合并;
  4. 可能   不能构成最小生成树,所以结束条件为优先级队列不为空;
  5. 使用一个变量记录有多少边,最后为n-1条就是最小生成树;

并查集代码:在最上面

        //贪心思想,选权值最小的边,使用并查集判断是否构成回路
		W Kruskal(Self& minTree)
		{
			//初始化生成树
			int n = _vertexs.size();
			minTree._vertexs = _vertexs;
			minTree._find_index = _find_index;
			minTree._matrix.resize(n, vector<W>(n, W_MAX) );

			//优先级队列保存边
			priority_queue<Edge<W>, vector<Edge<W>>, greater<Edge<W>>> pq;

			//添加所有边进优先级队列
			for (int i = 0; i < n; i++)
			{
				for (int j = i + 1; j < n; j++)
				{
					if (_matrix[i][j] != W_MAX)
						pq.push(Edge<W> (i, j, _matrix[i][j]) );
				}
			}

			//有n-1条边
			int count = 0;
			UnionFindSet<V> ufs(_vertexs);
			int totalW = 0;
			while (!pq.empty())
			{
				Edge<W> eg= pq.top();
				pq.pop();
				bool ret = ufs.Union(_vertexs[eg._src], _vertexs[eg._des]);
				if (ret == true){//不构成回路
					cout << _vertexs[eg._src] << ' ' << _vertexs[eg._des] << ' ' << eg._weight << endl;
					minTree.AddEdge(_vertexs[eg._src], _vertexs[eg._des], eg._weight);
					count++;
					totalW += eg._weight;
				}
				else {//
					cout << "构成环  " << _vertexs[eg._src] << ' ' << _vertexs[eg._des] << ' ' << eg._weight << endl;
				}
			//构成的总权值
			}
			if (count == n - 1)//能构成生成树
				return totalW;
			else//不能构成
				return W();
		}

测试代码:

void TestGraphMinTree()
{
	const char* ch = "abcdefghi";
	vector<char> v;
	for (int i = 0; i < strlen(ch); i++)
	{
		v.push_back(ch[i]);
	}
	Matrix::Graph<char, int> g(v);
	g.AddEdge('a', 'b', 4);
	g.AddEdge('a', 'h', 8);
	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);

	Matrix::Graph<char, int> kminTree;
	cout << "Kruskal:" << g.Kruskal(kminTree) << endl;
	kminTree.Print();
}
 2.4.2.Prim(普里姆算法)

贪心思想:

  1. 确定一个顶点,将这个顶点的所有边加入优先级队列,这个顶点被设置为已使用;
  2. 拿出堆顶的边,看两个顶点是否已使用,已使用代表构环;
  3. 不构环将这个顶点的所有边,插入优先级队列,这个顶点被设置为已使用;
  4. 重复这个过程,我使用哈希表来判断顶点的,使用一个bool的数组应该更好;
  5. 使用一个变量记录有多少边,最后为n-1条就是最小生成树;
        //贪心思想,按已选择的点延伸,使用set保存已使用,不使用使用过的就一定不会构环
		//不使用并查集是因为已使用节点都是连通的
		W Prim(Self &minTree, const V& val)
		{
			int n = _vertexs.size();
			//初始化mintree
			minTree._vertexs = _vertexs;
			minTree._find_index = _find_index;
			minTree._matrix.resize(n, vector<W>(n, W_MAX));
			
			int pos = _find_index[val];
			//保存已使用
			unordered_set<V> visited;
			visited.insert(pos);

			priority_queue<Edge<W>, vector<Edge<W>>, greater<Edge<W>> > pq;//
			for (int j = 0; j < n; j++) {
				if(_matrix[pos][j] != W_MAX)
				pq.push(Edge<W>(pos, j, _matrix[pos][j]));
			}
			int count = 0;//边数量
			W TotalW = 0;//权值大小
			while (!pq.empty())
			{
				//获取当前最小
				Edge<W> dg = pq.top();
				pq.pop();
				int des = dg._des;
				if(visited.count(des))//存在,不可用
					cout << "构成环  " << _vertexs[dg._src] << ' ' << _vertexs[dg._des] << ' ' << dg._weight << endl;
				else {
					for (int j = 0; j < n; j++)//添加新边
					{
						if(_matrix[des][j] != W_MAX)
						pq.push(Edge<W>(des, j, _matrix[des][j]));
					}
					visited.insert(des);//已使用
					minTree._matrix[dg._src][dg._des] = dg._weight;//生成树添边
					TotalW += dg._weight;
					count++;
					cout << _vertexs[dg._src] << ' ' << _vertexs[dg._des] << ' ' << dg._weight << endl;
				}


			}
			if (count == n - 1)
				return TotalW;
			else
				return W();
		}

测试代码: 

 

void TestGraphMinTree()
{
	const char* ch = "abcdefghi";
	vector<char> v;
	for (int i = 0; i < strlen(ch); i++)
	{
		v.push_back(ch[i]);
	}
	Matrix::Graph<char, int> g(v);
	g.AddEdge('a', 'b', 4);
	g.AddEdge('a', 'h', 8);
	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);

	Matrix::Graph<char, int> kminTree;
	cout << "Prim:" << g.Prim(kminTree, 'a') << endl;
	kminTree.Print();
}

2.5.最短路径

最短路径通常使用的都是有向图;

2.5.1.Dijkstra(迪杰斯特拉算法)

贪心思想:此算法的缺点:只能用做不带负权的图(负权就是小于0),因为不带负权已确定的路径值不可能更小(一个数加正数不可能更小);优势:性能最好(时间:O(N^2),邻接矩阵)

  1. 根据传入的顶点,将这个将他的路径值设为0;
  2. 进行遍历找到路径值最小且未使用的顶点,顶点设为已使用
  3. 遍历这个min顶点的所有边,如果这个min顶点路径值+这条边的值  < 目标顶点的路径值,更新目标顶点的路径值和  它的父亲为 这个min顶点;
  4. 循环此过程;
void Dijkstra(const V& v, vector<W>& dest, vector<int>& parentPath )
		{
			int n = _vertexs.size();
			//起始点下标
			int src = _find_index[v];
			//和初始点的最短路径
			dest.resize(n, W_MAX);
			//顶点父亲下标
			parentPath.resize(n, -1);

			dest[src] = W();//起始点置为0
			vector<bool> visited(n, false);
			//执行n次
			for (int i = 0; i < n; i++)
			{
				int minW = W_MAX, minI = src;
				//找到当前最小顶点且未使用
				for (int j = 0; j < n; j++)
				{
					if (visited[j] == false && dest[j] < minW)
					{
						minI = j;
						minW = dest[j];
					}
				}
				//设为已使用的值,不可能更小
				visited[minI] = true;

				//延伸是否能找到更小顶点权值
				for (int j = 0; j < n; j++)
				{
					//满足未使用,两个顶点右边
					if (visited[j] == false && _matrix[minI][j] != W_MAX)
					{
						//新路径值更小
						if (dest[minI] + _matrix[minI][j] < dest[j])
						{
							//设置路径值和父顶点下标
							dest[j] = dest[minI] + _matrix[minI][j];
							parentPath[j] = minI;
						}
					}
				}
			}
		}

测试代码:

void TestGraphDijkstra()
{
	const char* ch = "syztx";
	vector<char> v;
	for (int i = 0; i < strlen(ch); i++)
	{
		v.push_back(ch[i]);
	}
	Matrix::Graph<char, int, true, INT_MAX > g(v);
	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> dest;
	vector<int> parentPath;
	g.Dijkstra('s', dest, parentPath);
}
2.5.2.Bellman-Ford(贝尔曼-福特算法)

暴力思想:将邻接矩阵遍历n-1次;时间复杂度:O(N^3),优势:解决带负权的图问题

  1. 将传入的顶点的路径值设为0;
  2. 遍历整个邻接矩阵,当前顶点不为初始值且两者有边,当前顶点路径值 + 边的权值  < 目标顶点路径值,更新目标顶点的路径值和父亲;
  3. 遍历n - 1次,每个顶点最多有n - 1条边(除自己),如果一次遍历没有顶点路径值变小,结束循环;
  4. 如果带负权回路,可以无限小,没有结果,return false;
        bool BellmanFord(const V& v, vector<W>& dest, vector<int>& parentPath)
		{
			int n = _vertexs.size();
			int src = _find_index[v];//初始位置

			dest.resize(n, W_MAX);
			parentPath.resize(n, -1);

			//初始化初始位置
			dest[src] = 0;

			//为什么是n-1次:任意顶点到其他所有顶点最多n-1次,再多说明构成负权回路
			for (int k = 0; k < n - 1; k++)
			{
				bool quit = true;
				//遍历权值邻接矩阵
				for (int i = 0; i < n; i++)
				{
					for (int j = 0; j < n; j++)
					{
                        //有边且本身不为初始值
						if (_matrix[i][j] != W_MAX && dest[i] != W_MAX && dest[i] + _matrix[i][j] < dest[j])
						{
							//设置更小的路径值和父顶点下标
							dest[j] = dest[i] + _matrix[i][j];
							parentPath[j] = i;
							quit = false;
						}
					}
				}
				if (quit)
					break;
			}

			//判断是否构成负权回路
			for (int i = 0; i < n; i++)
			{
				for (int j = 0; j < n; j++)
				{
					if (_matrix[i][j] != W_MAX && dest[i] != W_MAX && dest[i] + _matrix[i][j] < dest[j])
						return false;
				}
			}
			return true;
		}

测试代码:

void TestGraphBellmanFord()
{
	const char* ch = "syztx";
	vector<char> v;
	for (int i = 0; i < strlen(ch); i++)
	{
		v.push_back(ch[i]);
	}
	Matrix::Graph<char, int, true, INT_MAX> g(v);
	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))
	{
	}
	else
	{
		cout << "存在负权回路" << endl;
	}
}
2.5.3.Floyd-Warshall(弗洛伊德算法)

动态规划思想:时间复杂度:O(N^3);可以算出所有节点为起始点的最短路径;

  1. 初始化路径值1.自己初始化为0   2.两顶点存在边初始化为边的权值;
  2. 顶点i 到 j 存在一个顶点k,满足Edge(i, k) + Edge(k, j)  < Edge(i, j);说明有更小的路径值;
		void FloydWarShall(vector<vector<W>>& vvDest, vector<vector<int>>& vvParentPath)
		{
			int n = _vertexs.size();
			vvDest.resize(n);
			vvParentPath.resize(n);
			//初始化
			for (int i = 0; i < n; i++)
			{
				vvDest[i].resize(n, W_MAX);
				vvParentPath[i].resize(n, -1);
			}
			//直接相连的顶点添加入二维数组
			for (int i = 0; i < n; i++)
			{
				for (int j = 0; j < n; j++)
				{
					if (_matrix[i][j] != W_MAX)
					{
						vvDest[i][j] = _matrix[i][j];
						vvParentPath[i][j] = i;
					}
					else
					{
						vvParentPath[i][j] = -1;
					}
					if (i == j) 
					{
						vvParentPath[i][j] = -1;
						vvDest[i][j] = 0;
					}
				}
			}
			
			//动规思想:i 到 j之间存在一个k,并且i到k + k到j  < i 到 j
			for (int k = 0; k < n; k++)
			{
				for (int i = 0; i < n; i++)
				{
					for (int j = 0; j < n; j++)
					{
						if (vvDest[i][k] != W_MAX && vvDest[k][j] != W_MAX && vvDest[i][k] + vvDest[k][j] < vvDest[i][j])
						{
							
							vvDest[i][j] = vvDest[i][k] + vvDest[k][j];
							vvParentPath[i][j] = vvParentPath[k][j];
						}
					}
				}
			}

		}

测试代码:

void TestFloydWarShall()
{
	const char* ch = "12345";
	vector<char> v;
	for (int i = 0; i < strlen(ch); i++)
	{
		v.push_back(ch[i]);
	}
	Matrix::Graph<char, int, true, INT_MAX> g(v);
	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);

}

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

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

相关文章

非遗主题网站的设计与实现基于PHP实现

包括源码参考论文 下载地址: https://juzhendongli.store/commodity/details/18

k8s-----26、细粒度权限管理 RBAC

0、导读 每一个用户对API资源进行操作都需要通经过以下三个步骤: 第一步:对客户端访问进行认证操作,确认是否具有访问k8s权限(也就是通过serviceaccount) token(共享秘钥) SSL(双向SSL认证) …通过任何一个认证即表示认证通过,进入下一步第二步:授权检查,确认是否对资源…

openpnp - SlotSchultzFeeder source code bugfix

文章目录 openpnp - SlotSchultzFeeder source code bugfix概述笔记openpnp源码调试环境排查思路开git分支查到的问题 - 1查到的问题 - 2查到的问题 - 3针对以上问题进行的逻辑修正D:\my_openpnp\openpnp_github\src\main\java\org\openpnp\machine\reference\driver\wizards\G…

Redis快速上手篇(六)主从复制

主从复制 主机数据更新后根据配置和策略&#xff0c; 自动同步到备机的master/slaver机制&#xff0c;Master以写为主&#xff0c;Slave以读为主 读写分离&#xff0c;性能扩展&#xff08;主 写 从 读&#xff09; 容灾快速恢复 一主二仆 拷贝多个redis.conf文件include(写绝…

动态规划太难了?是你没有找对方法,四题带你搞懂动态规划!

&#x1f4af; 博客内容&#xff1a;动态规划刷题 &#x1f600; 作  者&#xff1a;陈大大陈 &#x1f680; 个人简介&#xff1a;一个正在努力学技术的准前端&#xff0c;专注基础和实战分享 &#xff0c;欢迎私信&#xff01; &#x1f496; 欢迎大家&#xff1a;这里是CS…

【RV1106/RV1103】RV1103增加RTL8723BS

文章目录 Kernel 部分支持配置 menuconfigWiFi 功能支持Bluetooth 功能支持 原SDK的Busybox配置BT 部分文件系统部分蓝牙测试dbus 安装测试遇到的问题让hci0出来 使用hcitool来操作 Kernel 部分支持 配置 menuconfig WiFi 功能支持 Bluetooth 功能支持 正基和海华的模块都使用…

pip 更换源

方案1 在C盘用户名录下新建pip文件夹&#xff0c;里面包含pip.ini文件 方案2 在C盘用户名目录的AppData的Roaming下新建pip文件夹&#xff0c;里面包含pip.ini文件。 内容为 [global] index-url https://pypi.tuna.tsinghua.edu.cn/simple

题目 1056: 二级C语言-温度转换(python详解)——练气四层初期

✨博主&#xff1a;命运之光 &#x1f984;专栏&#xff1a;算法修炼之练气篇&#xff08;C\C版&#xff09; &#x1f353;专栏&#xff1a;算法修炼之筑基篇&#xff08;C\C版&#xff09; &#x1f352;专栏&#xff1a;算法修炼之练气篇&#xff08;Python版&#xff09; ✨…

CVE-2022-22965 Spring Framework远程命令执行

0x01 影响版本 Spring Framework < 5.3.18 Spring Framework < 5.2.20 JDK>9 0x02 复现环境 vulhub/spring/cve-2022-22965 0x03 漏洞复现 首先docker-compose up -d开启靶场 输入payload <%if("j".equals(request.getParameter("pwd")…

Transformer在计算机视觉领域的研究综述

论文地址&#xff1a;https://kns.cnki.net/kcms/detail/11.2127.TP.20221009.1217.003.html 目录 摘 要 1. Transformer 基本结构 1.1 位置编码 (1) 绝对位置编码 (2) 相对位置编码 1.2 自注意力机制 (1) 多头注意力 (2) 局部注意力 (3) 稀疏注意力机制 1.3 前馈神…

通信仿真软件SystemView安装教程(超详细)

介绍 system view是一种电子仿真工具。它是一个信号级的系统仿真软件&#xff0c;主要用于电路与通信系统的设计和仿真&#xff0c;是一个强有力的动态系统分析工具&#xff0c;能满足从数字信号处理&#xff0c;滤波器设计&#xff0c;直到复杂的通信系统等不同层次的设计&am…

SQL注入类型(详细讲解)

SQL注入方法(函数原理讲解) 前言 在进行SQL注入测试的时候&#xff0c;确实很重要要知道目标SQL语句是什么类型&#xff0c;因为不同类型的SQL语句对注入的有效载荷&#xff08;payload&#xff09;有不同的语法要求。下面列举了几个原因&#xff1a; 1. 语法闭合 你提到了…

“可编程网络”的基础概念介绍

什么是可编程网络&#xff1f; 可编程网络是指网络设备的行为和流量控制由独立于网络硬件运行的软件处理的网络。可编程网络的基本性质是将底层物理硬件与设备的控制软件分开。 网络可编程的历史 当可编程网络的概念首次出现时&#xff0c;它是计算机网络发展史上革命性的一…

Specializing Smaller Language Models towards Multi-Step Reasoning论文精读

0 Abstract 普遍认为&#xff0c;LLM涌现出来的few-shot learning能力是超大参数模型独有的&#xff08;>100B&#xff09;【emergent abilities】&#xff1b;作者认为&#xff0c;小模型&#xff08;<10B&#xff09;可以将这些能力从大模型&#xff08;>100B&…

【HTML/CSS学习】margin和padding的区别

1. margin margin&#xff08;外边距&#xff09;属性定义元素周围的空间。 margin主要在元素的外部创建空白区域&#xff0c;用于分隔元素与其相邻元素之间的距离。 用处&#xff1a;可以用于调整两个元素之间的间隔。 2. padding padding&#xff08;填充&#xff09;定义…

dcrcms 文件上传漏洞

dcrcms 文件上传漏洞 启动环境 启动靶场得到ip和映射端口 测试漏洞 输入http://10.9.47.6:14153/dcr进入后台 账号密码为admin:123456 寻找文件上传漏洞 看到添加新闻里有文件上传我们尝试进行上传 写一个php文件里面放入一句话木马 <?php eval($_REQUEST[666]); ?…

C++前缀和算法的应用:用地毯覆盖后的最少白色砖块 原理源码测试用例

本文涉及的基础知识点 C算法&#xff1a;前缀和、前缀乘积、前缀异或的原理、源码及测试用例 包括课程视频 题目 给你一个下标从 0 开始的 二进制 字符串 floor &#xff0c;它表示地板上砖块的颜色。 floor[i] ‘0’ 表示地板上第 i 块砖块的颜色是 黑色 。 floor[i] ‘1…

警务可视化 玫瑰图和柱折混合图

什么时候用&#xff0c;什么时候导入echarts 完整代码&#xff1a; 搭建页面结构的代码 html <template><h2>智慧公安数字服务系统</h2><div><div class"container1"> </div> <div class"container2"></di…

【golang】Windows环境下Gin框架安装和配置

Windows环境下Gin框架安装和配置 我终于搞定了Gin框架的安装&#xff0c;花了两三个小时&#xff0c;只能说道阻且长&#xff0c;所以写下这篇记录文章 先需要修改一些变量&#xff0c;这就需要打开终端&#xff0c;为了一次奏效&#xff0c;我们直接设置全局的&#xff1a; …

vue3后台管理系统

项目创建及代码规范化开发 vue脚手架创建项目 安装vue脚手架 npm install-g vue/cli npm update -g vue/cli终端输入vue create 项目名称 即可进入模板选择 //利用vue-cli创建项目 进入模板选择 Vue CLI v5.0.8 ? Please pick a preset:Default ([Vue 3] babel, eslint)De…