图论在算法中可谓是有着十分显赫的地位的,那么学习一些关于图论的算法还需要先去学习一下数据结构中的图的相关知识,两者是互通的——数据结构教会我们如何构造一个图,而算法则是如何使用和操作一个图。
图的定义和术语
图一般用< V,E >来表示,V表示顶点集,E表示边集
接下来用n来表示一个图的顶点的个数,用e表示边的个数
图一般分为 :
1.有向图 :
-边叫做弧,用< v , w >来表示从v到w的一条弧;
-e的范围是[0,n(n-1)],e等于n(n-1)称为有向完全图。
-顶点的度等于出度加上入度
2.无向图(特殊的有向图) :
-边就叫做边,用( v , w )来表示从v到w的一条边
-e的范围是[0,1/2n(n-1)],e等于1/2n(n-1)称为完全图。
-顶点的度就是和v相关联的边的数目
无向图其实就是特殊的一种有向图,而有向图不是无向图,既有弧又有边的叫混合图,我们一般要么分析有向图要么分析无向图。
而路径、回路、简单路径、连通图、强连通图这些定义不再赘述
严蔚敏老师的《数据结构c语言版》详细地描述了这些定义。
图的存储结构:
我在这只提出两个结构,因为这两个结构最常用,也最好用,其他结构不再多说。
1.邻接矩阵:
大二上学期刚学过离散,这个定义非常熟悉。
用一个二维数组存放顶点间关系(边或弧)的数据,这个二维数组就是邻接矩阵
比如上面的那个有向图,它的顶点集为{0,1,2,3},边集为{<0,1>,<1,3>,<3,2>,<2,0>}
那么它的邻接矩阵表示为
0 1 2 3
0 0 1 0 0
1 0 0 0 1
2 1 0 0 0
3 0 0 1 0
2.邻接表
它是图的一种链式存储结构。
其实在学《数据结构c语言版》中的邻接表的表示时并不喜欢,因为它需要定义表结点、头结点,具体表示代码如下图所示,参考了其他人的代码。
#include<iostream>
#include<cstdio>
#include<cstdlib>
using namespace std;
#define MAXVEX 100 /* 最大顶点数,应由用户定义 */
typedef char VertexType; /* 顶点类型应由用户定义 */
typedef int EdgeType; /* 边上的权值类型应由用户定义 */
typedef struct EdgeNode/* 表结点 */
{
int adjvex;/* 邻接点域,存储该顶点对应的下标 */
EdgeType weight;/* 用于存储权值,对于非网图可以不需要 */
struct EdgeNode *next; /* 链域,指向下一个邻接点 */
} EdgeNode;
typedef struct VextexNode/* 顶点头结点 */
{
VertexType data;/* 顶点域,存储顶点信息 */
EdgeNode *firstedge;/* 边表头指针 */
} VextexNode, AdjList[MAXVEX];
typedef struct
{
AdjList adjList;
int numNodes, numEdges; /* 图中当前顶点数和边数 */
} GraphAdjList;
void CreateALGraph(GraphAdjList *Gp)
{
int i, j, k;
EdgeNode *pe;
cout << "输入顶点数和边数(空格分隔):" << endl;
cin >> Gp->numNodes >> Gp->numEdges;
for (i = 0 ; i < Gp->numNodes; i++)
{
cout << "输入顶点信息:" << endl;
cin >> Gp->adjList[i].data;
Gp->adjList[i].firstedge = NULL;/* 将边表置为空表 */
}
for (k = 0; k < Gp->numEdges; k++)/* 建立边表 */
{
cout << "输入边(vi,vj)的顶点序号i,j(空格分隔):" << endl;
cin >> i >> j;
pe = (EdgeNode *)malloc(sizeof(EdgeNode));
pe->adjvex = j;/* 邻接序号为j */
/* 将pe的指针指向当前顶点上指向的结点 */
pe->next = Gp->adjList[i].firstedge;
Gp->adjList[i].firstedge = pe;/* 将当前顶点的指针指向pe */
pe = (EdgeNode *)malloc(sizeof(EdgeNode));
pe->adjvex = i;
pe->next = Gp->adjList[j].firstedge;
Gp->adjList[j].firstedge = pe;
}
}
int main(void)
{
GraphAdjList GL;
CreateALGraph(&GL);
return 0;
}
其实真的很复杂,构造一个邻接表就要花一大堆时间,那么有什么快速建立邻接表的方法吗?那就是用STL中的vector来建。
这是我总结的STL vector的文章:
http://blog.csdn.net/karry_zzj/article/details/68954252
#include<cstdio>
#include<iostream>
#include<vector>
using namespace std;
const int maxn = 1000;
vector<int> G[maxn];
int d[maxn];
int main()
{
int V,E;
scanf("%d%d",&V,&E);
int s,t;//从s向t连边
for(int i=0;i<V;i++)
{
scanf("%d",&d[i]);
}
for(int i=0;i<E;i++)
{
scanf("%d%d",&s,&t);
G[s].push_back(t);
}
for(int i=0;i<V;i++)
{
printf("顶点%d的邻接点有:",d[i]);
if(G[d[i]].empty()) printf("NULL");
else{
for(int j=0;j<G[d[i]].size();j++)
{
printf("%d ",G[d[i]][j]);
}
}
printf("\n");
}
return 0;
}
看看是不是超级简单方便。
自从我知道用vector可以表示邻接表,我就只用这种方式了。
邻接表和邻接矩阵的优缺点:
-邻接矩阵:
优点:查找两个顶点是否有边相连,那么使用邻接矩阵就十分快速方便了,建立矩阵也没有复杂度。
缺点:如果一个图是稀疏图,那么开一个二维数组,只有少数几个元素是1,其余都是0,那么很明显这个数组是十分浪费空间的。
-邻接表:
优点:如果图是稀疏图,那么它的优势是极其大的
缺点:如果需要查找图中两个顶点是否有边相连,需要遍历这个表,时间是很慢的。
所以,我们如果采取“空间换时间”的思想(毕竟现在电脑空间足够大),那么还是用邻接矩阵来表示图更好一些,但是如果顶点很多,边很稀疏,何不用vector来实现呢,多方便啊!