C++ 实现图的存储和遍历
定义一个图类
#include <iostream>
#include <iomanip>
using namespace std;
#include <stack>
#define MAX 100
#define INF 0x3f3f3f3f //一个很大的数
class Graph{
public:
Graph();
~Graph();
//邻接矩阵存储方式
void createMGraph(int a[][MAX],int n,int e);
void dispMGraph();
void matToList(); //将带权图的邻接矩阵转换成连接表
//邻接表存储方式
void creatALGraph(int a[][MAX],int n,int e);
void dispALGraph();
void listToMat();//将带权图的邻接表转换成邻接矩阵
//图的遍历
void DFS(int v); //深度优先搜索递归算法
void DFSN(int v);//深度优先搜索非递归算法
void BFS(int v);//广度优先递归算法
private:
void DFS1(ALGraph *G,int v);
void BFS1(ALGraph *G,int v);
MGraph g;
ALGraph *G;
int visited[MAX];
};
Graph::Graph(){
G = NULL;
}
Graph::~Graph(){
if(G!=NULL){
int i;
ANode *pre,*p;
for(i=0;i<G->n;i++){//遍历所有头结点
pre = G->adjlist[i].firstacr;
if(pre!=NULL){
p = pre->nextTar;
while(p!=NULL){//释放adjlist[i]的所有边结点
delete pre;
pre = p;
p = p->nextTar;
}
delete pre;
}
}
delete G;//释放G所指的头结点数组的内存空间
}
}
(一)邻接矩阵储存方法
邻接矩阵是表示顶点之间相邻关系的矩阵。设G=(V,E)是含有n(设n>0)个顶点的图,各顶点的编号为0~n-1,则G的邻接矩阵A是n阶方阵,其定义如下:
- 如果G是不带权无向图图,则
A [ i ] [ j ] = { 1 若(i,j)∈E(G) 0 其他 A[i][j]= \begin{cases} 1& \text{若(i,j)∈E(G)}\\ 0& \text{其他} \end{cases} A[i][j]={10若(i,j)∈E(G)其他 - 如果G是不带权有向图,则
A [ i ] [ j ] = { 1 若<i,j>∈E(G) 0 其他 A[i][j]= \begin{cases} 1& \text{若<i,j>∈E(G)}\\ 0& \text{其他} \end{cases} A[i][j]={10若<i,j>∈E(G)其他 - 如果G是带权无向图,则
A [ i ] [ j ] = { w i j 若i!=j且(i,j)∈E(G) 0 若i=j ∞ 其他 A[i][j]= \begin{cases} w_{ij}& \text{若i!=j且(i,j)∈E(G)}\\ 0& \text{若i=j}\\ ∞& \text{其他} \end{cases} A[i][j]=⎩⎪⎨⎪⎧wij0∞若i!=j且(i,j)∈E(G)若i=j其他 - 如果G是带权有向图,则
A [ i ] [ j ] = { w i j 若i!=j且<i,j>∈E(G) 0 若i=j ∞ 其他 A[i][j]= \begin{cases} w_{ij}& \text{若i!=j且<i,j>∈E(G)}\\ 0& \text{若i=j}\\ ∞& \text{其他} \end{cases} A[i][j]=⎩⎪⎨⎪⎧wij0∞若i!=j且<i,j>∈E(G)若i=j其他
连接矩阵的特点:
- 图的邻接矩阵表示是唯一的。
- 对于含有n个顶点的图,在采用邻接矩阵存储时,其存储空间均为O(n2),所以适合存储边数较多的稠密图。
- 无向图的邻接矩阵一定是一个对称矩阵,可以用对称矩阵的压缩存储方法减少存储空间。
- 无向图的邻接矩阵的i行(或第j列)非零元素(或非∞元素)的个数正好是顶点i的度。
- 有向图的邻接矩阵的i行(或第j列)非零元素(或非∞元素)的个数正好是顶点i的出度(或入度)。
邻接矩阵类型MGraph定义
//图的邻接矩阵存储方法
class VertexType{//顶点类型
public:
int no;
char data[MAX];
};
class MGraph{//图邻接矩阵类型
public:
int edges[MAX][MAX];
int n,e;
VertexType vexs[MAX];
};
建立图的邻接矩阵
void Graph::createMGraph(int a[][MAX],int n,int e){
int i,j;
g.n = n;//置顶点数
g.e = e;//置边数
for(i=0;i<g.n;i++){
for(j=0;j<g.n;j++){
g.edges[i][j] = a[i][j];
}
}
}
输出图的邻接矩阵
void Graph::dispMGraph(){
int i,j;
for(i=0;i<g.n;i++){
for(j=0;j<g.n;j++){
if(g.edges[i][j]==INF){
cout << setw(4) << "∞" ;
}else{
cout << setw(4) << g.edges[i][j];
}
}
cout << endl;
}
}
(二)邻接表储存方法
图的邻接表储存方法是一种将顺序分配与链式分配相结合的储存方法。在表示n个顶点的图的邻接表中,每个顶点建立一个单链表,第i(0≤i≤n-1)个单链表中的结点表示依附于顶点i的边(对于有向图是以顶点i为尾的边)。每个单链表上附设一个表头结点,将所有表头结点构成一个表头结点数组。
- 边结点:
adjvex指示与顶点i邻接的顶点的编号,nexTar指示下一条边的结点,weight存储与边的权重。 - 表头结点:
data存储顶点i的名称或其他信息,firstacr指向顶点i的链表中的第一个边结点。
邻接表的特点:
- 图的邻接表表示不唯一,这是因为在每个顶点对应的单链表中,各边结点的链接次序可以是任意的,取决于建立邻接表的算法以及边的输入次序。
- 对于有n个顶点和e条边的无向图,其邻接表有n个表头结点和2e个边结点;对于有n个顶点和e条边的有向图,其邻接表有n个表头结点和e个边结点。邻接表更适合存储边数较少的稀疏图。
- 对于无向图,邻接表的顶点i(0≤i≤n-1)对应的第i个单链表的边结点个数正好是顶点i的度
- 对于有向图,邻接表的顶点i(0≤i≤n-1)对应的第i个单链表的边结点个数仅仅是顶点i的出度,顶点i的入度为邻接表中所有adjvex域置为i的边结点个数。
邻接表类型ALGraph定义
//图的邻接表存储方法
class ANode{//边结点类型
public:
int adjvex;
ANode *nextTar;
int weight;
};
class VNode{//表头节点类型
public:
char data[MAX];
ANode *firstacr;
};
class ALGraph{//图的连接表类型
public:
VNode adjlist[MAX];
int n,e;
};
建立图的邻接表
void Graph::creatALGraph(int a[][MAX],int n,int e){
int i,j;
ANode *p;
G = new ALGraph();//创建图的邻接表
G->n = n;//设置顶点数
G->e = e;//设置边数
for(i=0;i<G->n;i++){
//给邻接表中所有头结点的指针域置初值
G->adjlist[i].firstacr = NULL;
}
for(i=0;i<G->n;i++){//检查边数组a中的每一个元素
for(j=G->n-1;j>=0;j--){
if(a[i][j]!=0&&a[i][j]!=INF){//存在一条边
p = new ANode();//创建边结点
p->adjvex = j;
p->weight = a[i][j];
p->nextTar = G->adjlist[i].firstacr;//采用头插法插入
G->adjlist[i].firstacr = p;
}
}
}
}
输出图的邻接表
void Graph::dispALGraph(){
int i;
ANode *p;
for(i=0;i<G->n;i++){
cout << "[" << i << "]";
p = G->adjlist[i].firstacr;//p指向第一个邻接点
if(p!=NULL){
cout << "→" ;
}
while(p!=NULL){
cout << " " << p->adjvex << "(" << p->weight << ")";
p = p->nextTar;//p移向下一个邻接点
if(p!=NULL){
cout << "→" ;
}else{
cout << "∧";
}
}
cout << endl;
}
}
(三)邻接矩阵和邻接表的相互转换
- 邻接矩阵转换成邻接表
void Graph::matToList(){
int i,j;
ANode *p;
this->G = new ALGraph();//给邻接表分配空间
for(i=0;i<this->g.n;i++){
//给邻接表中所有头结点的指针域置初值
G->adjlist[i].firstacr = NULL;
}
for(i=0;i<this->g.n;i++){//继承邻接矩阵中的每个元素
for(j=this->g.n-1;j>=0;j--){
if(this->g.edges[i][j]!=0&&this->g.edges[i][j]!=INF){//存在一条边
p = new ANode();
p->adjvex = j;
p->weight = this->g.edges[i][j];
p->nextTar = G->adjlist[i].firstacr;//采用头插法
G->adjlist[i].firstacr = p;
}
}
}
G->n = this->g.n;//置顶点数
G->e = this->g.e;//置边数
}
- 邻接表转换成邻接矩阵
void Graph::listToMat(){
int i,j;
ANode *p;
ALGraph *GT = new ALGraph();
GT = this->G;
for(i=0;i<G->n;i++){//初始化邻接矩阵
for(j=0;j<G->n;j++){
if(i==j){
this->g.edges[i][j] = 0;
}else{
this->g.edges[i][j] = INF;
}
}
}
for(i=0;i<G->n;i++){//遍历邻接表的所有表头结点
p = G->adjlist[i].firstacr;
while(p!=NULL){//当存在一条边时
this->g.edges[i][p->adjvex] = p->weight;//置相应的权值
p = p->nextTar;
}
}
this->g.n=G->n;//置顶点数
this->g.e=G->e;//置边数
}
(四)图的邻接表的广度优先遍历和深度优先遍历
- 广度优先遍历
void Graph::BFS(int v){
int i;
for(i=0;i<this->G->n;i++){
visited[i] = 0; //将visited数组元素置为0
}
BFS1(this->G,v);
}
void Graph::BFS1(ALGraph *G,int v){
ANode *p;
int w;
int qu[MAX];//定义一个循环队列
int front=0,rear=0;//将循环队列的队头和队尾初始化
cout << v << " ";
visited[v] = 1;//输出访问顶点后设置标记
rear = (rear+1)%MAX;
qu[rear] = v;//访问过进队
while(front!=rear){//队列不为空时循环
front = (front+1)%MAX;
w = qu[front];//出队并赋值给w
p = G->adjlist[w].firstacr;//找与顶点w邻接的第一个顶点
while(p!=NULL){
if(visited[p->adjvex]==0){//若当前邻接顶点未被访问
cout << p->adjvex << " ";
visited[p->adjvex] = 1;//输出访问顶点后设置标记
rear = (rear+1)%MAX;//该顶点进队
qu[rear] = p->adjvex;
}
p = p->nextTar;//找下一个邻接顶点
}
}
}
- 深度优先遍历
①递归
void Graph::DFS(int v){
int i;
for(i=0;i<this->G->n;i++){//将visited数组元素均置为0
visited[i] = 0;
}
DFS1(this->G,v);
}
void Graph::DFS1(ALGraph *G,int v){
int w;
ANode *p;
visited[v] = 1;//置已访问标记
cout << v << " ";
p = G->adjlist[v].firstacr;//指向顶点v的第一个邻接点
while(p!=NULL){
w = p->adjvex;
if(visited[w]==0){
DFS1(G,w);//若w顶点未访问,递归访问它
}
p = p->nextTar;//下一个邻接点
}
}
②非递归
void Graph::DFSN(int v)//深度优先(非递归)
{
ANode* p;
stack<int> St;//访问后的元素存放进栈
int x,w,i;
for (i=0;i<this->G->n;i++){
visited[i] = 0;//顶点全部置为0
}
cout << v << " ";//访问顶点v
visited[v] = 1;//访问后标记
St.push(v);//v进栈
while(!St.empty())
{
x = St.top();//取出x
p = this->G->adjlist[x].firstacr;//p指向x的相邻结点
while(p!=NULL)//第二重循环
{
w = p->adjvex;//有相邻结点,将值赋给w
if (visited[w] == 0)//未访问过
{
cout << w << " ";//访问
visited[w] = 1;//标记
St.push(w);//进栈
break;//直接退出第二重循环,不执行p=p->nextarc
}
p = p->nextTar;//否则(访问过),找下一个结点
}
if (p == NULL){
St.pop();
}
}
cout << endl;
}
(五)main函数
int main() {
Graph g;
int n=6,e=9;//n表示顶点数,e表示边数
int A[MAX][MAX] = {{0,5,INF,7,INF,INF},{INF,0,4,INF,INF,INF},{8,INF,0,INF,INF,9},{INF,INF,5,0,INF,6},{INF,INF,INF,5,0,INF},{3,INF,INF,INF,1,0}};
//A数组是通过每个顶点到其它顶点建立起来的,自己到自己就为0,可达就为该路径权值,不可达就为INF(一个很大的数)
cout << "该图对应的连接表为:" << endl;
g.creatALGraph(A,n,e);
g.dispALGraph();
cout << endl;
cout << "该图对应的邻接矩阵为:" << endl;
g.createMGraph(A,n,e);
g.dispMGraph();
cout << endl;
cout << "递归深度优先结果:";
g.DFS(0);
cout << endl;
cout << "非递归深度优先结果:";
g.DFSN(0);
cout << endl;
cout << "广度优先:";
g.BFS(0);
cout << endl;
return 0;
}