图的遍历
从图中某个顶点出发系统地访问图中所有顶点,使得每个顶点仅被访问一次,这一过程称作图的遍历。
深度优先搜索
——连通图的深度优先搜索遍历:从图中某个顶点V0 出发,访问此顶点,然后依次从V0的各个未被访问的邻接点出发深度优先搜索遍历图,直至图中所有和V0有路径相通的顶点都被访问到。
在下图假设约定右手原则:在没有碰到重复顶点的情况下,分叉路口始终是向右手边走,每路过一个顶点就做一个记号。
则此图的访问次序是:ABCDEFGHI
#include<stdio.h>
#include<stdlib.h>
// 邻接矩阵的深度有限递归算法
#define TRUE 1
#define FALSE 0
#define MAX 256
//---------图的数组(邻接矩阵)存储表示-------
#define INFINITY 65535 //最大值 代表无穷大
#define MAX_VERTEX_NUM 20 //最大顶点数
typedef struct{
char vex[20]; //顶点向量
int arcs[MAX_VERTEX_NUM][MAX_VERTEX_NUM]; //邻接矩阵
int vexnum,arcnum; //图的顶点数和弧数
}MGraph;
int visited[MAX_VERTEX_NUM];
//标记数组
void CreateMGraph(MGraph *G)
{
int i, j, k, w;
printf("请输入顶点数:\n");
scanf("%d",&G->vexnum);
printf("请输入边数:\n");
scanf("%d",&G->arcnum);
printf("请输入顶点:\n");
for( i=0; i < G->vexnum; i++ )
{
scanf("%c", &G->vex[i]);
}
//初始化矩阵
for(i=0 ; i<G->vexnum ;i++)
{
for(j=0 ;j <G->vexnum ;j++)
{
G->arcs[i][j] = 0;
}
}
//输入由顶点构成的边
printf("请输入与边关联的两个顶点的序号:\n");
for(j=k ; k<G->arcnum ; k++)
{
scanf("%d %d",&i ,&j);
G->arcs[i][j] = 1;
G->arcs[j][i] = 1;
}
}
void DFS(MGraph *G,int v)
{
int w;
printf("%c ",G->vex[v]);
visited[v] = 1;
for(w=0; w<G->vexnum ;w++)
{
if(G->arcs[v][w] == 1&& ! visited[w])
{
DFS(G,w);
}
}
}
//邻接矩阵的深度遍历操作
void DFSTraverse(MGraph *G)
{
int v;
for(v=0 ; v <G->vexnum ; v++)
{
visited[v] = 0;
}
for(v = 0;v<G->vexnum ;v++)
{
if(!visited[v])
{
DFS(G,v);
}
}
}
int main()
{
MGraph G;
CreateMGraph(&G);
printf("输出遍历的顶点:\n");
DFSTraverse(&G);
return 0;
}
广度优先搜索
类似于树的按层次遍历,从图中的某个顶点V0出发,并在访问此顶点之后依次访问V0的所有未被访问过的邻接点,之后按这些顶点被访问的先后次序依次访问它们的邻接点,直至图中所有和V0 有路径相通的顶点都被访问到。
若此时图中尚有顶点未被访问,则另选图中一个未曾被访问的顶点作起始点,重复上述过程,直至图中所有顶点都被访问到为止。
采用队列——
// 邻接矩阵的广度遍历算法
void BFSTraverse(MGraph G)
{
int i, j;
Queue Q;
for( i=0; i < G.numVertexes; i++ )
{
visited[i] = FALSE;
}
initQueue( &Q );
for( i=0; i < G.numVertexes; i++ )
{
if( !visited[i] )
{
printf("%c ", G.vex[i]);
visited[i] = TRUE;
EnQueue(&Q, i);
while( !QueueEmpty(Q) )
{
DeQueue(&Q, &i);
for( j=0; j < G.numVertexes; j++ )
{
if( G.art[i][j]==1 && !visited[j] )
{
printf("%c ", G.vex[j]);
visited[j] = TRUE;
EnQueue(&Q, j);
}
}
}
}
}
}
理解栈——
最小生成树
连通图(或强连通图)的所有顶点加上遍历过程中经过的边(或弧)可以构成图的生成树,根据遍历策略的不同,分别称为深度优先生成树和广度优先生成树。 对于不连通的无向图或不是强连通的有向图,从任意一个顶点出发一般不能系统地访问所有顶点,往往需要有两个或两个以上的出发点,这样便得到了生成森林。
最小生成树——
问题:假设要在 n 个城市之间建立通讯联络网,则连通 n 个城市只需要修建 n-1条线路,如何在最节省经费的前提下建立这个通讯网?
该问题等价于:构造网的一棵最小生成树,即:在 e 条带权的边中选取 n-1 条边(不构成回路),使“权值之和”为最小。
连通网的所有生成树中,各边权的总和为最小的生成树,称作该连通网的最小生成树(最小代价生成树)。
算法一:Prime(普里姆)算法
Prim算法在生成树的构造过程中,图中 n 个顶点分属两个集合:已落在生成树上的顶点集 U 和尚未落在生成树上的顶点集V-U ,则应在所有连通U中顶点和V-U中顶点的边中选取权值最小的边。
设置一个辅助数组,对当前V-U集中的每个顶点,记录和顶点集U中顶点相连接的代价最小的边:
struct {
VertexType adjvex; // U集中的顶点序号
VRType lowcost; // 边的权值
} closedge[MAX_VERTEX_NUM];
#define MAXVEX 100 // 最大顶点数
#define INFINITY 65535 // 用65535来代表无穷大
typedef struct
{
char vexs[MAXVEX]; // 顶点表
int arc[MAXVEX][MAXVEX]; // 邻接矩阵
int numVertexes, numEdges; // 图中当前的顶点数和边数
} MGraph;
void MiniSpanTree_Prime(MGraph G)
{
int min, i, j, k;
int adjvex[MAXVEX];
int lowcost[MAXVEX];
lowcost[0] = 0;
adjvex[0] = 0;
//初始化操作
for(i=1 ; i<G.numVertexes; i++)
{
lowcost[i] = G.arc[0][i];
adjvex[i] = 0;
}
//真正构造最小生成树的过程
for(i=1; i<G.numVertexes; i++)
{
min = INFINITY;
j = 1;
k = 0;
//遍历全部顶点
while(j<G.numVertexes )
{
//找出lowcosr数组已存储的最小权值
if(lowcost[j]!=0 && lowcost[j] < min)
{
min = lowcost[j] ;
k = j; // 将发现的最小权值的下标存入k,以待使用。
}
j++;
}
// 打印当前顶点边中权值最小的边
printf("(%d,%d)",adjvex[k], k);
lowcost[k] = 0;
// 将当前顶点的权值设置为0,表示此顶点已经完成任务,进行下一个顶点的遍历
// 邻接矩阵k行逐个遍历全部顶点
for(j = 1; j<G.numVertexes; j++)
{
if(lowcost[j] != 0 && G.arc[k][j]<lowcost[j]);
adjvex[j] = k;
}
}
}
算法二:Kruskal(克鲁斯卡尔)算法
考虑问题的出发点: 为使生成树上边的权值之和达到最小,则应使生成树中每一条边的权值尽可能地小。
具体做法: 首先构造一个只含 n 个顶点的子图 T,然后选一条权值最小的边,若在T 中添加它后不产生回路,则在 T 中加上这条边,否则舍弃之,选另一条权值最小的边,如此重复,直
至在 T 中加上第 n-1 条边为止。
用将邻接矩阵转化为边集数组
伪代码:
int Find(int *parent, int f)
{
while( parent[f] > 0 )
{
f = parent[f];
}
return f;
}
// Kruskal算法生成最小生成树
void MiniSpanTree_Kruskal(MGraph G)
{
int i, n, m;
Edge edges[MAGEDGE]; // 定义边集数组
int parent[MAXVEX]; // 定义parent数组用来判断边与边是否形成环路
for( i=0; i < G.numVertexes; i++ )
{
parent[i] = 0;
}
for( i=0; i < G.numEdges; i++ )
{
n = Find(parent, edges[i].begin); // 4 2 0 1 5 3 8 6 6 6 7
m = Find(parent, edges[i].end); // 7 8 1 5 8 7 6 6 6 7 7
if( n != m ) // 如果n==m,则形成环路,不满足!
{
parent[n] = m; // 将此边的结尾顶点放入下标为起点的parent数组中,表示此顶点已经在生成树集合中
printf("(%d, %d) %d ", edges[i].begin, edges[i].end, edges[i].weight);
}
}
}