图论 c++实现

本文介绍了图的概念,包括邻接矩阵和邻接表两种表示方法,以及无环图的定义。接着详细讲解了BFS和DFS遍历算法,包括伪代码和实现。此外,讨论了最短路径问题,如贝尔曼-福特算法和Dijkstra算法,以及如何在有向无环图(DAG)中寻找最短路径。最后,提到了最小生成树问题,介绍了Kruskal和Prim算法的实现。
摘要由CSDN通过智能技术生成

概述

图,是用来对对象之间的关系建模的数学结构。图是由由边连接起来的顶点组成的。
在这里插入图片描述

术语

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
circuit与cycle的区别:
circuit(简单回路):起点终点相同。
cycle(圈):起点到终点所经过的顶点除起点终点相同外,其余顶点各异,又称简单回路。
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
有向无环图
在这里插入图片描述

邻接矩阵

在这里插入图片描述

#pragma once
#include <iostream>

namespace simple_graph_adjmateix
{
	class simple_graph
	{
		int** vertex_matrix;
		int num_of_vertices, num_of_edges;
	public:
		simple_graph(int _num_of_vertices = 10)
			:num_of_vertices(_num_of_vertices)
		{
			num_of_edges = 0;
			vertex_matrix = new int* [_num_of_vertices];

			for (size_t i = 0; i < _num_of_vertices; i++)
			{
				vertex_matrix[i] = new int[_num_of_vertices];
			}

			for (size_t i = 0; i < num_of_vertices; i++)
			{
				for (size_t j = 0; j < num_of_vertices; ++j)
				{
					vertex_matrix[i][j] = INT_MAX;
				}
			}
		}

		~simple_graph()
		{
			for (size_t i = 0; i < num_of_vertices; i++)
			{
				delete[] vertex_matrix[i];
			}
			delete[] vertex_matrix;
		}

		void add_edge(int v1, int v2, int weight)
		{
			if (vertex_matrix[v1 - 1][v2 - 1] == INT_MAX)
				num_of_edges++;
			vertex_matrix[v1 - 1][v2 - 1] = weight;
		}

		void del_edge(int v1, int v2)
		{
			if (vertex_matrix[v1 - 1][v2 - 1] != INT_MAX)
				num_of_edges--;
			vertex_matrix[v1 - 1][v2 - 1] = INT_MAX;
		}

		void print_graph()
		{
			for (size_t i = 0; i < num_of_vertices; i++)
			{
				for (size_t j = 0; j < num_of_vertices; ++j)
				{
					if (vertex_matrix[i][j] != INT_MAX)
					{
						std::cout << vertex_matrix[i][j];
					}
					else
					{
						std::cout << "I";
					}
				}
				std::cout << std::endl;
			}
		}
	};
}

邻接表

在这里插入图片描述

#pragma once

#include <list>
#include <string>
#include <vector>
enum NODE_COLOR
{
	WHITE,
};

namespace simple_graph_adjlist
{
	struct vertex
	{
		int node_id;
		std::string value;
		std::vector< std::pair<vertex*, int> > adj_list;//链表

		NODE_COLOR color = WHITE;
		int distance = INT_MAX;
		vertex* predecessor = NULL;

		vertex() {}

		vertex(int _id, std::string _value = "")
			:node_id(_id), value(_value)
		{
		}

		void print_vertex()
		{
		}
	};

	class simple_graph
	{
		std::vector<vertex*> vertex_list;//储存所有的顶点
	public:
		simple_graph() {}
		~simple_graph() {}
		void add_node(int _node_id, std::string value = "")
		{
			for (auto& var : vertex_list)
			{
				if (var->node_id == _node_id)
				{
					throw;
				}
			}
			vertex_list.push_back(new vertex(_node_id, value));
		}
		void add_edge(int v1_id, int v2_id, int weight = 0)
		{
			vertex* v1 = NULL;
			vertex* v2 = NULL;

			bool found_v1 = false;
			bool found_v2 = false;

			//先找到两个顶点
			for (auto& var : vertex_list)
			{
				if (var->node_id == v1_id)
				{
					v1 = var;
					found_v1 = true;
				}

				if (var->node_id == v2_id)
				{
					v2 = var;
					found_v2 = true;
				}

				if (found_v1 && found_v2)
					break;
			}

			if (found_v1 && found_v2)
			{
				bool edge_already_exist = false;

				//遍历顶点v1判断是否已经连线
				for (auto& var : v1->adj_list)
				{
					if (var.first->node_id == v2_id)
					{
						edge_already_exist = true;
						throw;
						break;
					}
				}
				if (!edge_already_exist)
				{
					v1->adj_list.push_back(std::make_pair(v2, weight));
				}
			}
			else
			{
				throw;
			}
		}

