一.图的简介
图是计算机中比较重要的一种数据结构,在很多方面应用比较广泛比如:计算机网络中的路由算法,地图上搜索最短路径,搜索引擎等方面。当然图也是比较难理解的一种数据结构,数据结构之所以难是因为它将逻辑结构存储在计算机物理内存中比较难。
二.图的存储
数据结构其实就是存储+算法。算法是依赖于存储结构的(这里可能有异议,有的人觉得算法独立于存储)。下面探讨3种常用的图存储方式。
1.邻接矩阵(二维数组存储)

上面两种存储方式差不多,只不过矩阵A4存储的不能直接到达的距离为无穷大而已。这种方式存储比较简单,但是有着明显的缺点:当边很少,顶点很多的时候,数组中存储的有效值很少,这就造成内存的大大浪费。
代码:
void creat(vexList GV, adjmatrix GA, int n,int e)
{
int i,j,k,w;
cout << "输入"<<n<<"个顶点的值:"<<endl;
//初始化顶点数组
for(int i = 0; i < n; i++)
{ cin>>GV[i];}
//初始化邻接矩阵
for(int i = 0; i < n; i++)
for(int j = 0; j < n; j++)
{if(i==j) GA[i][j] = 0;
else GA[i][j] = maxValue;
}
//建立邻接数组
cout << "输入"<<e<<"条边:"<<endl;
for(int k = 0; k < e; k++)
{cin >> i >> j >>w;
GA[i][j] =GA[j][i] = w;
}
}
2.邻接表存储
邻接表存储其实就是链表存储,将一个点连接的点,全部链接到顶点上,这样的话就大大节省了内存。看下面的图:


创建图的代码:(无向图)
假设:NodeTable[]数组存储了每个链表的首节点,就是上面的邻接表的最前面的0,1,2,3,4,5
假设:构造函数已经构造好空的NodeTable[]
insertEdge(int v1,int v2,int weight)
{
if(v1>= 0 && v1 < maxNum && v2>= 0 && v2 < maxNum)
{
Edge *temp1,*temp2;
Edge *p = new Edge;
Edge *q = new Edge;
p->dest = v1;
p->cost = wight;
q->dest = v2;
q->cost = wight;
temp1 = NodeTable[v1];
temp2 = NodeTable[v2];
while(temp1!=NULL)
temp1=temp1->next;
while(temp2!=NULL)
temp2=temp2->next;
temp1->next = q;
temp2->next = p;
}
}
3.边集数组
编辑数组是利用一维数组存储图中所有边的一种方式。每个元素用来存储一条边。比较节省内存。对于无向图而言任意一条边只需要存储一次即可:


数据结构为:
struct Edge{
int fromvex;
int endvex;
int weight;
};
for(int k = 1; k <= 变数; k++)
{
cin>>i>>j>>w;
GE[k].fromvex = i;
GE[k].endvex = j;
GE[k].weight = w;
}
图的存储结构
图的存储结构除了要存储图中各个顶点的本身信息外,同时还要存储顶点与顶点之间的所有关系(边的信息),因此,图的结构比较复杂,很难以数据元素在存储区中的物理位置来表示元素之间的关系,但也正是由于其任意的特性,故物理表示方法很多。常用的图的存储结构有邻接矩阵、邻接表、十字链表和邻接多重表。
一、 邻接矩阵表示法
对于一个具有n个结点的图,可以使用n*n的矩阵(二维数组)来表示它们间的邻接关系。图8.10和图8.11中,矩阵A(i,j)=1表示图中存在一条边(Vi,Vj),而A(i,j)=0表示图中不存在边(Vi,Vj)。实际编程时,当图为不带权图时,可以在二维数组中存放bool值,A(i,j)=true表示存在边(Vi,Vj),A(i,j)=false表示不存在边(Vi,Vj);当图带权值时,则可以直接在二维数值中存放权值,A(i,j)=null表示不存在边(Vi,Vj)。
图8.10所示的是无向图的邻接矩阵表示法,可以观察到,矩阵对角线对称,即A(i,j)=A(j,i)。无向图邻接矩阵的第i行 或第i列非零元素的个数其实就是第i顶点的度。这表示无向图邻接矩阵存在一定的数据冗余。
图8.11所示的是有向图邻接矩阵表示法,矩阵并不延对角线对称,A(i,j)=1表示顶点Vi邻接到顶点Vj;A(j,i)=1则表示顶点Vi邻接自顶点Vj。两者并不象无向图邻接矩阵那样表示相同的意思。有向图邻接矩阵的第i行非零元素的个数其实就是第i个顶点的出度,而第i列非零元素的个数是第i个顶点的入度,即第i个顶点的度是第i行和第i列非零元素个数之和。
二、邻接表表示法
图的邻接矩阵存储方法跟树的孩子链表示法相类似,是一种顺序分配和链式分配相结合的存储结构。邻接表由表头结点和表结点两部分组成,其中图中每个顶点均对应一个存储在数组中的表头结点。如这个表头结点所对应的顶点存在相邻顶点,则把相邻顶点依次存放于表头结点所指向的单向链表中。如图8.12所示,表结点存放的是邻接顶点在数组中的索引。对于无向图来说,使用邻接表进行存储也会出现数据冗余,表头结点A所指链表中存在一个指向C的表结点的同时,表头结点C所指链表也会存在一个指向A的表结点。
有向图的邻接表有出边表和入边表(又称逆邻接表)之分。出边表的表结点存放的是从表头结点出发的有向边所指的尾顶点;入边表的表结点存放的则是指向表头结点的某个头顶点。如图所示,图(b)和(c)分别为有向图(a)的出边表和入边表。
以上所讨论的邻接表所表示的都是不带权的图,如果要表示带权图,可以在表结点中增加一个存放权的字段,其效果如图8.14所示
注意:观察图8.14可以发现,当删除存储表头结点的数组中的某一元素,有可能使部分表头结点索引号的改变,从而导致大面积修改表结点的情况发生。可以在表结点中直接存放指向表头结点的指针以解决这个 问题。在实际创建邻接表时,甚至可以使用链表代替数组存放表头结点或使用顺序表代替链表存放表结点。对所学的数据结构知识应当根据实际情况及所使用语言的特点灵活应用,切不可生搬硬套。