图(Graph)
定义
图(Graph)是由 顶点的有穷非空
集合 和 顶点之间边 的集合组成,通常表示为G(V, E)
,其中,G表示一个图,V是图G中顶点的集合,E是图G中边的集合。
图中的元素我们叫作顶点(Vertex),图中任意一个顶点可以和其他顶点建立连接关系,我们把这种建立的连接关系叫作边(Edge)。
图的各种相关概念
-
无向边:若顶点 v i v_i vi到顶点 v j v_j vj之间的边没有方向,则称这条边为无向边(Edge),用无序偶对( v i v_i vi, v j v_j vj)表示。
-
有向边:若顶点 v i v_i vi到顶点 v j v_j vj之间的边有方向。则称这条边为有向边,也称为弧(Arc),用有序偶对< v i v_i vi, v j v_j vj>表示。
-
简单图:不存在自环,且不存在平行边。
-
无向完全图:在无向图中,任意两个顶点之间都存在边。含有n个顶点的无向完全图有 n ∗ ( n − 1 ) 2 \frac{n*(n-1)}{2} 2n∗(n−1)条边。
-
有向完全图:在有向图中,任意两个顶点之间都存在方向互为相反的两条弧。含有n个顶点的有向完全图有 n ∗ ( n − 1 ) n*(n-1) n∗(n−1)条边。
-
顶点的度:跟顶点相连接的边的条数
-
入度:在有向图中,顶点的入度表示有多少条边指向这个顶点。
-
出度:在有向图中,顶点的出度表示有多少条边是以这个顶点为起点指向其他顶点。
-
网或带权图(weighted graph):在带权图中,每条边都有一个权重(weight)。
-
路径(path):从顶点 v i v_i vi到顶点 v j v_j vj的路径是一个顶点序列。路径的长度是路径上的边或弧的数目。序列中顶点不重复出现的路径称为简单路径。
-
回路或环(cycle):路径的顶点序列中第一个顶点和最后一个顶点相同。若除了第一个顶点和最后一个顶点之外,其余顶点不重复出现的回路叫作简单回路或简单环。
-
连通图:如果从顶点 v i v_i vi到顶点 v j v_j vj有路径,则称 v i v_i vi和 v j v_j vj是连通的,如果图中任意两个顶点之间都是连通的,则称该图为连通图。
-
连通分量:无向图的极大连通子图。
-
强连通图:在有向图G中,若对于每一对 v i v_i vi和 v j v_j vj,从 v i v_i vi到 v j v_j vj存在路径,且 v j v_j vj到 v i v_i vi也存在路径,则G是强连通图。邻接矩阵为对称矩阵。
-
强连通分量:有向图的极大强连通子图。
图的存储
邻接矩阵存储
稠密图适合用邻接矩阵存储。
class DenseGraph {
private:
int n, m; // 图的顶点数和边数
bool directed; // 是否为有向图
vector<vector<bool>> g; // 图g
public:
DenseGraph(int n, bool directed) {
this->n = n;
this->m = 0;
this->directed = directed;
for (int i = 0; i < n; i++)
g.push_back(vector<bool>(n, false));
}
~DenseGraph() {
}
int V() { return n; }
int E() { return m; }
void addEdge(int v, int w) {
assert(v >= 0 && v < n); // 断言,检查v是否在合理范围内
assert(w >= 0 && w < n);
if (hasEdge(v, w)) // 可以非常方便的处理平行边
return;
g[v][w] = true;
if (!directed)
g[w][v] = true;
m++;
}
bool hasEdge(int v, int w) {
assert(v >= 0 && v < n);
assert(w >= 0 && w < n);
return g[v][w];
}
class adjIterator { // 相邻结点的迭代器,作为外部遍历图的接口
private:
DenseGraph& G;
int v;
int index;
public:
adjIterator(DenseGraph& graph, int v) :G(graph) {
this->v = v;
this->index = -1;
}
int begin() {
index = -1;
return next();
}
int next() {
for (index += 1; index < G.V(); index++)
if (G.g[v][index])
return index;
return -1;
}
bool end() {
return index >= G.V();
}
};
};
邻接表存储
class SparseGraph
{
private:
int n, m;
bool directed;
vector<vector<int>> g;
public:
SparseGraph(int n, bool directed)
{
this->n = n;
this->m = 0;
this->directed = directed;
for (int i = 0; i < n; i++)
g.push_back(vector<int>());
}
~SparseGraph() {}
int V() { return n; }
int E() { return m; }
void addEdge(int v, int w)
{
assert(v >= 0 && v < n);
assert(w >= 0 && w < n);
// 处理平行边:如果每次addEdge时都判断是否存在v到w的边
// 那么时间复杂度为O(n),时间成本较高
// 因此,一般情况下,在addEdge函数中我们先不处理平行边
// 可以等整张图中的所有边都添加完之后,再综合处理所有平行边
if (v != w)
{ // 处理自环边
g[v].push_back(w);
if (!directed)
g[w].push_back(v);
m++;
}
}
bool hasEdge(int v, int w)
{
assert(v >= 0 && v < n);
assert(w >= 0 && w < n);
for (int i = 0; i < g[v].size(); i++) // 最坏情况时间复杂度为O(n)
if (g[v][i] == w)
return true;
return false;
}
class adjIterator
{
private:
SparseGraph& G;
int v;
int index;
public:
adjIterator(SparseGraph& graph, int v) : G(graph)
{
this->v = v;
this->index = 0;
}
int begin() {
index = 0;
if (G.g[v].size())
return G.g[v][index];
return -1;
}
int next() {
index++;
if (index < G.g[v].size())
return G.g[v][index];
return -1;
}
bool end() {
return index >= G.g[v].size();
}
};
};
带权图的存储
对于带权图我们需要增加一个边类。
template<typename Weight>
class Edge {
private:
int a, b;
Weight weight;
public:
Edge(int a, int b, Weight weight) {
this->a = a;
this->b = b;
this->weight = weight;
}
Edge(){}
~Edge(){}
int v() { return a; }
int w() { return b; }
Weight wt() { return weight; }
int other(int x) {
assert(x == a || x == b);
return x == a ? b : a;
}
};
带权图的邻接矩阵存储
template<typename Weight>
class DenseGraph {
private:
int n, m;
bool directed;
vector<vector<Edge<Weight> *>> g;
public:
DenseGraph(int n, bool directed) {
this->n = n;
this->m = 0;
this->directed = directed;
for (int i = 0; i < n; i++)
g.push_back(vector<Edge<Weight> *>(n, nullptr));
}
~DenseGraph() {
for (int i = 0; i < n; i++)
for (int j = 0; j < n; j++)
if (g[i][j] != nullptr)
delete g[i][j];
}
int V() { return n; }
int E() { return m; }
void addEdge(int v, int w, Weight weight ) {
assert(v >= 0 && v < n);
assert(w >= 0 && w < n);
if (hasEdge(v, w)) {// 可以非常方便的处理平行边
delete g[v][w];
if (!directed)
delete g[w][v];
m--;
}
g[v][w] = new Edge<Weight>(v, w, weight);
if (!directed)
g[w][v] = new Edge<Weight>(w, v, weight);
m++;
}
bool hasEdge(int v, int w) {
assert(v >= 0 && v < n);
assert(w >= 0 && w < n);
return g[v][w] != nullptr;
}
void show() {
for (int i = 0; i < n; i++) {
for (int j = 0; j < n; j++)
if (g[i][j])
cout << g[i][j]->wt() << "\t";
else
cout << "NULL\t";
cout << endl;
}
}
class adjIterator {
private:
DenseGraph& G;
int v;
int index;
public:
adjIterator(DenseGraph& graph, int v) :G(graph) {
this->v = v;
this->index = -1;
}
Edge<Weight>* begin() {
index = -1;
return next();
}
Edge<Weight>* next() {
for (index += 1; index < G.V(); index++)
if (G.g[v][index])
return G.g[v][index];
return nullptr;
}
bool end() {
return index >= G.V();
}
};
};
带权图的邻接表存储
template<typename Weight>
class SparseGraph
{
private:
int n, m;
bool directed;
vector<vector<Edge<Weight> *>> g;
public:
SparseGraph(int n, bool directed)
{
this->n = n;
this->m = 0;
this->directed = directed;
for (int i = 0; i < n; i++)
g.push_back(vector<Edge<Weight>*>());
}
~SparseGraph() {
for (int i = 0; i < n; i++)
for (int j = 0; j < g[i].size(); j++)
delete g[i][j];
}
int V() { return n; }
int E() { return m; }
void addEdge(int v, int w, Weight weight)
{
assert(v >= 0 && v < n);
assert(w >= 0 && w < n);
// 处理平行边:如果每次addEdge时都判断是否存在v到w的边
// 那么时间复杂度为O(n),时间成本较高
// 因此,一般情况下,在addEdge函数中我们先不处理平行边
// 可以等整张图中的所有边都添加完之后,再综合处理所有平行边
if (v != w)
{ // 处理自环边
g[v].push_back(new Edge<Weight>(v, w, weight));
if (!directed)
g[w].push_back(new Edge<Weight>(w, v, weight));
m++;
}
}
bool hasEdge(int v, int w)
{
assert(v >= 0 && v < n);
assert(w >= 0 && w < n);
for (int i = 0; i < g[v].size(); i++) // 最坏情况时间复杂度为O(n)
if (g[v][i]->other(v) == w)
return true;
return false;
}
void show() {
for (int i = 0; i < n; i++) {
cout << "vertex " << i << ":\t";
for (int j = 0; j < g[i].size(); j++)
cout << "( to: " << g[i][j]->w() << ", wt: " << g[i][j]->wt() << ")\t";
cout << endl;
}
}
class adjIterator
{
private:
SparseGraph& G;
int v;
int index;
public:
adjIterator(SparseGraph& graph, int v) : G(graph)
{
this->v = v;
this->index = 0;
}
Edge<Weight>* begin() {
index = 0;
if (G.g[v].size())
return G.g[v][index];
return nullptr;
}
Edge<Weight>* next() {
index++;
if (index < G.g[v].size())
return G.g[v][index];
return nullptr;
}
bool end() {
return index >= G.g[v].size();
}
};
};