图
图中将每个对象用一个顶点表示,并常用一个序号来标识一个顶点。
其中弧表示单向关系,边表示双向关系,用离散数学中的术语来说,则分别表示为非对称关系和对称关系。
弧用<A, B>表示(A为尾,B为头),边用(A, B)表示。
一个图G由两部分内容构成,即顶点(vertex)集合(V)和边(或弧edge)的集合(E),并用二元组(V, E)来表示,记做G = (V, E)
根据顶点间的关系是否有向而引入有向图和无向图。
给每条边或弧加上权值,这样的带权图称为网络。
若无向图中任意两点间都有一条边,则称此图G为无向完全图。(共有边数 n*(n-1)/2 )
若有向图中任意一个顶点到其余各点间均有一条弧,则称为有向完全图。(共有弧数 n*(n-1) )
若一个图G1是从G中选取部分顶点和部分边(或弧)组成,则称G1是G的子图。(注意,顶点和边必须都为子关系)
若无向图中两个顶点i, j之间存在一条边,则称i, j相邻接,并互为邻接点。
在有向图中,若存在弧<Vi, Vj>,也做Vi, Vj相邻接,但为区别弧的头、尾顶点,可进一步称做Vi邻接到Vj,Vj邻接于Vi。
与一个顶点相邻接的顶点数称为该顶点的度。
在有向图中,进入一个顶点的弧数称为该顶点的入度,从一个顶点发出的弧数为该顶点的出度,并将入度和出度之和作为该顶点的度。
一个顶点经过一定的可经路程到达另一个顶点,就为顶点之间的路径。
若某路径所经过的顶点不重复,则称此路径为简单路径。
若某路径的首尾相同,则称此路径为回路(或称为环)。
若某回路的中间不重复,则称之为简单回路。
若无向图中任意两点之间均存在路径,则称G为连通图,否则不连通,就存在若干个连通分量。
若有向图中任意两点间可以互相到达,则称为强连通图。
一个无向图,连通并且无回路,称这样的图为树。
若有向图中仅有一个顶点的入度为0,其余顶点的入度都为1,称此图为有向树,入度为0的顶点为根。
图的存储结构:
1。邻接矩阵表示
对n个顶点的图来说,其邻接矩阵为n*n阶的。
邻接矩阵的元素存放边(弧)的权值,对不存在的边(弧),则用0或∞表示。
定义格式如下:
#define n 6 /* 图顶点数 */
#define e 8 /* 图的边(弧)数 */
typedef struct
{
vextype vexs[n]; /* 顶点类型 */
datatype arcs[n][n]; /* 权值类型 */
} graph;
建立一个无向网络的算法:
CreateGraph(graph *G)
{
int i, j, k;
float w;
for (i=0; i<n; i++)
G->vexs[i] = getchar(); /* 读入顶点信息,创建表,这里用字符型 */
for (i=0; i<n; i++)
for (j=0; j<n; j++)
G->arcs[i][j] = 0; /* 邻接矩阵初始化 */
for (k=0; k<e; k++)
{
scanf("%d%d%f", &i, &j, &w); /* 读入边(vi, vj)上的权w(暂用float类型) */
G->arcs[i][j] = w;
G->arcs[j][i] = w;
}
}
2.邻接表表示法
将每个顶点的邻接点连成链表,并将各链表的表头指针合在一起(用数组或链表表示均可),其中每个头指针与该结点的信息合为一个整体结点。
如果将邻接表中各顶点的邻接表变为其前驱顶点即可,从而得到逆邻接表。
用邻接表存储网络时,需要将各条边(弧)的权值作为相应邻接结点中的一个字段。
结构:
typedef struct node
{
int adjvex; /* 邻接点域 */
struct node *next; /* 链域 */
datatype arc; /* 权值 */
} edgenode; /* 边表指针 */
typedef struct
{
vextype vertex; /* 顶点信息 */
edgenode *link; /* 边表头指针 */
} vexnode; /* 顶点表结点 */
vexnode gnode[n]; /* 整个图的构成 */
建立无向图的邻接表:
CreateAdjlist(gnode)
{
int i, j, k;
edgenode *s;
for (i=0; i<n; i++) /* 读入顶点信息 */
{
gnode[i].vertex = getchar();
gnode[i].link = NULL; /* 边表指针初始化 */
}
for (k=0; k<e; k++) /* 建立边表 */
{
scanf("%d%d", &i, &j); /* 读入边(vi,vj)的顶点序号 */
s = malloc(sizeof(edgenode)); /* 生成邻接点序号为j的表结点 */
s->adjvex = j;
s->next = gnode[i].link;
gnode[i].link = s; /* 将*s插入顶点vi的边表头部(插到头部比尾部简单) */
s = malloc(sizeof(edgenode)); /* 生成邻接点序号为i的边表结点*s */
s->adjvex = i;
s->next = gnode[j].link;
gnode[j].link = s; /* 将*s插入顶点vj的边表头部(最后四行由于是无向图,所以相互,两次) */
}
}
图的遍历算法及其应用
1.深度遍历
(1)访问V0
(2)依次从V0 的各个未被访问的邻接点出发深度遍历
(两句话说的非常清楚。是一种以深度为绝对优先的访问。)
2。深度优先搜索遍历算法
由于实际算法比较复杂,这里算法依赖两个函数来求解(对于不同的存储结构有不同的写法)
firstadj(G, v):返回图G中顶点v的第一个邻接点。若不存在,返回0。
nextadj(G, v, w):返回图G中顶点v的邻接点中处于w之后的那个邻接点。若不存在,返回0。
depth first search:
void dfs(graph G, int v)
{
int w;
visit(v);
visited[v] = 1;
w = firstadj(G, v)
while (w != 0)
{
if (visited[w] == 0)
dfs(w);
w = nextadj(G, v, w);
}
}
如果不是连通图,或者是有向图,那么访问一个v不可能遍历所有顶点。所以,需要再选择未被访问的顶点作为起点再调用dfs.
所以,深度遍历图的算法如下:
void dfs_travel(graph G)
{
int i;
for (i=1; i<=n; i++)
visited[i] = 0; //初始化各顶点的访问标志
for (i=1; i<=n; i++)
if (visited[i] == 0)
dfs(G, i);
}
3.广度优先搜索遍历算法
广度优先搜索遍历算法(bfs)是一种由近而远的层次遍历算法,从顶点V0出发的广度遍历bfs描述为:
(1)访问V0(可作为访问的第一层);
(2)假设最近一层的访问顶点依次为V1, V2, ..., Vk,则依次访问他们的未被访问的邻接点。
(3)重复2,直到找不到未被访问的邻接点为止。
void bfs(graph G, int V0)
{
int w;
int v;
queue Q;
init_queue(Q);
visit(V0);
visited[V0] = 1;
Enqueue(Q, V0);
while (!empty(Q))
{
v = Outqueue(Q);
w = firstadj(G, v);
while (w != 0)
{
if (visited[w] == 0)
{
visit(w);
visited[w] = 1;
Enqueue(Q, w);
}
w = nextadj(G, v, w);
}
}
}
广度遍历图的算法和深度一样:
void bfs_travel(graph G)
{
int i;
for (i=1; i<=n; i++)
visited[i] = 0;
for (i=1; i<=n; i++)
if (visited[i] = 0)
bfs(G, i);
}
最小生成树:
从图中选取若干条边,将所有顶点连接起来,并且所选取的这些边的权值之和最小。
这样所选取的边构成了一棵树,称这样的树为生成树,由于权值最小,称为最小生成树。
构造最小生成树有两种方法:
1.Prim算法:
首先将所指定的起点作为已选顶点,然后反复在满足如下条件的边中选择一条最小边,直到所有顶点成为已选顶点为止(选择n-1条边):一端已选,另一端未选。
(简单的说,就是先任选一点,然后每次选择一条最小权值的边,而且只连接到一个已选顶点)
2.Kruskal算法:
反复在满足如下条件的边中选出一条最小的,和已选边不够成回路。
(条件就是不够成回路就OK,反复选最小边,知道所有顶点都有连接)
最短路径:
一般即是要一个顶点到其余各个顶点的最短路径。(比如隔很远的顶点,要绕哪几条边走)
求解方法:
首先,我们要画一个表,每个顶点有path和dist两个值,分别用来存储到各点的最短路径(比如(1,5,6),就是1-5-6这个路径)和相应的长度(到该点的权值之和)。
(1)对V以外的各顶点,若两点间的邻接路径存在,则将其作为最短路径和最短长度存到path[v]和dist[v]中。(实际上也就是最开始对顶点的直接后继进行处理)
(2)从未解顶点中选择一个dist值最小的顶点v,则当前的path[v]和dist[v]就是顶点v的最终解(从而使v成为已解顶点)。
(3)如果v的直接后继经过v会更近一些,则修改v的直接后继的path和dist值。
(上面的确是很难懂,只能通过例子自己慢慢熟悉。)