7.1 图论基础
节点
边
应用:交通运输,社交网络,互联网,工作安排,脑区活动,程序状态执行
-
图的分类:有向图,无向图
后文讲解以无向图为主
无向图是一种特殊的有向图(两个节点相互指向),有向图由于其不对称型,演变出更多复杂的问题和专门针对有向图的算法(暂不涉及) -
图的分类:无权图,有权图
-
图的连通性
-
简单图:自环边,平行边。
7.2 图的表示
- 邻接矩阵:适合表示稠密图(Dense Graph),例如完全图
#ifndef DENSEGRAPH_H_INCLUDED
#define DENSEGRAPH_H_INCLUDED
#include<iostream>
#include<vector>
#include<cassert>
using namespace std;
//稠密图 -- 邻接矩阵
class DenseGraph{
private:
int n,m;
bool directed;
vector<vector<bool>> 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)
{
//严谨一些,先考虑v和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];
}
};
#endif // DENSEGRAPH_H_INCLUDED
- 邻接表:适合表示稀疏图(Sparse Graph)
#ifndef SPARSEGRAPH_H_INCLUDED
#define SPARSEGRAPH_H_INCLUDED
#include<iostream>
#include<vector>
#include<cassert>
using namespace std;
//稀疏图 —— 邻接表
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)
{
//严谨一些,先考虑v和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)//防止有自环边问题
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++)
if(g[v][i]==w)
return true;
return false;
}
};
#endif // SPARSEGRAPH_H_INCLUDED
7.3 相邻点迭代器
使用上述的思路其实就是遍历class里的二维数组g就可以得到一个点相邻的点,但这的前提是要把vector<vector<bool>> g
设为public,但其实,这样会让外部用户访问到g,有可能会泄露或修改数据,不够安全
如何在vector<vector<bool>> g
保持为private的情况下遍历一个点的相临点?
答:使用迭代器,用户通过迭代器找到图中一个结点的相邻接点
稀疏图
#ifndef SPARSEGRAPH_H_INCLUDED
#define SPARSEGRAPH_H_INCLUDED
#include<iostream>
#include<vector>
#include<cassert>
using namespace std;
//稀疏图 —— 邻接表
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)
{
//严谨一些,先考虑v和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)//防止有自环边问题
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++)
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();
}
};
};
#endif // SPARSEGRAPH_H_INCLUDED
稠密图
#ifndef DENSEGRAPH_H_INCLUDED
#define DENSEGRAPH_H_INCLUDED
#include<iostream>
#include<vector>
#include<cassert>
using namespace std;
//稠密图 -- 邻接矩阵
class DenseGraph{
private:
int n,m;
bool directed;
vector<vector<bool> > 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)
{
//严谨一些,先考虑v和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];
}
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();
}
};
};
#endif // DENSEGRAPH_H_INCLUDED
测试主函数
#include <iostream>
#include<stdlib.h>
#include<ctime>
#include"DenseGraph.h"
#include"SparseGraph.h"
using namespace std;
int main()
{
//首先生成一个图
int N =20;
int M = 100;
srand(time(NULL));
cout<<" ---Sparse Graph----"<<endl;
//Sparse Graph
SparseGraph g1(N,false);
for(int i=0;i<M;i++)
{
int a = rand()%N;
int b = rand()%N;
g1.addEdge(a,b);
}
//O(E)
for(int v=0;v<N;v++)
{
cout<<v<<" : ";
//对每个顶点生成寻找它相邻点的迭代器
SparseGraph::adjIterator adj(g1,v);
for(int w = adj.begin();!adj.end();w=adj.next())
cout<<w<<" ";
cout<<endl;
}
cout<<endl;
cout<<" ---Dense Graph----"<<endl;
DenseGraph g2(N,false);
for(int i=0;i<M;i++)
{
int a = rand()%N;
int b = rand()%N;
g2.addEdge(a,b);
}
//O(v^2)
for(int v=0;v<N;v++)
{
cout<<v<<" : ";
//对每个顶点生成寻找它相邻点的迭代器
DenseGraph::adjIterator adj(g2,v);
for(int w = adj.begin();!adj.end();w=adj.next())
cout<<w<<" ";
cout<<endl;
}
cout<<endl;
return 0;
}
可以看到对于稀疏图和稠密图的调用接口一样,可以任意传入稀疏图或者说稠密图图算法被封装,
7.4 图的算法框架
我们定义一个类readGraph读取txt文件里面的图
#ifndef READGRAPH_H_INCLUDED
#define READGRAPH_H_INCLUDED
#include<iostream>
#include<string>
#include<fstream>
#include<sstream>
#include<cassert>
using namespace std;
template<typename Graph>
class ReadGraph{
public:
ReadGraph(Graph &graph,const string &filename)
{
ifstream file(filename.c_str());
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++){
assert(getline(file,line));
stringstream ss(line);
int a,b;
ss>>a>>b;
assert(a>=0 && a< V);
assert(b>=0 && b< V);
graph.addEdge(a,b);
}
}
};
#endif // READGRAPH_H_INCLUDED
在稀疏图和稠密图里分别加入一个供测试使用的打印函数
稀疏图中
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;
}
}
稠密图中:
void show(){
for( int i = 0 ; i < n ; i ++ ){
for( int j = 0 ; j < n ; j ++ )
cout<<g[i][j]<<"\t";
cout<<endl;
}
}
测试
#include<iostream>
#include"SparseGraph.h"
#include"DenseGraph.h"
#include"ReadGraph.h"
using namespace std;
int main()
{
//打印稀疏图的邻接表
string filename = "testG1.txt";
SparseGraph g1(13,false);
ReadGraph<SparseGraph> readGraph1(g1,filename);
g1.show();
cout<<endl;
//打印稠密图的邻接矩阵
string filename2 = "testG2.txt";
DenseGraph g2(6,false);
ReadGraph<DenseGraph> readGraph2(g2,filename2);
g2.show();
return 0;
}
7.5 深度优先遍历和连通分量
#ifndef COMPONENT_H_INCLUDED
#define COMPONENT_H_INCLUDED
#include<iostream>
#include<cassert>
template <typename Graph>
class Component{
Graph &G;
bool *visited;
int ccount;//记录有多少个连通分量
int *id;
void dfs(int v)
{
visited[v]=true;
id[v]=ccount;
typename Graph::adjIterator adj(G,v);
for(int i=adj.begin();!adj.end();i=adj.next())
{
if(!visited[i])
dfs(i);//递归的方式进行深度优先遍历
}
}
public:
Component(Graph &graph):G(graph)
{
visited = new bool[G.V()];
id = new int[G.V()];
ccount = 0;
for(int i=0;i<G.V();i++)
{
visited[i]=false;
id[i]=-1;
}
for(int i=0;i<G.V();i++)
if(!visited[i]){
dfs(i);
ccount++;
}
}
~Component()
{
delete []visited;
delete []id;
}
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];
}
};
#endif // COMPONENT_H_INCLUDED
7.6 寻路
#ifndef PATH_H_INCLUDED
#define PATH_H_INCLUDED
#include<stack>
template<typename Graph>
class Path{
private:
Graph &G;
int s;
bool* visited;
int* from;
void dfs(int v)
{
visited[v]=true;
typename Graph::adjIterator adj(G,v);
for(int i=adj.begin();!adj.end();i=adj.next())
{
if(!visited[i])
{
from[i]=v;
dfs(i);//递归的方式进行深度优先遍历
}
}
}
public:
Path(Graph &graph,int s):G(graph)
{
//算法初始化
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);
}
~Path()
{
delete[] visited;
delete[] from;
}
bool hasPath(int w)
{
assert(w>=0 && w<G.V());
return visited[w];
}
void path(int w,vector<int> &vec)
{
stack<int> s;
int p = w;
while(p!=-1)
{
s.push(p);
p=from[p];
}
vec.clear();
while(!s.empty()){
vec.push_back(s.top());
s.pop();
}
}
void showPath(int w)
{
vector<int> vec;
path(w,vec);
for(int i=0;i<vec.size();i++)
{
cout<<vec[i];
if(i==vec.size()-1)
cout<<endl;
else
cout<<"==>";
}
}
};
#endif // PATH_H_INCLUDED
int main()
{
string filename = "testG2.txt";
SparseGraph g = SparseGraph(7,false);
ReadGraph<SparseGraph> ReadGraph(g,filename);
g.show();
cout<<endl;
Path<SparseGraph> dfs(g,0);
cout<<"DFS:";
dfs.showPath(6);
return 0;
}
7.7 广度优先遍历和最短路径
广度优先遍历求出了无权图的最短路径
#ifndef INC_07_BFS_AND_SHORTEST_PATH_SHORTESTPATH_H
#define INC_07_BFS_AND_SHORTEST_PATH_SHORTESTPATH_H
#include <vector>
#include <queue>
#include <stack>
#include <iostream>
#include <cassert>
using namespace std;
// 寻找无权图的最短路径
template <typename Graph>
class ShortestPath{
private:
Graph &G; // 图的引用
int s; // 起始点
bool *visited; // 记录dfs的过程中节点是否被访问
int *from; // 记录路径, from[i]表示查找的路径上i的上一个节点
int *ord; // 记录路径中节点的次序。ord[i]表示i节点在路径中的次序。
public:
// 构造函数, 寻找无权图graph从s点到其他点的最短路径
ShortestPath(Graph &graph, int s):G(graph){
// 算法初始化
assert( s >= 0 && s < graph.V() );
visited = new bool[graph.V()];
from = new int[graph.V()];
ord = new int[graph.V()];
for( int i = 0 ; i < graph.V() ; i ++ ){
visited[i] = false;
from[i] = -1;
ord[i] = -1;
}
this->s = s;
// 无向图最短路径算法, 从s开始广度优先遍历整张图
queue<int> q;
q.push( s );
visited[s] = true;
ord[s] = 0;
while( !q.empty() ){
int v = q.front();
q.pop();
typename Graph::adjIterator adj(G, v);
for( int i = adj.begin() ; !adj.end() ; i = adj.next() )
if( !visited[i] ){
q.push(i);
visited[i] = true;
from[i] = v;
ord[i] = ord[v] + 1;
}
}
}
// 析构函数
~ShortestPath(){
delete [] visited;
delete [] from;
delete [] ord;
}
// 查询从s点到w点是否有路径
bool hasPath(int w){
assert( w >= 0 && w < G.V() );
return visited[w];
}
// 查询从s点到w点的路径, 存放在vec中
void path(int w, vector<int> &vec){
assert( w >= 0 && w < G.V() );
stack<int> s;
// 通过from数组逆向查找到从s到w的路径, 存放到栈中
int p = w;
while( p != -1 ){
s.push(p);
p = from[p];
}
// 从栈中依次取出元素, 获得顺序的从s到w的路径
vec.clear();
while( !s.empty() ){
vec.push_back( s.top() );
s.pop();
}
}
// 打印出从s点到w点的路径
void showPath(int w){
assert( w >= 0 && w < G.V() );
vector<int> vec;
path(w, vec);
for( int i = 0 ; i < vec.size() ; i ++ ){
cout<<vec[i];
if( i == vec.size()-1 )
cout<<endl;
else
cout<<" -> ";
}
}
// 查看从s点到w点的最短路径长度
int length(int w){
assert( w >= 0 && w < G.V() );
return ord[w];
}
};
#endif //INC_07_BFS_AND_SHORTEST_PATH_SHORTESTPATH_H
复杂度与深度优先相同