参考:https://blog.youkuaiyun.com/j_oop/category_10048144.html
https://blog.youkuaiyun.com/j_oop/category_10048144.html
1.图论基础
一个图由顶点集(V)和边集(E)组成,每一条边就是一个点对(v,w),其中 v,w∈V。如果点对是有序的(即v,w与w,v不同),那么图就是有向图,否则称作无向图(无向图是特殊的有向图)。如果在边集中允许出现(w,w)这种一个顶点的点对,这种边称为自环边。如果边集中允许出现多组相同的点对,这样的边称为平行边。
2.图的表示和相邻点迭代器
图的表示实际上就是对图中边的存储,每条边由两个顶点组成
(1)邻接矩阵:假设图中有n个顶点,那么图中的边有(nn)种可能,使用nn的二维矩阵(bool[][])来存储边,每一行(列)表示当前顶点与图中所有其他顶点之间的邻接关系,[i][j]为true表示图中存在点对(i,j)的边,否则表示不存在。由于在二维矩阵中[i][j]有且只有一个位置与之对应,因此邻接矩阵的实现消除了平行边
(2)邻接表:假设图中有n个顶点,使用n个int数组存储与当前顶点邻接的所有点
在实际应用中,对于稠密图一般使用邻接矩阵来实现,对于稀疏图一般使用邻接表来实现。(判断是稠密图还是稀疏图的标准是每个顶点的实际连接数与理论最大连接数相比)。
代码实现如下:
DenseGraph(稠密图)
#include <iostream>
#include <vector>
#include <cassert>
using namespace std;
// 稠密图,使用邻接矩阵实现(不存在平行边,允许存在自环边)
class DenseGraph {
private:
int n, m;//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++) {//这个vevtor里含有n个元素,每个元素都是false,
g.push_back(vector<bool>(n, false));//1Xn,所以再push到g第一个vec里就是二维的;
}
}
~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++) {
for (int j = 0; j < n; j++) {
cout << g[i][j]<<"\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;//因为稠密图是由01组成的
}
int begin() {
index = -1;
return next();
}
int next() {//+= 1从第0个数开始找第一个1
for (index += 1; index < G.V(); index++)
if (G.g[v][index])
return index;
return -1;
}
bool end() {
return index >= G.V();
}
};
};
SparseGraph(稀疏图)
#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) {
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);
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;
}
}
// 稀疏图的迭代器
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();
}
};
};
3.图的深度优先遍历和联通分量:
基本思路:递归遍历图中指定顶点的邻接顶点,用bool数组标记图中顶点是否已被遍历过。深度遍历可用于求图的联通分量,借助于联通分量我们又可以求得两顶点是否存在路径,使用int数组记录每个顶点所属的联通分量。
#include <iostream>
#include <cassert>
using namespace std;
template <typename Graph>
class Component{
private:
Graph &G;
bool *visited;
int ccount;//记录连通分量个数
int *id;//看节点是否相连
void dfs( int v ){
visited[v] = true;
id[v] = ccount;
typename Graph::adjIterator adj(G, v);//typename 是为了说明adjIterator是Graph中的一个类型
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];
}
};
4.寻路
在遍历的基础上,我们可以使用一个int数组来记录当前顶点是由哪个顶点遍历而来,即int* from,from[i]=v表示在当前图的遍历中,顶点i是在遍历顶点v的邻接顶时被访问到。如果要求到任意顶点m的路径,只需要根据from[i] = m反推回去,就可以得到一条完整的路径。下面给出代码实现:
template<typename Graph>
class Path
{
private:
Graph& G;//寻路的图
int s;//起始点
int* visited;//标记顶点是否被访问过的数据
int* from;//记录遍历到的每个顶点的从哪个顶点索引到
void depthFirstSearch(int v)//深度优先遍历
{
visited[v] = true; //标记当前节点为已访问过
Graph::adjIterator ite(G,v);
for(int i=ite.begin();!ite.end();i=ite.next())
{
if(!visited[i])
{
from[i] = v;
depthFirstSearch(i); //使用递归实现深度优先,继续遍历连接节点的连接节点
}
}
}
public:
Path(Graph& graph,int source):G(graph)
{
//初始化
this->s = source;
visited = new int[G.V()];
from = new int[G.V()];
for(int i=0;i<G.V();i++)
{
visited[i] = false;
from[i] = -1;
}
depthFirstSearch(source);//深度优先遍历source源顶点
}
~Path()
{
delete[] visited;
delete[] from;
}
//顶点v与源顶点是否存在连通路径
bool hasPath(int v)
{
return visited[v];
}
//获取顶点v与源顶点连通路径
void path(int v,vector<int> &vec)
{
stack<int> _stack;
int p = v;
while(p!=-1)
{
_stack.push(p);
p = from[p];
}
vec.clear();
while(!_stack.empty())
{
vec.push_back(_stack.top());
_stack.pop();
}
}
// 打印出从s点到w点的路径
void showPath(int w)
{
assert( hasPath(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<<" -> ";
}
}
};
5.广度优先遍历和最短路径
基本思路:遍历到每一个顶点时先将其所有邻接顶点加入遍历队列中并出队当前顶点,按照队列中的顺序遍历顶点,队列为空时遍历完成。代码实现如下:
#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;
int *from;
int *ord;//记录路径中,节点的次序(即距离起始顶点的距离)
public:
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;
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;
}
bool hasPath(int w){
assert( w >= 0 && w < G.V() );
return visited[w];
}
void path(int w, vector<int> &vec){
assert( w >= 0 && w < G.V() );
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){
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<<" -> ";
}
}
int length(int w){
assert( w >= 0 && w < G.V() );
return ord[w];
}
};