概述
图,是用来对对象之间的关系建模的数学结构。图是由由边连接起来的顶点组成的。
术语
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;
}