图的表示
邻接矩阵在边数相对点数较少时会浪费存储空间。使用数组和链表相结合的邻接表存储。
图的存储————邻接矩阵法
有向图中,第i个结点的出(入)度=第i行(列)的非零元素个数,第i个结点的度= 第i行和第i列的非零个数之和
无向图中,第i个结点的度 = 第i行(列)的非零元素个数,无向图的邻接矩阵是对称矩阵可压缩
对于n个顶点和e条边的图,时间复杂度是 O ( n + n 2 + e ) O(n+n^{2}+e) O(n+n2+e)
typedef char VertexType;
typedef int EdgeType;
/*邻接矩阵在边数相对点数较少时会浪费存储空间。使用数组和链表相结合的邻接表存储。*/
typedef struct
{
VertexType vexs[MAXVEX]; //顶点表
EdgeType arc[MAXVEX][MAXVEX]; //邻接矩阵
int numVertexes, numEdges; //图中当前的顶点数和边数
}MGraph;
/* 图的存储————邻接矩阵法存储带权图*/
void CreateMGraph(MGraph *G)
{
int i, j, k, w;
printf("输入顶点数和边数:\n");
scanf("%d,%d", &(G->numVertexes), &(G->numEdges));
for (i = 0; i < G->numVertexes; i++)
scanf(&G->vexs[i]); /*输入顶点,建立顶点表*/
for (i = 0; i < G->numVertexes; i++)
for (j = 0; j < G->numVertexes; j++)
G->arc[i][j] = INFINITY; /*邻接矩阵初始化*/
for (k = 0; k < G->numEdges; k++) /*读入numEdges条边,建立邻接矩阵*/
{
printf("输入边(vi,vj)的下标i,下标j,权重w: \n");
scanf("%d,%d,%d", &i, &j, &w);
G->arc[i][j] = w;
G->arc[j][i] = G->arc[i][j]; /*无向网,矩阵对称*/
}
}
图的存储————邻接表法
//typedef char VertexType; //顶点类型
//typedef int EdgeType; //边上的权值类型
typedef struct EdgeNode /*边表结点*/
{
int adjvex; /*邻接点域,顶点对应的下标*/
EdgeType weight; /*存储权值,非网图可以不需要*/
struct EdgeNode *next; /*链域,指向下一个邻接点*/
}EdgeNode;
typedef struct VertexNode /*顶点表结点*/
{
VertexType data; //顶点域,存储顶点信息
EdgeNode *firstedge; /*边表头指针*/
}VertexNode, AdjList[MAXVEX];
//邻接表的数据结构
typedef struct
{
AdjList adjList;
int numVertexes, numEdges; //图中当前顶点数和边数
}GraphAdjList;
/*建立无向图的邻接表*/
void CreateALGraph(GraphAdjList *G)
{
int i, j, k; //i.j代表邻接的两个点,k表示点的数量
EdgeNode *e;
printf("输入顶点数和边数:\n");
scanf("%d,%d", &(G->numVertexes), &(G->numEdges));
for (i = 0; i < G->numVertexes; i++) //读入顶点信息,建立顶点表
{
scanf(&(G->adjList[i].data));
G->adjList[i].firstedge = NULL; //将边表置为空表
}
for (k = 0; k < G->numVertexes; k++) //建立边表
{
printf("输入边(vi,vj)的顶点序号:\n");
scanf("%d,%d", &i, &j); //输入边(vi,vj)的顶点序号
e = (EdgeNode *)malloc(sizeof(EdgeNode));
//使用头插法;对于无向图,一条边对应都是两个顶点,因此在循环中一次就针对i和j分别进行了插入
e->adjvex = j; /*邻接序号为j,则是顶点i的链表*/
e->next = G->adjList[i].firstedge; /*e的下一个变为头结点指针的指向*/
G->adjList[i].firstedge = e; //将当前顶点的指针指向e
e = (EdgeNode *)malloc(sizeof(EdgeNode));
e->adjvex = i; /*邻接序号为i,则是顶点j的链表*/
e->next = G->adjList[j].firstedge; /*e的下一个变为头结点指针的指向*/
G->adjList[j].firstedge = e; //将当前顶点的指针指向e
}
}
图的遍历
从图中某一顶点出发访遍图中其余顶点,且使每一个顶点仅被访问一次
空间的复杂度:O(|V|)
邻接矩阵:时间复杂度O(|V|^2)要对每个顶点进行入队操作,找到所有的邻接结点
邻接表法:时间复杂度O(|V|+|E|)
深度优先遍历(Depth_First_Search)
我们假设初始状态所有顶点都没被访问,然后从每一个顶点v出发先访问该顶点,再从它的各个未被访问的邻接点出发,深度优先遍历图,直到图中所有和v相通的顶点都被访问到。遍历完后,还与欧其他顶点没被访问到,则另选一个未被访问的顶点作为起始点,重复上述过程直到所有的顶点都被访问完为止。对于连通图,对于非连通图,只需要对它的连通分量分别进行深度优先遍历。
#define MAX 1000
#define TRUE 1
#define FALSE 0
typedef int Boolean;
Boolean visited[MAX];
typedef struct
{
VertexType vexs[MAXVEX]; /*顶点表*/
EdgeType arc[MAXVEX][MAXVEX]; /*边表,即邻接矩阵*/
int numVertexes, numEdges;
}MGraph;
邻接矩阵的深度优先递归算法
//邻接矩阵的深度优先递归算法
void DFS_AdjMat(MGraph G, int i) //i点,对与i相邻的点进行访问
{
int j;
visited[i]= TRUE;
printf("%c",G.vexs[i]); //打印顶点
for(j=0;j<G.numVertexes;j++)
if(G.arc[i][j]==1 && !visited[j]) //当j没有被访问时,且i,j相邻时
DFS_AdjMat(G,j); //对为访问的邻接顶点递归调用
}
//邻接矩阵的深度优先遍历操作
void DFSTraverse_AdjMAT(MGraph G)
{
int i;
for (i = 0; i<G.numVertexes;i++)
visited[i] = FALSE; //初始所有顶点都是为访问过的状态
for (i=0;i<G.numVertexes;i++)
if (!visited[i]) //对未访问过的顶点调用DFS,若是连通图,则只会执行一次
DFS_AdjMat(G,i);
}
邻接表的深度优先递归算法
//邻接表的深度优先递归算法
void DFS_AdjList(GraphAdjList GL,int i)
{
EdgeNode *p;
visited[i] =TRUE;
printf("%c",GL.adjList[i].data);
p = GL.adjList[i].firstedge;
//每次递归都指向 GL.adjList[p->adjvex].firstedge,如果已经被遍历则找该结点的其他相邻结点
while (p)
{
if (!visited[p->adjvex]) //对未访问过的顶点调用DFS_AdjList,若是连通图,则只会执行一次
DFS_AdjList(GL,p->adjvex);
p= p->next;
}
}
//邻接表的深度优先遍历操作
void DFSTraverse_AdjList(GraphAdjList GL)
{
int i;
for (i = 0; i<GL.numVertexes;i++)
visited[i] = FALSE; //初始所有顶点都是为访问过的状态
for (i=0;i<GL.numVertexes;i++)
if (!visited[i]) //对未访问过的顶点调用DFS,若是连通图,则只会执行一次
DFS_AdjList(GL,i);
}
广度优先遍历(Breadth-First Search)
从图中的某一个顶点v出发,在访问了v以后依次访问v的各个没有访问到的邻接点,然后分别从这些邻接点出发依次访问他们的邻接点使得掀背访问的顶点的邻接点先于后被访问到的邻接点被访问,直到图中所有已经被访问的顶点的邻接点都被访问到。如果此时图中尚有顶点未被访问,则需要另选一个未曾被访问到的顶点作为新的起始点,重复上述过程
广度优先往往用到队列
#define OK 1
#define ERROR 0
#define TRUE 1
#define FALSE 0
#define MAXSIZE 1000
typedef int Status;
//在可以确定队列长度最大值的情况下,建议使用循环队列;当无法预估队列的长度,则用链队列
typedef int QElemType; //视情况而定
/*循环队列的顺序存储结构*/
typedef struct
{
QElemType data[MAXSIZE];
int front; /*头指针*/
int rear; /*尾指针,若队列不空,指向队列元素的下一个位置*/
}Queue;
/*初始化一个空队列*/
Status InitQueue(Queue *Q)
{
Q->front= 0;
Q->rear=0;
}
/*返回Q的元素个数,也就是队列的当前长度*/
Status QueueLength(Queue Q)
{
return (Q.rear - Q.front+MAXSIZE)%MAXSIZE;
}
/*循环队列的入队列操作代码*/
Status EnQueue(Queue *Q,QElemType e) {
if ((Q->rear + 1) % MAXSIZE == Q->front) /*队列满的判断*/
return ERROR;
Q->data[Q->rear] = e; //将元素e赋值给队尾
Q->rear = (Q->rear + 1) % MAXSIZE; //rear指针向后移一个位置,在末尾则转到数组头部
}
/*循环队列的出队列操作代码*/
Status DeQueue(Queue *Q,QElemType *e)
{
if (Q->front == Q->rear)
return ERROR;
*e= Q->data[Q->front]; //将队头元素赋值给e
Q->front=(Q->front+1)%MAXSIZE; //front指针向后移一位置,若到最后转到数组头部
}
//判断队列是否为空
Boolean QueueEmpty(Queue *Q){
if (Q->front== 0&&Q->rear==0)
return FALSE;
}
邻接矩阵的广度优先递归算法
void BFSTraverse_AdjMAT(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]) //若是未访问过就处理
{
visited[i]=TRUE; //设置当前顶点访问过
printf("%c",G.vexs[i]); //打印顶点
EnQueue(&Q,i); //将此顶点入队列
while(!QueueEmpty(&Q))
{
DeQueue(&Q,&i); //将队中元素出队列,赋值给i
for (j=0;j<G.numVertexes;j++)
{
//判断其他顶点若与当前顶点存在边且未被访问过
if (G.arc[i][j]==1&&!visited[j])
{
visited[j]=TRUE; // 将找到的顶点标记为已访问
printf("%c",G.vexs[j]); //打印顶点
EnQueue(&Q,j); //将找到的顶点入队列
}
} //对队列中的每个i进行遍历,找到下一层且与i相邻的点进行遍历
} //若当前队列不为空,广度优先以相邻的边结点作为下一层
}
}
}
邻接矩阵的广度优先递归算法
void BFSTraverse_AdjList(GraphAdjList GL)
{
int i;
EdgeNode *p;
Queue Q;
for (i=0;i<GL.numVertexes;i++) //访问标记数组初始化
visited[i] = FALSE;
InitQueue(&Q); //初始化辅助用的队列
for( i = 0; i<GL.numVertexes;i++) //对每个顶点做循环
{
if(!visited[i]) //若是未访问过就处理
{
visited[i]=TRUE; //设置当前顶点访问过
printf("%c",GL.adjList[i].data); //打印顶点
EnQueue(&Q,i); //将此顶点入队列
while(!QueueEmpty(&Q))
{
DeQueue(&Q,&i); //将队中元素出队列,赋值给i
p = GL.adjList[i].firstedge;
while (p)
{
if (!visited[p->adjvex])
{
visited[p->adjvex]=TRUE; // 将找到的顶点标记为已访问
printf("%c",GL.adjList[p->adjvex].data); //打印顶点
EnQueue(&Q,p->adjvex); //将找到的顶点入队列
}
p=p->next; //指针指向下一个邻接点
}
}//对队列中的每个i进行遍历,找到下一层且与i相邻的点进行遍历
}
}
}
邻接矩阵的深(广度)优先递归算法案例实现
#include <stdio.h>
#include <stdlib.h>
#include <math.h>
#include <string.h>
#define isLetter(a) ((((a)>='a')&&((a)<='z')) || (((a)>='A')&&((a)<='Z')))
/*
* 返回ch在matrix矩阵中的位置
*/
static int get_position(MGraph G, char ch)
{
int i;
for(i=0; i<G.numVertexes; i++)
if(G.vexs[i]==ch)
return i;
return -1;
}
/*
* 读取一个输入字符
*/
static char read_char()
{
char ch;
do {
ch = getchar();
} while(!isLetter(ch));
return ch;
}
//创建图(用已提供的矩阵)
MGraph* create_example_graph()
{
char vexs[] = {
'A', 'B', 'C', 'D', 'E', 'F', 'G'};
char edges[][2] = {
{
'A', 'B'},
{
'B', 'C'},
{
'B', 'E'},
{
'B', 'F'},
{
'C', 'E'},
{
'D', 'C'},
{
'E', 'B'},
{
'E', 'D'},
{
'F', 'G'}};
MGraph* pG;
// 输入"顶点数"和"边数"
if ((pG=(MGraph*)malloc(sizeof(MGraph))) == NULL )
return NULL;
memset(pG, 0, sizeof(MGraph)); //在一段内存块中填充某个给定的值,是对较大的结构体或数组进行清零操作的方法。
// 初始化"顶点数"和"边数"
pG->numVertexes=(sizeof(vexs)/sizeof(vexs[0]));
pG->numEdges = (sizeof(edges)/sizeof(edges[0]));
int i, p1, p2;
// 初始化"顶点"
for (i = 0; i < pG->numVertexes; i++)
{
pG->vexs[i] = vexs[i];
}
// 初始化"边"
for (i = 0; i < pG->numEdges; i++)
{
// 读取边的起始顶点和结束顶点
p1 = get_position(*pG, edges[i][0]);
p2 = get_position(*pG, edges[i][1]);
pG->arc[p1][p2] = 1;
}
return pG;
}
/*
* 打印矩阵队列图
*/
void print_graph(MGraph G)
{
int i,j;
printf("Martix Graph:\n");
for (i = 0; i < G.numVertexes; i++)
{
for (j = 0; j < G.numVertexes; j++)
printf("%d ", G.arc[i][j]);
printf("\n");
}
}
/*
* 创建图(自己输入)
*/
MGraph* create_graph()
{
char c1, c2;
int v, e;
int i, p1, p2;
MGraph* pG;
// 输入"顶点数"和"边数"
printf("input vertex number: ");
scanf("%d", &v);
printf("input edge number: ");
scanf("%d", &e);
if ( v < 1 || e < 1 || (e > (v * (v-1))))
{
printf("input error: invalid parameters!\n");
return NULL;
}
if ((pG=(MGraph*)malloc(sizeof(MGraph))) == NULL )
return NULL;
memset(pG, 0, sizeof(MGraph));
// 初始化"顶点数"和"边数"
pG->numVertexes = v;
pG->numEdges = e;
// 初始化"顶点"
for (i = 0; i < pG->numVertexes; i++)
{
printf("vertex(%d): ", i);
pG->vexs[i] = read_char();
}
// 初始化"边"
for (i = 0; i < pG->numEdges; i++)
{
// 读取边的起始顶点和结束顶点
printf("edge(%d):", i);
c1 = read_char();
c2 = read_char();
p1 = get_position(*pG, c1);
p2 = get_position(*pG, c2);
if (p1==-1 || p2==-1)
{
printf("input error: invalid edge!\n");
free(pG);
return NULL;
}
pG->arc[p1][p2] = 1;
}
return pG;
}
void main()
{
MGraph* pG;
// 自定义"图"(输入矩阵队列)
//pG = create_graph();
// 采用已有的"图"
pG = create_example_graph();
print_graph(*pG); // 打印图
printf("深度优先遍历结果\n");
DFSTraverse_AdjMAT(*pG); // 深度优先遍历
printf("\n");
printf("广度优先遍历结果\n");
BFSTraverse_AdjMAT(*pG); // 广度优先遍历
}
参考资料:
1.《大话数据结构》
2.https://github.com/wangkuiwu/datastructs_and_algorithm/blob/master/source/graph/iterator/dg/c/matrix_dg.c(创建图部分)