		void print_graph() {}

		void print_nodes() {}
	};
}

稠密图与稀疏图

在这里插入图片描述

在这里插入图片描述

BFS

在这里插入图片描述
在这里插入图片描述

BFS伪代码

在这里插入图片描述
图示:
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

vertex* initialize_and_find_vertex(int vertex_id)
		{
			vertex* v1 = NULL;
			for (auto& vertex1:vertex_list)
			{
				vertex1->color = WHITE;
				vertex1->distance = -1;
				vertex1->predecessor = NULL;

				if (vertex1->node_id == vertex_id)
					v1 = vertex1;
			}
			return v1;
		}
		
void BFS(simple_graph& graph, int source_vertex_id)
	{
		std::queue<vertex*> q;
		vertex* v1 = graph.initialize_and_find_vertex(source_vertex_id);
		if (v1 == NULL)
		{
			throw std::runtime_error("error!!!!");
		}
		else
		{
			q.push(v1);
			while (!q.empty())
			{
				vertex* u = q.front();
				q.pop();
				for (auto& v : u->adj_list)
				{
					if (v.first->color == WHITE)
					{
						v.first->color = GRAY;
						v.first->distance = u->distance + 1;
						v.first->predecessor = u;
						q.push((vertex*)v.first);
					}
				}
				u->color = BLACK;
			}
		}
	}

DFS

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

void depth_first_search(vertex* v)
		{
			v->color = GRAY;
			std::cout << " ( " << v->node_id;
			for (auto& adj_vetex : v->adj_list)
			{
				if (adj_vetex.first->color = WHITE)
				{
					depth_first_search((vertex*)(adj_vetex.first));
				}
			}
			std::cout << v->node_id << " ) ";
			v->color = BLACK;
		}

void DFS(simple_graph& graph, int source_vertex_id = -1)
	{
		if (source_vertex_id != -1)
		{
			vertex* v1 = graph.initialize_and_find_vertex(source_vertex_id);
			if (v1 = NULL)
			{
				throw std::runtime_error("error!!!!");
			}
			else
			{
				graph.depth_first_search(v1);
				std::cout << std::endl;
			}
		}

		for (auto& v1 : graph.vertex_list)
		{
			if (v1->color == WHITE)
			{
				graph.depth_first_search(v1);
				std::cout << std::endl;
			}
		}
	}

在这里插入图片描述
图进行DFS会得到一棵DFS树(森林),在这个树上 才有了这些概念。对图进行DFS,可以从任意的顶点开始,遍历的方式也是多样的,所以不同的遍历会得到不同的DFS树,进而产生不同的树边,前向边,后向 边,横叉边。所以这4种边,是一个相对的概念。
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

分类

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
红色是树边,
紫色是后向边,
蓝色是前向边,
黑色是交叉边。

环探测

在这里插入图片描述

Topological sort拓扑排序

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

void depth_first_search(vertex* v)
		{
			//access_counter++;
			v->color = GRAY;
			//v->distance = access_counter;
			std::cout << " ( " << v->node_id;
			for (auto& adj_vetex : v->adj_list)
			{
				if (adj_vetex.first->color = WHITE)
				{
					adj_vetex.first->distance = v->distance + 1;
					adj_vetex.first->predecessor = v;
					depth_first_search((vertex*)(adj_vetex.first));
				}
				else if (adj_vetex.first->color == GRAY)
				{
					std::cout << " \n Cycle detected" << std::endl;
				}
			}
			std::cout << v->node_id << " ) ";
			v->color = BLACK;

			if (ADD_TO_PROCESSED_LIST)
				processed_list.push_front(v);
		}
		
void DFS(simple_graph& graph, int source_vertex_id = -1)
	{
		if (source_vertex_id != -1)
		{
			vertex* v1 = graph.initialize_and_find_vertex(source_vertex_id);
			if (v1 = NULL)
			{
				throw std::runtime_error("error!!!!");
			}
			else
			{
				graph.depth_first_search(v1);
				std::cout << std::endl;
			}
		}

		for (auto& v1 : graph.vertex_list)
		{
			if (v1->color == WHITE)
			{
				graph.depth_first_search(v1);
				std::cout << std::endl;
			}
		}
	}
	
