数据结构: 图
分类
图的连通性
图的表示
邻接矩阵
- 使用一个
二维矩阵NxN
来表示图
- 邻接矩阵适合 表示 稠密图
#pragma once
#ifndef GRAPH_DENSEGRAPH_H
#define GRAPH_DENSEGRAPH_H
#include "MyInclude.h"
class DenseGraph {
private:
int n, m; // 顶点和边数
bool directed; // 有向图还是无向图
vector<vector<bool>> g;
public:
DenseGraph(int n,int directed) {
this->n = n;
this->m = 0;
this->directed = directed;
// NxN
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);
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];
}
void show() {
for (int i = 0; i < n; i++) {
cout << "vertex " << i << ":\t";
for (int j = 0; j < n; j++) {
//if (g[i][j])
cout << g[i][j] << "\t";
}
cout << endl;
}
cout << endl;
}
class adjIterator {
private:
DenseGraph &G;
int v;
int index;
public:
adjIterator(DenseGraph &graph, int v) :G(graph) {
this->v = v;
this->index = 0;
}
int begin() {
// 返回第一个相邻的节点
index = -1;
return next();
}
int next() {
// 返回下一个相邻的节点
for (index += 1; index < G.V(); index++) {
if (G.g[v][index] == true) {
return index;
}
}
return -1;
}
bool end() {
// 返回是否已经达到末尾
return index >= G.V();
}
};
};
#endif // !GRAPH_DENSEGRAPH_H
邻接表
#pragma once
#ifndef GRAPH_SPARSEGRAPH_H
#define GRAPH_SPARSEGRAPH_H
#include "MyInclude.h"
class SparseGraph {
private:
int n, m; // 顶点和边数
bool directed; // 有向图还是无向图
// 使用链表实现,删除的话更有效
vector<vector<int>> g; // 存储是int类型,表示顶点编号[0,n-1]
public:
SparseGraph(int n, int 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);
if (hasEdge(v, w))
return;
g[v].push_back(w);
if (v!=w && !directed) { // v!=w 处理自环边
g[w].push_back(v);
}
m++;
}
// 判断是否有没有边
bool hasEdge(int v, int w) {
assert(v >= 0 && v < n);
assert(w >= 0 && w < n);
// 时间复杂度 O(n)
for (int i = 0; i < g[v].size(); i++) {
if (g[v][i] == 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 << g[i][j] << "\t";
}
cout << endl;
}
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;
}
int begin() {
// 返回第一个相邻的节点
index = 0;
if (G.g[v].size() != 0) {
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();
}
};
};
#endif // !GRAPH_SPARSEGRAPH_H
图的读取与创建
template< typename Graph>
class ReadGraph {
public:
// 注意:filename文件格式如下:
// 第一行表示图的 顶点数和边数
/*
6 8
0 1
0 2
0 5
1 2
1 3
1 4
3 4
3 5
*/
ReadGraph(Graph &graph, const string filename) {
ifstream file(filename);
string line;
int V, E;
assert(file.is_open());
assert(getline(file, line));
stringstream ss(line);
ss >> V >> E;
assert(V == graph.V());
for (int i = 0; i < E; i++) {
getline(file, line);
stringstream ss(line);
int v, w;
ss >> v >> w;
assert(v >= 0 && v < V);
assert(w >= 0 && w < V);
graph.addEdge(v, w);
}
}
};
#include "SparseGraph.h"
#include "DenseGraph.h"
#include "ReadGraph.h"
int main() {
string filename = "testG1.txt";
SparseGraph g1(13, false);
ReadGraph<SparseGraph> readGraph1(g1, filename);
g1.show();
DenseGraph g2(13, false);
ReadGraph<DenseGraph> readGraph2(g2, filename);
g2.show();
system("pause");
return 0;
}
图的遍历
迭代器
图的深度优先遍历
- DFS类似于树的先根遍历,尽可能深的去访问节点。
- 步骤1:访问节点,将这个节点标志为 已访问.
- 步骤2:如果节点还有未标记的邻接点,继续步骤1,否则返回。\
深度优先遍历求连通分量
代码如下:
#ifndef GRAPH_COMPONENT_H
#define GRAPH_COMPONENT_H
#include "MyInclude.h"
// 求图的连通分量!
#include "SparseGraph.h"
#include "DenseGraph.h"
template< typename Graph>
class Component {
private:
Graph &G;
bool *visited;
int ccount; // 计算有多少连通分量
int *id; // id表示每个顶点属于哪个组的,类似于并查集,每一个连通分量是一个组的
void dfs(int v) {
// 访问v
visited[v] = true;
id[v] = ccount; // 属于哪个连通分量的
// 对于v的每一个相邻点,进行深度优先遍历
typename Graph::adjIterator adj(G, v);
for (int i = adj.begin(); !adj.end(); i = adj.next()) {
if (!visited[i]) {
id[i] = v;
dfs(i);
}
}
}
public:
Component(Graph &graph) :G(graph){
visited = new bool[G.V()];
id = new bool[G.V()];
ccount = 0;
for (int i = 0; i < G.V(); i++) {
visited[i] = false; // 初始化为false
id[i] = -1; // 初始化为-1
}
for (int i = 0; i < G.V(); i++) {
// 若这个节点没有访问过,就访问
if (!visited[i]) {
dfs(i);// 深度优先遍历
ccount++;
}
}
}
int count() {
return ccount;
}
// 判断两个节点是否相连
bool isConnected(int v, int w) {
assert(v >= 0 && v < G.V());
assert(w >= 0 && w < G.V());
return id[v] == id[w];
}
~Component() {
delete[]visited;
}
};
#endif // !GRAPH_COMPONENT_H
#ifndef GRAPH_PATH_H
#define GRAPH_PATH_H
#include "MyInclude.h"
// 求图的路径
#include "SparseGraph.h"
#include "DenseGraph.h"
template< typename Graph>
class Path {
private:
Graph &G;
int s; // source
bool *visited; // 是否访问过
int *from; // 记录当前访问的节点是哪个节点那过来的
void dfs(int v) {
visited[v] = true;
typename Graph::adjIterator adj(G, v);
for (int j = adj.begin(); !adj.end(); j = adj.next()) {
if (!visited[j]) {
from[j] = v;
dfs(j);
}
}
}
public:
Path(Graph &graph,int s) :G(graph) {
// s表示source,源点
assert(s >= 0 && s < G.V());
// 算法初始化
visited = new bool[G.V()];
from = new int[G.V()];
for (int i = 0; i < G.V(); i++) {
visited[i] = false;
from[i] = -1;
}
this->s = s;
// 寻路算法
dfs(s);
}
// 从源点s,到w是否有路径
bool hasPath(int w) {
assert(w >= 0 && w < G.V());
return visited[w];
}
// 从源点s到w的具体路径是怎么样的
void path(int w, vector<int> &vec) {
if (hasPath(w)) {
stack<int> m_stack;
int p = w;
while (p != -1) {
m_stack.push(p);
p = from[p];
}
vec.clear();
while (!m_stack.empty()) {
vec.push_back(m_stack.top());
m_stack.pop();
}
}
}
// 打印路径,从源点s到w
void showPath(int w) {
vector<int> vec;
path(w, vec);
for (int i = 0; i < vec.size(); i++) {
if(i!=vec.size()-1){
cout << vec[i] << " --> ";
}
else {
cout << vec[i] << endl;
}
}
}
~Path() {
delete[]visited;
delete[]from;
}
};
#endif // !GRAPH_PATH_H
图的广度优先遍历
- BFS我们用队列来实现。
- 步骤1:如果队列为空,则返回,否则,取出队列头节点,将该节点标记为已访问。
- 步骤2:同时,将该节点所有未标记的邻接点,加入队列尾,继续步骤1。
利用队列遍历实现图广度优先遍历
#ifndef GRAPH_SHORTESTPATH_H
#define GRAPH_SHORTESTPATH_H
#include "MyInclude.h"
// 求最短路径,用的广度优先遍历
#include "SparseGraph.h"
#include "DenseGraph.h"
template< typename Graph>
class ShortestPath {
private:
Graph &G;
int s;
bool *visited; // 是否访问过
int *from; // 记录当前访问的节点是哪个节点那过来的
int *ord; // ord表示,s到每一个节点的最短距离
void bfs() {
queue<int> q;
q.push(s);
ord[s] = 0;
visited[s] = true;
while (!q.empty()) {
int v = q.front();
q.pop();
typename Graph::adjIterator adj(G, v);
for (int j = adj.begin(); !adj.end(); j = adj.next()) {
if (!visited[j]) {
q.push(j);
visited[j] = true;
from[j] = v;
ord[j] = ord[v] + 1; // 更新长度
}
}
}
}
public:
ShortestPath(Graph &graph, int s) :G(graph) {
// s表示source,源点
assert(s >= 0 && s < G.V());
// 算法初始化
visited = new bool[G.V()];
from = new int[G.V()];
ord = new int[G.V()];
for (int i = 0; i < G.V(); i++) {
visited[i] = false;
from[i] = -1;
ord[i] = -1;
}
this->s = s;
// 寻路算法
bfs();
}
bool hasPath(int w) {
assert(w >= 0 && w < G.V());
return visited[w];
}
// 返回最短路径是什么
int length(int w) {
assert(w >= 0 && w < G.V());
return ord[w];
}
// 从源点s到w的具体路径是怎么样的
void path(int w, vector<int> &vec) {
if (hasPath(w)) {
stack<int> m_stack;
int p = w;
while (p != -1) {
m_stack.push(p);
p = from[p];
}
vec.clear();
while (!m_stack.empty()) {
vec.push_back(m_stack.top());
m_stack.pop();
}
}
}
// 打印路径,从源点s到w
void showPath(int w) {
vector<int> vec;
path(w, vec);
for (int i = 0; i < vec.size(); i++) {
if (i != vec.size() - 1) {
cout << vec[i] << " --> ";
} else {
cout << vec[i] << endl;
}
}
}
~ShortestPath() {
delete[] visited;
delete[] from;
delete[] ord;
}
};
#endif // !GRAPH_SHORTESTPATH_H
参考文献
图的遍历