1.前言
图是一种比线性结构和树结构更为复杂的数据结构。其中结点可以具有零个或多个相邻元素。两个结点之间的连接称为边。结点也可以称为顶点。至于图的定义以及图的基本术语多看多理解就懂了,我直接开始介绍图的一些存储结构吧。
2.邻接矩阵
用邻接矩阵表示法表示图,除了一个用于存储邻接矩阵的二维数组外,还需要用一个一维数组来存储顶点信息。话不多说,直接上代码部分。
#include<stdio.h>
#include<stdlib.h>
#include<malloc.h>
#define MAX 100 // 矩阵最大容量
#define INF 9999 // 最大值
#define isLetter(a) ((((a)>='a')&&((a)<='z')) || (((a)>='A')&&((a)<='Z')))
typedef struct _graph
{
char vexs[MAX]; // 顶点集合
int vexnum; // 顶点数
int edgnum; // 边数
int matrix[MAX][MAX]; // 邻接矩阵
}Graph, * PGraph;
/* 返回名为ch的顶点在图G中的位置 */
static int get_position(Graph G, char ch)
{
int i;
for (i = 0; i < G.vexnum; i++)
if (ch == G.vexs[i])//与顶点集合中的元素相比较
return i;
return -1;
}
/* 读取一个输入字符 */
static char read_char()
{
char ch;
do {
ch = getchar();
} while (!isLetter(ch));
return ch;
}
Graph* create_graph()
{
Graph* pG;
char c1, c2;
int i, j,w,p1,p2;
int v, e; //v为图G中的顶点个数,e为边的个数
printf("请输入图G中顶点的个数: ");
scanf_s("%d", &v);
printf("请输入图G中边的个数: ");
scanf_s("%d", &e);
while (v < 1 || e < 1 || (e > (v * (v - 1)))) // 有向完全图有(v * (v-1))条边,超过次数则不符
{
printf("输入错误: 无效的参数!请核实!\n");
printf("请输入图G中顶点的个数: ");
scanf_s("%d", &v);
printf("请输入图G中边的个数: ");
scanf_s("%d", &e);
}
if ((pG = (Graph*)malloc(sizeof(Graph))) == NULL)
return NULL;
memset(pG,0,sizeof(Graph)); /* menset 算法*/
// 初始化图的"顶点个数"和"边的条数"
pG->vexnum = v;
pG->edgnum = e;
// 初始化"顶点"信息
printf("\n请输入%d个顶点的字母标号:\n", v);
for (i = 0; i < pG->vexnum; i++)
{
printf("顶点(%d): ", i + 1);
pG->vexs[i] = read_char(); /* scanf("%c",&pG->vexs[i]);*/
}
// 1. 初始化"边"权值的初始值
for (i = 0; i < pG->vexnum; i++)
{
for (j = 0; j < pG->vexnum; j++)
{
if (i == j)
pG->matrix[i][j] = 0; //对角线上的元素都为0
else
pG->matrix[i][j] = INF; //非对角线上的元素都为无穷大
}
}
// 2. 根据用户的输入信息,设置边的权值
printf("\n请输入%d条边的信息(起点 终点 权值):\n", e);
for (i = 0; i < pG->edgnum; i++)
{
// 读取边的起始顶点,结束顶点,权值
printf("第%d条边:", i + 1);
c1 = read_char(); //输入边上的一个字符
c2 = read_char(); //输入边上的另一个字符
scanf_s("%d", &w); //输入边上的权值
p1 = get_position(*pG, c1); //查找c1字符在图顶点数组对应下标
p2 = get_position(*pG, c2); //查找c2字符在图顶点数组对应下标
while (p1 == -1 || p2 == -1)
{
printf("输入错误: 无效的边!请重新输入:\n");
printf("边(%d):", i + 1);
c1 = read_char();
c2 = read_char();
scanf_s("%d", &w);
p1 = get_position(*pG, c1);
p2 = get_position(*pG, c2);
}
pG->matrix[p1][p2] = w; //修改图邻接矩阵中对应边的权值
pG->matrix[p2][p1] = w;
}
return pG;
}
首先我们从键盘输入我们想要图的顶点以及边的个数,如果输入不合法则重新输入,在这当中要注意memset函数。
memset函数定义于<string.h>头文件中。
函数原型:
extern void *memset(void *buffer, int c, int count)
buffer:为指针或是数组
c:是赋给buffer的值
count:赋值buffer中的位数
函数功能:为指针变量buffer所指的前n个字节的内存单元填充给定的int型数值c,它可以为任何数据进行初始化。换句话说,就是将数值c以单个字节逐个拷贝的方式放到指针变量buffer所指的内存中去。 注意:只将数值c的最低一个字节填充到内存。
void print_graph(Graph G)
{
int i, j;
printf("\n图G的权值矩阵:\n");
for (i = 0; i < G.vexnum; i++)
{
for (j = 0; j < G.vexnum; j++)
printf("%10d ", G.matrix[i][j]);
printf("\n");
}
}
我们写一个函数将图用二维矩阵打印出来。
3.图的遍历
和树的遍历类似,图的遍历也是从图中某一顶点出发,按照某种方法对图中所有顶点进行访问且仅访问一次。图的遍历算法是求解图的连通性问题、拓扑排序和关键路径等算法的基础。
根据搜索路径的方向,通常有两条遍历图的路径:深度优先搜索和广度优先搜索。
深度优先搜索
深度优先搜索(Depth First Search, DFS)遍历类似于树的先序遍历,是树的先序遍历的推广。
/* 返回顶点v的第一个邻接顶点的索引(下标),失败则返回-1 */
static int first_vertex(Graph G, int v) //v是顶点在图中顶点数组的下标
{
int i;
if (v<0 || v>(G.vexnum - 1))
return -1;
for (i = 0; i < G.vexnum; i++)
if (G.matrix[v][i] != 0 && G.matrix[v][i] != INF) //邻接矩阵中不在对角线上和不是无穷大
return i;
return -1;
}
/* 返回顶点v相对于w的下一个邻接顶点的索引,失败则返回-1 */
static int next_vertix(Graph G, int v, int w)
{
int i;
if (v<0 || v>(G.vexnum - 1) || w<0 || w>(G.vexnum - 1))
return -1;
for (i = w + 1; i < G.vexnum; i++)
if (G.matrix[v][i] != 0 && G.matrix[v][i] != INF)
return i;
return -1;
}
/* 深度优先搜索遍历图的递归实现 */
void DFS(Graph G, int i, int* visited) { //从下标为i的顶点出发,遍历该顶点所有邻接点
int w;
visited[i] = 1;
printf("%c ", G.vexs[i]);
// 遍历该顶点的所有邻接顶点。若是没有访问过,那么继续往下走
for (w = first_vertex(G, i); w >= 0; w = next_vertix(G, i, w))
{
if (visited[w] == 0)
DFS(G, w, visited);
}
}
/* 深度优先搜索遍历图 */
void DFSTraverse(Graph G)
{
int i;
int visited[MAX]; // 顶点访问标记
// 初始化所有顶点都没有被访问
for (i = 0; i < G.vexnum; i++)
visited[i] = 0;
printf("\n深度优先搜索遍历(DFS): ");
for (i = 0; i < G.vexnum; i++)//按照下标次序和顶点关系,深度遍历图中顶点
{
if (visited[i] == 0)
DFS(G, i, visited);
}
printf("\n");
}
广度优先搜索
广度优先搜索(Breadth First Search,BFS)遍历类似于树的按层次遍历的过程。
/* * 广度优先搜索(类似于树的层次遍历) */
void BFS(Graph G)
{
int head = 0;
int rear = 0;
int queue[MAX]; // 辅组队列,用来存放读取的顶点下标
int visited[MAX]; // 顶点访问标记
int i, j, k;
for (i = 0; i < G.vexnum; i++) //设置所有顶点状态均未访问
visited[i] = 0;
printf("\n广度优先搜索遍历(BFS): ");
for (i = 0; i < G.vexnum; i++)
{
if (visited[i]==0)
{
visited[i] = 1;
printf("%c ", G.vexs[i]);
queue[rear++] = i; // 入队列
}
while (head != rear)
{
j = queue[head++]; // 出队列
for (k = first_vertex(G, j); k >= 0; k = next_vertix(G, j, k)) //k是为访问的邻接顶点
{
if (visited[k]==0)
{
visited[k] = 1;
printf("%c ", G.vexs[k]);
queue[rear++]=k;
}
}
}
}
printf("\n");
}
和深度优先算法搜索类似,广度优先搜索在遍历的过程中也需要一个访问标志数组。
int main()
{
int prev[MAX] = {0};
int dist[MAX] = {0};
Graph* pG;
printf(" **************************************************\n");
printf(" 图 的 常 用 算 法\n");
printf(" **************************************************\n\n");
//自定义"图"(输入矩阵队列)
pG = create_graph();
print_graph(*pG); // 打印图
DFSTraverse(*pG);
BFS(*pG);
return 0;
}
现实生活中的许多问题都可以利用图来解决,对于图的最小生成树和克鲁斯卡尔算法,下次有机会再分享吧
4.总结
代码部分如果有错误或者可以优化希望大佬们提出来,我再加以改进。最后我们一起进步!