void topological_sort(simple_graph& graph, int source_id)
	{
		graph.processed_list.clear();
		graph.ADD_TO_PROCESSED_LIST = true;
		DFS(graph, source_id);
		graph.ADD_TO_PROCESSED_LIST = false;

		for (auto& v : graph.processed_list)
		{
			v->print_vertex();
		}
	}

Strongly connected components

在这里插入图片描述
在一幅无向图中,如果有一条路径连接顶点v和w,则它们就是连通的;然后,在一幅有向图中,如果从顶点v有一条有向路径达到w,则顶点w是从顶点v可达的,但如果从w到达v的路径可能不存在。这两个顶点不是强连通的。

如果两个顶点互相可达,则它们是强连通的。如果一幅有向图中任意两个顶点都是强连通的,则这幅有向图也是强连通的。

有向图中的强连通性是一种顶点之间平等关系,有着以下性质:

自反性: 任意顶点和自己都是强连通的
对称性:如果v和w是强连通的,那么w和v也是强连通的
传递性:如果v和w是强连通的且如果w和x也是强连通的,那么v和x也是强连通的

强连通性将所有顶点分成了一些平等的部分,每个部分都是由相互均为强连通的顶点的最大子集组成的。这些子集称为强连通分量(强连通分支)
在这里插入图片描述
一个含有V个顶点的有向图可能有1~V个强连通分量;一个强连通图只含一个强连通分量;而一个有向无环图中含有V个强连通分量。

有向图的强连通分量和无向图的连通分量不同,因为有向图带有方向,情况略为复杂。
比如考虑上图,{ 0 , 1 , 2 } 和{ 4 , 5 , 6 , 7 } 是两个不同的强连通分量。但是从4有一条路径到另个一分量中的2,同时6也能到8,但是反过来2却不能到4,所以2和4属于不同的分量之中。
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

void topological_sort(simple_graph& graph, int source_id)
	{
		graph.processed_list.clear();
		graph.ADD_TO_PROCESSED_LIST = true;
		DFS(graph, source_id);
		graph.ADD_TO_PROCESSED_LIST = false;

		for (auto& v : graph.processed_list)
		{
			v->print_vertex();
		}
	}
	
void delete_transpose()
		{
			for (auto& var : grap_transpose_list)
			{
				delete[] var;
			}
		}
		
void strongly_connexted_components(simple_graph& graph)
	{
		graph.PRINT_WHILE_DFS = false;
		topological_sort(graph, -1);
		//获取转置图
		graph.get_transpose();
		graph.PRINT_WHILE_DFS = true;

		//按照顶点在已处理列表中的出现顺序选择顶点
		for (auto& var : graph.processed_list)//拓扑排序的点
		{
			for (auto& var2 : graph.grap_transpose_list)
			{
				if (var->node_id == var2->node_id && var2->color == WHITE)
				{
					graph.depth_first_search(var2);
					std::cout << std::endl;
					break;
				}
			}
		}
		std::cout << "End of printing component list" << std::endl;
		//当构造转置时,我们在堆上创建顶点对象
		//因此我们必须重新存储它,否则将发生内存泄漏
		graph.delete_transpose();
	}

最短路径问题

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

伪代码

在这里插入图片描述
举例:
在这里插入图片描述

贝尔曼福特算法

与迪杰斯特拉算法的区别:
迪杰斯特拉算法是借助贪心思想,每次选取一个未处理的最近的结点,去对与他相连接的边进行松弛操作;贝尔曼福特算法是直接对所有边进行N-1遍松弛操作。

迪杰斯特拉算法要求边的权值不能是负数;贝尔曼福特算法边的权值可以为负数,并可检测负权回路。

在这里插入图片描述

伪代码

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
但是注意如果图中有负权回路的话,最短路就不一定存在了!!!!
负权回路:在一个图里每条边都有一个权值(有正有负)。如果存在一个环(从某个点出发又回到自己的路径),而且这个环上所有权值之和是负数,那这就是一个负权环,也叫负权回路。存在负权回路的图是不能求两点间最短路的,因为只要在负权回路上不断兜圈子,所得的最短路长度可以任意小。

