图的两种表示方法
前面我们简单的介绍了一些关于图的基础知识,这一次我们来讨论一下在计算机中如何去描述一个图,采用怎样的一个数据结构。前面我们看到,一个图的基本组成就是节点和边,因此,我们只想找到一种描述节点并且节点之间边关系的数据结构就好了。通常我们使用两种不同的表示方法来表示一个图:
1.邻接矩阵法
2.邻接表法
这两种表示方法对于图中的点来说都是一样的,区别在与对点与点之间的边表示存在着不同。
①邻接矩阵表示法
如上图所示,图中有4个节点,则对应的邻接表中就有4行4列,如果这个矩阵命名为a的话,则a[i][j]的值代表着i节点与j节点之间是否存在着边,我们用布尔值0和1来表示两种状态,0表示不存在边,即两点之间无连接。1表示有连接,即两点之间存在着边连接。我们现在讨论的是无向图,则a[i][j]与a[j][i]表示的值是一样的,因为假如1节点与2节点之间存在边关系,则2节点与1节点肯定也是存在着边关系的。因此,无向图的邻接矩阵中关于斜对角线对称。如果用邻接矩阵来表示有向图的话,则不一定对称,因此时的a[i][j]表示的是存在i节点指向j节点的边,而j节点是否有指向i节点的边就不一定了。
②邻接表表示法
如上图所示,邻接表与邻接矩阵的不同之处就在于:邻接矩阵把所有点与点之间的关系是否存在都表示出来了,而邻接矩阵只把存在关系的点表示出来,没有表示则表明不存在着边关系。
例如上图中,第0行只有1个1节点,即表示与0节点相连的节点只有1节点,第1行有0,2,3这3个节点,表示着和1节点相连的节点有3个,即0节点,2节点,3节点。第2行后面有1,3节点,表示与2节点相连的节点有两个,分别是1节点和3节点,以此类推……
当然,邻接表表示法也可以用来表示有向图,如下图所示:
则表示0节点有指向1节点的一条边,1节点有一条指向2节点的边,2节点有指向3节点的一条边,3节点有指向1节点的一条边,即此时表示的边关系都是带有方向的。
在上面我们可以看出,邻接表相比于邻接矩阵来说,所占用的空间更小,这是邻接表的一个优势。但是邻接表如果表示的是一个有很多条边的图,即稠密图的话,则邻接表的优势就不能够完好的体现了。因此,对于一个图来说,我们要根据具体的情况来判断使用哪种方式去表示该图,一般邻接表适合表示稀疏图,邻接矩阵适合表示稠密图。
图的具体实现
下面来具体的实现一下两种图的表示方法:
稠密图类的实现(使用邻接矩阵表示):
class DenseGraph{//构建稠密图类,使用邻接矩阵法表示
private:
int n,m;//n为图的顶点数量,m为边的数量
bool directed;//是否为有向图
vector<vector<bool>> g;//构建二维数组g作为邻接表的结构基础,其存储类型为布尔类型
public:
DenseGraph(int n,bool directed ){//构造函数
this->n=n;
this->directed=directed;
this->m=0;
for(int i=0;i<n;i++){
g.push_back(vector<bool>(n, false));//初始化邻接表的关系全部为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;//v节点存在到w节点的边,则不需要进行加边操作,直接返回即可
g[v][w]= true;
m++;//增加边的数量
if(directed){//如果为有向图
return;
}
else{//如果为无向图,则邻接图成对称关系,w节点到v节点之间也一定是存在连接的
g[w][v]= true;
}
}
bool hasEdge(int v,int w){//判断v节点到w节点之间是否已经存在边
assert(v>=0&&v<n);//防止越界
assert(w>=0&&w<n);
return g[v][w];
}
};
稀疏图类的实现(使用邻接表表示)
class SparseGraph{//构建稀疏图,并使用邻接表结构
private:
int n,m;//n为图的节点数量,m为图的边数量
bool directed;//表示图是否为有向图
vector<vector<int>> g;//使用二维数组来表示邻接表,且数据类型为int类型
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;
}
//在v与w节点之间加上一条边
void addEdge(int v,int w){
assert(v>=0&&v<n);
assert(w>=0&&w<n);//防止越界
g[v].push_back(w);//在v的邻接表中加入w节点
if(v!=w&&!directed) g[w].push_back(v);//v与w不是同一个节点且图不是有向图,才需要
//执行改该步骤
m++;
}
bool hasEdege(int v,int w) {//判断v节点与w节点之间是否存在边
assert(v>=0&&v<n);
assert(w>=0&&w<n);
for(int i=0;i<g[v].size();i++){//遍历与v节点相连的节点
if(g[v][i]==w) return true;//如果在邻接串中发现其中某一个节点与w节点相同,则表明
//v节点与w节点之间已经存在边
}
return false;
}
对于邻接表来说,虽然能较好的节省空间,但是不能较好的处理平行边,即在添加边的时候不能防止添加平行边,即使在每次添加边之前,通过hasedge()判断是否有边来添加边可以防止平行边的产生,但是这样会大大减少图添加边的效率。因此,我们暂时允许平行边的产生。因此,这也算上邻接表的一个缺点吧。
如需获取本节的邻接矩阵和邻接表表示法完整代码,请点击此处。
基础不牢?新手不友好?无人带路?关注《扬俊的小屋》公众号吧!
博客文章版权说明
第一条 本博客文章仅代表作者本人的观点,不保证文章等内容的有效性。
第二条 本博客部分内容转载于合作站点或摘录于部分书籍,但都会注明作/译者和原出处。如有不妥之处,敬请指出。
第三条 在征得本博客作者同意的情况下,本博客的作品允许非盈利性引用,并请注明出处:“作者:____转载自____”字样,以尊重作者的劳动成果。版权归原作/译者所有。未经允许,严禁转载。
第四条 对非法转载者,“扬俊的小屋”和作/译者保留采用法律手段追究的权利。
第五条 本博客之声明以及其修改权、更新权及最终解释权均属“扬俊的小屋”。
第六条 以上声明的解释权归“扬俊的小屋”所有。