概述
图——基本的图算法(一)图的存储结构
1. 图的存储结构概述
对于图G=(V, E)来说,可以有两种标准的表示方法,一个是邻接矩阵,另一个是邻接链表,这两种方法都可以表示有向图和无向图。除此之外,图的存储结构还有十字链表、邻接多重表、边集数组等。
2. 图的各种存储结构及实现
2.1 邻接矩阵
(1)总体思路
邻接矩阵是用两个数组来表示一个图:一个一维数组用来存储每个顶点的信息;一个二维数组(即邻接矩阵)用来存储图中的边或弧信息。对于图G =(V, E)来说,邻接矩阵matrix是一个|V|*|V|的方阵,假设1 <= i, j <= |V|,如果matrix[i][j] == 0,则表示顶点i和顶点j之间没有边相连;反之,如果matrix[i][j] != 0,则表示表示顶点i和顶点j之间有边相连,且matrix[i][j]存储的值即为该边的权重。
(2)具体实现
A. 数据结构
首先明确,一个图结构中包含两个数组,一个顶点表和一个用来存储图中边或弧信息的二维数组:
顶点表:
用来存储图中顶点信息,是一个一维数组,顶点信息可以自定义,此处定义为int类型:
#define Maximum 1000
typedef int VexType;
VexType vertexlist[Maximum];
邻接矩阵
是一个二维矩阵,大小为|V|*|V|,用来存储图中边或弧信息,其中二维矩阵的类型(即边的权值类型)可以自定义,此处定义为int:
#define Maximum 1000
typedef int MatrixType;
MatrixType matrix[Maximum][Maximum];
图
综合起来,图的数据结构为:
#define Maximum 1000
typedef int VexType;
typedef int MatrixType;
typedef struct GraphAdjMatrix {
VexType vertexlist[Maximum];
MatrixType matrix[Maximum][Maximum];
int vertexnumber;
int edgenumber;
};
B. 图的创建
图的创建分为顶点表初始化和邻接矩阵初始化两步:
a. 顶点表初始化:
CreateVertexlist() {
int i, j, k;
cout<<"Please enter the number of vertexes"<<endl;
cin>>i;
G->vertexnumber = i;
cout<<"Please enter the data of each vertex in term"<<endl;
for(j=0; j<i; j++) {
cin>>k;
G->vertexlist[j] = k;
}
}
b. 邻接矩阵初始化
CreateMatrix() {
int i, j, k, s, e, w;
cout<<"Please enter the number of edges"<<endl;
cin>>i;
G->edgenumber = i;
cout<<"Please enter the information of each edge in term"<<endl;
for(j=0; j<i; j++) {
cin>>s>>e>>w;
G->matrix[s][e] = w;
}
}
c. 图的创建
CreateGraph() {
CreateVertexlist();
CreateMatrix();
}
C. 针对有向图和无向图分别写一个类,对上述内容进行封装:
a. class UndirectGraph(无向图)
#define Maximum 1000
typedef int VexType;
typedef int MatrixType;
typedef struct GraphAdjMatrix {
VexType vertexlist[Maximum];
MatrixType matrix[Maximum][Maximum];
int vertexnumber;
int edgenumber;
};
class UndirectGraph {
public:
GraphAdjMatrix* G;
UndirectGraph() {
G = (GraphAdjMatrix*)malloc(sizeof(GraphAdjMatrix));
memset(G->vertexlist, 0, sizeof(G->vertexlist));
memset(G->matrix, 0, sizeof(G->matrix));
}
CreateVertexlist() {
int i, j, k;
cout<<"Please enter the number of vertexes"<<endl;
cin>>i;
G->vertexnumber = i;
cout<<"Please enter the data of each vertex in term"<<endl;
for(j=0; j<i; j++) {
cin>>k;
G->vertexlist[j] = k;
}
}
CreateMatrix() {
int i, j, k, s, e, w;
cout<<"Please enter the number of edges"<<endl;
cin>>i;
G->edgenumber = i;
cout<<"Please enter the information of each edge in term"<<endl;
for(j=0; j<i; j++) {
cin>>s>>e>>w;
G->matrix[s][e] = w;
G->matrix[e][s] = w;
}
}
CreateGraph() {
CreateVertexlist();
CreateMatrix();
}
};
b. class DirectGraph(无向图)
#define Maximum 1000
typedef int VexType;
typedef int MatrixType;
typedef struct GraphAdjMatrix {
VexType vertexlist[Maximum];
MatrixType matrix[Maximum][Maximum];
int vertexnumber;
int edgenumber;
};
class DirectGraph {
public:
GraphAdjMatrix* G;
DirectGraph() {
G = (GraphAdjMatrix*)malloc(sizeof(GraphAdjMatrix));
memset(G->vertexlist, 0, sizeof(G->vertexlist));
memset(G->matrix, 0, sizeof(G->matrix));
}
CreateVertexlist() {
int i, j, k;
cout<<"Please enter the number of vertexes"<<endl;
cin>>i;
G->vertexnumber = i;
cout<<"Please enter the data of each vertex in term"<<endl;
for(j=0; j<i; j++) {
cin>>k;
G->vertexlist[j] = k;
}
}
CreateMatrix() {
int i, j, k, s, e, w;
cout<<"Please enter the number of edges"<<endl;
cin>>i;
G->edgenumber = i;
cout<<"Please enter the information of each edge in term"<<endl;
for(j=0; j<i; j++) {
cin>>s>>e>>w; //s:边的起点
e:边的终点
w:边的权值
G->matrix[s][e] = w;
}
}
CreateGraph() {
CreateVertexlist();
CreateMatrix();
}
};
2.2 邻接链表
(1)总体思路
邻接链表是一种不错的图存储结构,由于它在表示稀疏图的时候非常紧凑而成为通常的选择。对于图G =(V, E)来说,在其邻接链表表示中,每个结点对应一条链表,因此这个图里有V条链表。假设用一个V维的数组Adj来存储这V条链表,且Adj[i]表示的是结点i对应的链表,那么Adj[i]这条链表里存储的就是所有与节点i之间有边相连的结点,即与结点i相邻的结点。举个例子:
上图中的(a)是一个无向图,(b)是它的邻接链表表示,可以看到图中有5个结点,每个结点对应一条链表,链表中的结点都是与该节点相邻的,例如结点1的链表中有结点2和结点5,它们都与结点1相邻。
(2)具体实现
A. 数据结构
首先要明确,一个图包含了一个顶点表,而顶点表里的每一项又包含一个边表:
顶点表:该表包含了图里的所有顶点,顶点表的每一项用于存储该顶点的属性(例如该结点对应的值),以及指向其边表的第一个结点的指针。
边表:某个顶点的边表存放了与其相邻的结点。
明确上述两个结构后,就具体介绍这两个表里每一项的数据结构:
边表结点:边表里的每一项叫做边表结点,它包含adjId、weight和next两个区域。其中,adjId存储的是某顶点的邻接点在顶点表中的下标值,weight存储了这条边的权重,而next则是一个指向边表中下一个结点的指针。具体代码:
typedef struct EdgeListNode{
int adjId;
int weight;
EdgeListNode* next;
};
顶点表结点:顶点表里的每一项叫顶点表结点,它包含两个区域,分别是该顶点的属性(此处令它为一个int类型的值)和指向其边表第一个结点的指针:
typedef struct VertexListNode{
int data;
EdgeListNode* firstadj;
};
那么,一个图的数据结构中就可以包含顶点数、边数、一个顶点表:
#define Maximum 1000 //预设一个边表大小的最大值
typedef struct GraphAdjList{
int vertexnumber;
int edgenumber;
VertexListNode vertextlist[Maximum];
};
对上述数据结构进行统一整理:
#define Maximum 1000
typedef struct EdgeListNode{
int adjId;
int weight;
EdgeListNode* next;
};
typedef struct VertexListNode{
int data;
EdgeListNode* firstadj;
};
typedef struct GraphAdjList{
int vertexnumber;
int edgenumber;
VertexListNode vertextlist[Maximum];
};
B. 图的创建
定义好图的数据结构之后,就要开始进行图的创建了。创建图的过程分为两步,先初始化图里的顶点表,然后再来创建边表:
a. 顶点表初始化:
void VertexListInit() {
int i, j, k;
cout<<"Please enter the number of vertexes"<<endl; //输入顶点个数
cin>>i;
G->vertexnumber = i; //设置图结构里的顶点个数
cout<<"Please enter the data of each vertex in term"<<endl;
for(j=0; j<i; j++) { //依此输入每个顶点,且该顶点再顶点表中对应的下标为i
cin>>k;
k = G->vertextlist[i].data;
G->vertextlist[i].firstadj = NULL; //将其指向边表第一个结点的指针置为空
}
}
b. 创建边表
这里要注意区别有向图和无向图。有向图每增加一条边只需在该边出口顶点对应的边表里添加一个边表结点,而无向图需要在边的两个顶点对应的边表里进行添加。
无向图:
void EdgeListCreate() {
int i, j, s, e, w;
EdgeListNode* edge;
cout<<"Please enter the number of edges"<<endl; //输入边的条数
cin>>i;
G->edgenumber = i;
cout<<"Please enter the information of each edge in term"<<endl;
for(j=0; j<i; j++) {
cin>>s>>e>>w; //s:边的起点
e:边的终点
w:边的权值
edge = (EdgeListNode*)malloc(sizeof(EdgeListNode));
edge->adjId = e;
edge->weight = w;
/*此处采用头插法,即每次往边表的头部插入新结点*/
edge->next = G->vertextlist[s].firstadj;
G->vertextlist[s].firstadj = edge;
edge = (EdgeListNode*)malloc(sizeof(EdgeListNode));
edge->adjId = s;
edge->weight = w;
edge->next = G->vertextlist[e].firstadj;
G->vertextlist[e].firstadj = edge;
}
}
有向图:
void EdgeListCreate() {
int i, j, s, e, w;
EdgeListNode* edge;
cout<<"Please enter the number of edges"<<endl; //输入边的条数
cin>>i;
G->edgenumber = i;
cout<<"Please enter the information of each edge in term"<<endl;
for(j=0; j<i; j++) {
cin>>s>>e>>w; //s:边的起点
e:边的终点
w:边的权重
edge = (EdgeListNode*)malloc(sizeof(EdgeListNode));
edge->adjId = e;
edge->weight = w;
/*此处采用头插法,即每次往边表的头部插入新结点*/
edge->next = G->vertextlist[s].firstadj;
G->vertextlist[s].firstadj = edge;
}
}
c. 综合起来,图的创建:
void CreateGraphAdjList() {
VertexListInit();
EdgeListCreate();
}
C. 针对有向图和无向图分别写一个类,对上述内容进行封装:
a. class UndirectGraph(无向图)
#define Maximum 1000
typedef struct EdgeListNode{
int adjId;
int weight;
EdgeListNode* next;
};
typedef struct VertexListNode{
int data;
EdgeListNode* firstadj;
};
typedef struct GraphAdjList{
int vertexnumber;
int edgenumber;
VertexListNode vertextlist[Maximum];
};
class UndirectGraph {
public:
GraphAdjList* G;
UndirectGraph() {
G = (GraphAdjList*)malloc(sizeof(GraphAdjList));
}
void VertexListInit() {
int i, j, k;
cout<<"Please enter the number of vertexes"<<endl;
cin>>i;
G->vertexnumber = i;
cout<<"Please enter the data of each vertex in term"<<endl;
for(j=0; j<i; j++) {
cin>>k;
G->vertextlist[i].data = k;
G->vertextlist[i].firstadj = NULL;
}
}
void EdgeListCreate() {
int i, j, s, e, w;
EdgeListNode* edge;
cout<<"Please enter the number of edges"<<endl;
cin>>i;
G->edgenumber = i;
cout<<"Please enter the information of each edge in term"<<endl;
for(j=0; j<i; j++) {
cin>>s>>e>>w;
edge = (EdgeListNode*)malloc(sizeof(EdgeListNode));
edge->adjId = e;
edge->weight = w;
edge->next = G->vertextlist[s].firstadj;
G->vertextlist[s].firstadj = edge;
edge = (EdgeListNode*)malloc(sizeof(EdgeListNode));
edge->adjId = s;
edge->weight = w;
edge->next = G->vertextlist[e].firstadj;
G->vertextlist[e].firstadj = edge;
}
}
void CreateGraphAdjList() {
VertexListInit();
EdgeListCreate();
}
};
b. class DirectGraph(有向图)
#define Maximum 1000
typedef struct EdgeListNode{
int adjId;
int weight;
EdgeListNode* next;
};
typedef struct VertexListNode{
int data;
EdgeListNode* firstadj;
};
typedef struct GraphAdjList{
int vertexnumber;
int edgenumber;
VertexListNode vertextlist[Maximum];
};
class DirectGraph {
public:
GraphAdjList* G;
DirectGraph() {
G = (GraphAdjList*)malloc(sizeof(GraphAdjList));
}
void VertexListInit() {
int i, j, k;
cout<<"Please enter the number of vertexes"<<endl;
cin>>i;
G->vertexnumber = i;
cout<<"Please enter the data of each vertex in term"<<endl;
for(j=0; j<i; j++) {
cin>>k;
G->vertextlist[i].data = k;
G->vertextlist[i].firstadj = NULL;
}
}
void EdgeListCreate() {
int i, j, s, e, w;
EdgeListNode* edge;
cout<<"Please enter the number of edges"<<endl;
cin>>i;
G->edgenumber = i;
cout<<"Please enter the information of each edge in term"<<endl;
for(j=0; j<i; j++) {
cin>>s>>e>>w;
edge = (EdgeListNode*)malloc(sizeof(EdgeListNode));
edge->adjId = e;
edge->weight = w;
edge->next = G->vertextlist[s].firstadj;
G->vertextlist[s].firstadj = edge;
}
}
void CreateGraphAdjList() {
VertexListInit();
EdgeListCreate();
}
};
逆邻接链表(补充)
逆邻接链表一个图的逆邻接链表,即对每个顶点Vi都建立一个以Vi为入口结点的链表),例如下面这个例子,图中以顶点V0为入口结点的弧有(V1,V0)和(V2,V0),因此顶点V0的边表就包含了V1和V2。
2.3 十字链表(针对有向图邻接链表的优化)
(1)总体思路
对于有向图而言,邻接链表的缺陷是要查询某个顶点的入度时需要遍历整个链表,而逆邻接链表在查询某个顶点的出度时要遍历整个链表。为了解决这些问题,十字链表将邻接链表和逆邻接链表综合了起来,其基本思想就是在邻接链表的出边表的基础上,增加一个入边表。
(2)具体实现
A. 数据结构
a. 顶点表中结点的结构:
typedef struct VertexListNode{
int data;
EdgeListNode* firstadjin;
EdgeListNode* firstadjout;
};
其中,firstadjin表示入边表头指针,指向该顶点的入边表的第一个结点;firstadjout表示出边表头指针,指向该顶点的出边表的第一个结点,这个和邻接链表里的是一样的。
b. 边表中结点的结构:
typedef struct EdgeListNode{
int tailvex;
int headvex;
int weight;
EdgeListNode* headlink;
EdgeListNode* taillink;
};
其中,tailvex是指弧起点在顶点表中的下标;headvex是指弧终点在顶点表中的下标;weight表示这条边的权值;headlink是指弧终点的入边表指针域,指向弧终点的入边表里的下一个结点,即指向与该边终点相同的下一条边;taillink是弧起点的出边表指针域,指向弧起点的出边表里的下一个结点,即指向与该边起点相同的下一条边。
c. 用十字链表表示的图结构:
typedef struct GraphOthList{
int vertexnumber;
int edgenumber;
VertexListNode vertextlist[Maximum];
};
B. 图的创建
a. 初始化顶点表
void VertexListInit() {
int i, j, k;
cout<<"Please enter the number of vertexes"<<endl;
G->vertexnumber = i;
cout<<"Please enter the data of each vertex in term"<<endl;
for(j=0; j<i; j++) {
cin>>k;
k = G->vertextlist[j].data;
G->vertextlist[j].firstadjin = NULL;
G->vertextlist[j].firstadjout = NULL;
}
}
b. 初始化边表
void EdgeListCreate() {
int i, j, s, e, w;
cout<<"Please enter the number of edges"<<endl;
cin>>i;
G->edgenumber = i;
cout<<"Please enter the information of each edge in term"<<endl;
for(j=0; j<i; j++) {
cin>>s>>e>>w;
//s:弧的起点
e:弧的终点
w:弧的权值
EdgeListNode* edge;
edge = (EdgeListNode*)malloc(sizeof(EdgeListNode));
edge->headvex = s;
edge->tailvex = e;
//指向弧起点的出边表中的下一个结点,采用头插法
edge->taillink = G->vertextlist[s].firstadjout;
G->vertextlist[s].firstadjout = edge;
//指向弧终点的入边表中的下一个结点,采用头插法
edge->headlink = G->vertextlist[e].firstadjin;
G->vertextlist[e].firstadjin = edge;
}
}
c. 综合起来,图的创建:
void CreateGraph() {
VertexListInit();
EdgeListCreate();
}
C. 把上述内容封装起来,创建一个十字链表表示图的类:
#define Maximum 1000
typedef struct EdgeListNode{
int tailvex;
int headvex;
EdgeListNode* headlink;
EdgeListNode* taillink;
};
typedef struct VertexListNode{
int data;
EdgeListNode* firstadjin;
EdgeListNode* firstadjout;
};
typedef struct GraphOthList{
int vertexnumber;
int edgenumber;
VertexListNode vertextlist[Maximum];
};
class DirectGraph {
public:
GraphOthList* G;
DirectGraph() {
G = (GraphOthList*)malloc(sizeof(GraphOthList));
}
void VertexListInit() {
int i, j, k;
cout<<"Please enter the number of vertexes"<<endl;
G->vertexnumber = i;
cout<<"Please enter the data of each vertex in term"<<endl;
for(j=0; j<i; j++) {
cin>>k;
k = G->vertextlist[j].data;
G->vertextlist[j].firstadjin = NULL;
G->vertextlist[j].firstadjout = NULL;
}
}
void EdgeListCreate() {
int i, j, s, e, w;
cout<<"Please enter the number of edges"<<endl;
cin>>i;
G->edgenumber = i;
cout<<"Please enter the information of each edge in term"<<endl;
for(j=0; j<i; j++) {
cin>>s>>e>>w;
EdgeListNode* edge;
edge = (EdgeListNode*)malloc(sizeof(EdgeListNode));
edge->headvex = s;
edge->tailvex = e;
edge->taillink = G->vertextlist[s].firstadjout;
G->vertextlist[s].firstadjout = edge;
edge->headlink = G->vertextlist[e].firstadjin;
G->vertextlist[e].firstadjin = edge;
}
}
void CreateGraph() {
VertexListInit();
EdgeListCreate();
}
};
(3)实际示例
2.4 邻接多重表(针对无向图邻接链表的优化)
(1)总体思想
对于无向图而言,其每条边在邻接链表中都需要两个结点来表示,而邻接多重表正是对其进行优化,让同一条边只用一个结点表示即可。邻接多重表仿照了十字链表的思想,对邻接链表的边表结点进行了改进。
(2)具体实现
A. 数据结构
a. 顶点表中结点的结构:
typedef struct VertexListNode{
int data;
EdgeListNode* firstedge;
};
其中,firstedge指向该顶点的边表的第一个结点。
b. 边表中结点的结构:
typedef struct EdgeListNode{
int ivex;
EdgeListNode* ilink;
int jvex;
EdgeListNode* jlink;
int weight;
};
其中,ivex和jvex是一条边的两个顶点在顶点表中的下标;ilink指向了表示图中另一条以ivex为其中一个顶点的边的边表结点,因为ivex的边表里每个结点表示的边都以ivex作为其中一个顶点,所以可以让ilink指向G->vertextlist[ivex].firstedge;jlink指向了表示图中另一条以jvex为其中一个顶点的边的边表结点,因为jvex的边表里每个结点表示的边都以jvex作为其中一个顶点,所以可以让jlink指向G->vertextlist[jvex].firstedge;weight表示这条边的权值。
c. 用邻接多重表表示的图结构:
typedef struct GraphMultiAdjList{
int vertexnumber;
int edgenumber;
VertexListNode vertextlist[Maximum];
};
B. 图的创建
a. 初始化顶点表
void VertexListInit() {
int i, j, k;
cout<<"Please enter the number of vertexes"<<endl;
G->vertexnumber = i;
cout<<"Please enter the data of each vertex in term"<<endl;
for(j=0; j<i; j++) {
cin>>k;
k = G->vertextlist[j].data;
G->vertextlist[j].firstedge = NULL;
}
}
b. 初始化边表
void EdgeListCreate() {
int i, j, k, s, e, w;
cout<<"Please enter the number of edges"<<endl;
cin>>i;
G->edgenumber = i;
cout<<"Please enter the information of each edge in term"<<endl;
for(j=0; j<i; j++) {
cin>>s>>e>>w;
EdgeListNode* edge;
edge = (EdgeListNode*)malloc(sizeof(EdgeListNode));
edge->ivex = s;
edge->jvex = e;
edge->weight = w;
/*G->vertextlist[s]里的结点表示的边都以s为其中一个顶点*/
edge->ilink = G->vertextlist[s].firstedge;
/*当前这条边也是以s为其中一个顶点的,因此可以让G->vertextlist[s].firstedge指向它*/
G->vertextlist[s].firstedge = edge;
/*G->vertextlist[e]里的结点表示的边都以e为其中一个顶点*/
edge->jlink = G->vertextlist[e].firstedge;
/*当前这条边也是以e为其中一个顶点的,因此可以让G->vertextlist[e].firstedge指向它*/
G->vertextlist[e].firstedge = edge;
}
}
c. 综合起来,图的创建:
void CreateGraph() {
VertexListInit();
EdgeListCreate();
}
C. 把上述内容封装起来,创建一个多重邻接表表示图的类:
#define Maximum 1000
typedef struct EdgeListNode{
int ivex;
EdgeListNode* ilink;
int jvex;
EdgeListNode* jlink;
int weight;
};
typedef struct VertexListNode{
int data;
EdgeListNode* firstedge;
};
typedef struct GraphMultiAdjList{
int vertexnumber;
int edgenumber;
VertexListNode vertextlist[Maximum];
};
class DirectGraph {
public:
GraphMultiAdjList* G;
DirectGraph() {
G = (GraphMultiAdjList*)malloc(sizeof(GraphMultiAdjList));
}
void VertexListInit() {
int i, j, k;
cout<<"Please enter the number of vertexes"<<endl;
G->vertexnumber = i;
cout<<"Please enter the data of each vertex in term"<<endl;
for(j=0; j<i; j++) {
cin>>k;
k = G->vertextlist[j].data;
G->vertextlist[j].firstedge = NULL;
}
}
void EdgeListCreate() {
int i, j, k, s, e, w;
cout<<"Please enter the number of edges"<<endl;
cin>>i;
G->edgenumber = i;
cout<<"Please enter the information of each edge in term"<<endl;
for(j=0; j<i; j++) {
cin>>s>>e>>w;
if(s > e) {
k = s;
s = e;
e = k;
}
EdgeListNode* edge;
edge = (EdgeListNode*)malloc(sizeof(EdgeListNode));
edge->ivex = s;
edge->jvex = e;
edge->weight = w;
edge->ilink = G->vertextlist[s].firstedge;
G->vertextlist[s].firstedge = edge;
edge->jlink = G->vertextlist[e].firstedge;
G->vertextlist[e].firstedge = edge;
}
}
void CreateGraph() {
VertexListInit();
EdgeListCreate();
}
};
2.5 边集数组
(1)基本思想
边集数组是由两个一维数组组成的:一个用于存储顶点的信息(顶点数组);一个用于存储边信息(边数组)。在边集数组中要查找一个顶点的度需要遍历整个边数组,效率并不高,因此它并不适合对顶点进行的相关操作,而更适合于对边依次进行处理的操作。
(2)数据结构
a. 顶点数组:
int vertexlist[Maximum]; //存放每个顶点的权值
b. 边数组里每一项的结构:
typedef struct EdgeArrayNode{
int startvertex; //起点下标
int endvertex; //终点下标
int weight; //边的权值
};
c. 用边集数组表示的图的结构:
typedef struct GraphEdgeArray {
int vertexnumber; //顶点数
int edgenumber; //边数
int vertexlist[Maximum];
EdgeArrayNode edgearray[Maximum*Maximum];
};
(3)实际例子
最后
以上就是美好舞蹈为你收集整理的图——基本的图算法(一)图的存储结构图——基本的图算法(一)图的存储结构的全部内容,希望文章能够帮你解决图——基本的图算法(一)图的存储结构图——基本的图算法(一)图的存储结构所遇到的程序开发问题。
如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。
发表评论 取消回复