/**
vertex* initialize_and_find_vertex2(int vertex_id)
	{
		vertex* v1 = NULL;
		for (auto& vertex1 : vertex_list)
		{
			vertex1->color = WHITE;
			vertex1->distance = INT_MAX;
			vertex1->predecessor = NULL;

			if (vertex1->node_id == vertex_id)
			{
				v1 = vertex1;
				v1->distance = 0;
			}
		}
		return v1;
	}
 
void relax(vertex* u, vertex* v, int weight)
{
	if (v->distance > u->distance + weight)
	{
		v->distance = u->distance + weight;
		v->predecessor = u;
	}
}

bool sort_path_bellman_ford(simple_graph& graph, int source_vertex_id)
{
	vertex* v1 = graph.initialize_and_find_vertex2(source_vertex_id);

	if (v1 == NULL)
	{
		throw std::runtime_error("error!!!!");
	}
	else
	{
		//外部循环迭代V-1次
		int outer_loop_count = graph.vertex_list.size() - 1;
		for (int i = 0; i < outer_loop_count; ++i)
		{
			//遍历所有边
			for (auto& u : graph.vertex_list)
			{
				for (auto& v : u->adj_list)
				{
					if (u->distance != INT_MAX)
					{
						//调用relax操作
						relax(u, (vertex*)v.first, v.second);
					}
				}
			}
		}
		//检查负权重循环
		for (auto& u : graph.vertex_list)
		{
			for (auto& v : u->adj_list)
			{
				if (u->distance != INT_MAX)
				{
					if (v.first->distance > u->distance + v.second)
					{
						return false;
					}
				}
			}
		}
		return true;
	}
}

DAG最短路径

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

void sortest_path_topological(simple_graph& graph, int source_vertex_id)
	{
		//拓扑排序图
		topological_sort(graph, source_vertex_id);
		graph.initialize_and_find_vertex2(source_vertex_id);

		// 在拓扑排序数组上运行最短路径
	// 在这一点上抓住。Processed_list包含拓扑排序的顶点列表
		for (auto& u : graph.vertex_list)
		{
			for (auto& v : u->adj_list)
			{
				if (u->distance != INT_MAX)
				{
					//调用relax操作
					relax(u, (vertex*)v.first, v.second);
				}
			}
		}
	}

Dijkstra算法

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
因为优先队列
在这里插入图片描述

伪代码

在这里插入图片描述

void sortest_path_dijkstras(simple_graph& graph, int source_vertex_id)
	{
		vertex* v1 = NULL;
		v1 = graph.initialize_and_find_vertex2(source_vertex_id);
		//T表示已经确定的路径集
		std::vector<vertex*> T;

		bool* state_array = new bool[graph.vertex_list.size()];
		//初始化状态数组
		for (int i = 0; i < graph.vertex_list.size(); ++i)
		{
			state_array[i] = false;
		}
		//优先考虑的对象
		class prioritize
		{
		public:
			bool operator()(std::pair<vertex*, int>& p1,
				std::pair<vertex*, int>& p2)
			{
				return p1.second > p2.second;
			}
		};

		std::priority_queue
			<
			std::pair<vertex*, int>,
			std::vector< std::pair<vertex*, int>>,
			prioritize
			>pq;

		if (v1 == NULL)
		{
			throw std::runtime_error("error!!!!");
		}
		else
		{
			//将源顶点推到pq的起始位置
			pq.push(std::make_pair(v1,v1->distance));
	
			while (!pq.empty())
			{
				vertex* u = NULL;
				int vertex_count = 0;
				bool found_in_current_iter = false;

				//检查virtex是否已经被弹出
				while (!pq.empty())
				{
					u = pq.top().first;
					pq.pop();
					if (!state_array[u->node_id - 1])
					{
						state_array[u->node_id - 1] = true;
						found_in_current_iter = true;
						vertex_count++;
						break;
					}
				}

				if (!found_in_current_iter &&
					vertex_count == graph.vertex_list.size())
				{
					break;
				}
				//检查结束
				// 
				//添加u到已确定的路径列表
				T.push_back(u);

				for (auto& v : u->adj_list)
				{
					if (!state_array[v.first->node_id - 1] &&
						v.first->distance > u->distance + v.second)
					{
						v.first->distance = u->distance + v.second;
						v.first->predecessor = u;
						pq.push(std::make_pair(v.first, v.first->distance));
					}
				}
			}
		}
	}

最小生成树(Minimum spanning tree,MST)

上面的最小路径是有向
最小生成树是无向
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

Kruskal’s algorithm(并查集)

伪代码

在这里插入图片描述

int find_set(int* vertex_set, int node_id)
{
	return vertex_set[node_id - 1];
}

void union_sets(int v1_set, int v2_set, int* vertex_set, int n)
{
	for (int i = 0; i < n; ++i)
	{
		if (vertex_set[i] == v2_set)
		{
			vertex_set[i] == v1_set;
		}
	}
}

void mst_kruskals(simple_graph& graph)
{
	//包含顶点集的数组
	//数组的索引与vertex_id-1相同
	int* vertex_set = new int[graph.vertex_list.size()];
	class prioritize
	{
	public:
		bool operator()(edge* a, edge* b)
		{
			return a->weight > b->weight;
		}
	};
	std::priority_queue<edge*, std::vector<edge*>, prioritize> pq;

	//初始化集合
	for (auto& var1 : graph.vertex_list)
	{
		vertex_set[(var1->node_id) - 1] = var1->node_id;
		for (auto& var2 : var1->adj_list)
		{
			// 创建新的边缘并推到pq
			// 记住在不删除顶点对象的情况下声明内存
			pq.push(new edge(var1, var2.first, var2.second));
		}
	}

	std::vector<edge*> min_spaning_tree;
	// std::vector<edge*> ignord_list;
	int min_spaning_tree_cost = 0;

	while (!pq.empty())
	{
		edge* temp = pq.top();
		pq.pop();

		//获取该边的结束顶点所属的集合,去看并查集vertex_set
		int	v1_set = find_set(vertex_set, temp->start->node_id);
		int	v2_set = find_set(vertex_set, temp->end->node_id);

		//比较结束顶点集合
		if (v1_set != v2_set)
		{
			min_spaning_tree.push_back(temp);
			//将结束顶点所属的集合合并
			union_sets(v1_set, v2_set, vertex_set, graph.vertex_list.size());
			min_spaning_tree_cost += temp->weight;
		}
	}

	for (auto& var : min_spaning_tree)
	{
		std::cout << var->start->value << "---" << var->end->value << std::endl;
	}
	std::cout << "Total weight of MST is -" << min_spaning_tree_cost << std::endl;
}

Prims algorithm

在这里插入图片描述
在这里插入图片描述

void mst_prims(simple_graph& graph, int source_vertex_id)
{
	vertex* v1 = graph.initialize_and_find_vertex2(source_vertex_id);

	int* vertex_map = new int[graph.vertex_list.size()];
	//设置顶点初始值
	for (auto& var1 : graph.vertex_list)
	{
		for (auto& var2 : var1->adj_list)
		{
			vertex_map[var1->node_id - 1] = var1->node_id;
			vertex_map[var2.first->node_id - 1] = var2.first->node_id;
		}
	}
	// 我们的最小优先级队列,它将存储边
	class prioritize
	{
	public:
		bool operator()(edge* a, edge* b)
		{
			return a->weight > b->weight;
		}
	};
	std::priority_queue<edge*, std::vector<edge*>, prioritize> pq;

	//在迭代开始前推送离开源顶点的边
	for (auto& var : v1->adj_list)
	{
		// 记得回收边缘列表的内存分配
		// 但是不要删除顶点指针
		pq.push(new edge(v1, var.first, var.second));
	}

	std::vector<edge*> min_spaning_tree;
	int min_spaning_tree_cost = 0;

	while (!pq.empty())
	{
		edge* temp = pq.top();
		pq.pop();

		//获取该边的结束顶点所属的集合,去看并查集vertex_set
		int	v1_set = find_set(vertex_map, temp->start->node_id);
		int	v2_set = find_set(vertex_map, temp->end->node_id);

		//比较结束顶点集合
		if (v1_set != v2_set)
		{
			min_spaning_tree.push_back(temp);
			//如果条件传递v2总是单个顶点
			vertex_map[temp->end->node_id - 1] = v1_set;
			min_spaning_tree_cost += temp->weight;

			//和边缘离开
			for (auto& var : temp->end->adj_list)
			{
				pq.push(new edge(temp->end, var.first, var.second));
			}
		}
	}

	for (auto& var : min_spaning_tree)
	{
		std::cout << var->start->value << "---" << var->end->value << std::endl;
	}
	std::cout << "Total weight of MST is -" << min_spaning_tree_cost << std::endl;

}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

yhaida

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值