1. 概念
图(Graph
)是一种非线性数据结构。
2. 术语
- 有向图和无向图
- 网: 弧边上有权值,带权值得图成为网
- 顶点的度
- 路径:路径上边的条数定义为该路径的长度
3. 特征
任意的两个元素都可能相关,即图中任一元素可以有若干个直接前驱和直接后继,属于网状结构类型。
4. 存储结构
4.1. 邻接矩阵(数组)
#include <stdio.h>
#include <string.h>
#define N 5
typedef struct
{
int V[N];
int R[N][N];
} adjmatrix_t;
int main(int argc, const char *argv[])
{
adjmatrix_t graph;
int i, j;
// 1.初始顶点集合
for (i = 0; i < N; i++)
graph.V[i] = i;
// 2.将关系集合清空置0,然后输入顶点之间的关系
bzero(graph.R, sizeof(graph.R));
printf("请您输入顶点关系:\n");
while (scanf("(V%d,V%d) ", &i, &j) == 2) //scanf函数,如果输入i j两个值成功,返回值为 2代表输入成功有两个,如果不成功为EOF
{
graph.R[i][j] = 1; //vi --> vj
graph.R[j][i] = 1; //vj --> vi 如何将此行代码注销,可用表示有向图
}
// 3.打印图
for (i = 0; i < N; i++)
{
printf("V%d:", graph.V[i]);
for (j = 0; j < N; j++)
{
if (graph.R[i][j] == 1) // == 1说明vi ---> vj
{
printf("V%d ", graph.V[j]); //将Vi 所有能到达的顶点Vj打印
}
}
printf("\n");
}
return 0;
}
4.2. 邻接表(链表)
#include <stdio.h>
#include <stdlib.h>
#define N 5 //5代表5个顶点
typedef struct node_t
{
int vertex; //保存顶点
struct node_t *next; //指向下一个节点的指针
} adjlist_node_t;
//画边函数,也就是将顶点vj插入到对应的链表vi中 draw 画 edge边
void drawEdge(adjlist_node_t *vi, int vj) //vi代表的是对应链表的头结点的指针
{ //在插入的时候对其进行排序同昨天的霍夫曼数插入根据权值排序思想一样
adjlist_node_t *pnew = NULL;
while (vi->next != NULL && vj > vi->next->vertex)
vi = vi->next;
//创建新的节点保存vj
pnew = (adjlist_node_t *)malloc(sizeof(adjlist_node_t));
if (NULL == pnew)
{
perror("pnew malloc failed");
return;
}
//创建pnew后装东西
pnew->vertex = vj;
pnew->next = NULL;
//将节点链接到链表中,先连后面再连前面
pnew->next = vi->next;
vi->next = pnew;
return;
}
int main(int argc, const char *argv[])
{
int i, j;
adjlist_node_t *h = NULL; //用来保存每条链表的头指针(无头)
//1.创建一个结构体数组
adjlist_node_t g[N] = {0};
for (i = 0; i < N; i++)
g[i].vertex = i;
printf("请您输入顶点的关系:\n");
// 2.输入顶点之间的关系
while (scanf("(V%d,V%d) ", &i, &j) == 2)
{
//将vj这个顶点插入到vi顶点的链表中
drawEdge(g + i, j); //此行等价于 &g[i]
// drawEdge(g+j,i);//如果输入的想代表无向图,将此行代码解除注释
}
// 3.将各个顶点所有到达的顶点进行打印
for (i = 0; i < N; i++)
{
printf("V%d:", g[i].vertex); //g[i].vertex 代表的是Vi这个顶点
h = g[i].next;
//相当于遍历无头链表,需要对每一条链表进行遍历
while (h != NULL)
{
printf("V%d ", h->vertex);
h = h->next;
}
printf("\n"); //遍历完一条链表后打印一个换行
}
printf("\n---------------\n");
return 0;
}
int u;
//这段代码实现将v顶点所有能到达顶点全部找一遍
u = firstAdj(g, v); //u此时为第一个邻接点
while (u != -1)
{
u = nextAdj(g, v, u); //每循环一次,就向下再继续找下一个邻接点
//知道u == -1 说明已经没有v能到达的点了
}
5. 遍历
5.1. 深度优先搜索
// 深度搜索 visited 拜访,访问 v 代表的是从哪个顶点开始深度搜索
//visited是一个指向整型数组的指针,用来标记这个顶点是否被访问
void DFS(adjmatrix_t *g, int v, int *visited)
{
// visited[v] == 0代表没被访问, 1代表被访问
int u; //用来保存邻接点
if (visited[v] == 0) //说明该顶点没有被访问
{
printf("V%d ", g->V[v]); //相当于已经被访问了
visited[v] = 1; //将标志位置1,变为已经被访问,已经被打印输出
}
//获取v的第一个邻接点
u = firstAdj(g, v);
while (u != -1) //如果u == -1代表,没有邻接点
{
if (visited[u] == 0) //没有被访问
DFS(g, u, visited);
u = nextAdj(g, v, u); //获取下一个邻接点
}
}
5.2. 广度优先搜索
// 广度搜索
void BFS(adjmatrix_t *g, int v, int *visited)
{
int u;
//创建一个队列
linkqueue_t *p = createEmptyLinkQueue();
inLinkQueue(p, v);
while (!isEmptyLinkQueue(p))
{
v = outLinkQueue(p);
if (visited[v] == 0) //说明v未被访问
{
printf("V%d ", g->V[v]);
visited[v] = 1; //打印之后标记为已经访问
}
//开始遍历v所有能达到的顶点
u = firstAdj(g, v);
while (u != -1)
{
if (visited[u] == 0) //v到达的顶点没有被访问
inLinkQueue(p, u);
u = nextAdj(g, v, u); //在上面入列的u基础上,在找到下一个邻接点返回
}
}
}
#include "graph.h"
#include "linkqueue.h"
#include <stdio.h>
#include <stdlib.h>
#include <strings.h>
// 1.创建一个图
adjmatrix_t *createGraph()
{
int i, j;
adjmatrix_t *g = (adjmatrix_t *)malloc(sizeof(adjmatrix_t));
if (NULL == g)
{
perror("createGraph malloc failed");
return NULL;
}
//2.初始化顶点集合
for (i = 0; i < N; i++)
g->V[i] = i;
//清空下关系结合
bzero(g->R, sizeof(g->R));
printf("请您输入顶点的关系(V0,V1):\n");
while (scanf("(V%d,V%d) ", &i, &j) == 2)
{
g->R[i][j] = 1;
g->R[j][i] = 1;
}
return g;
}
// 2.获取第一个邻接点 v是被搜索的顶点
int firstAdj(adjmatrix_t *g, int v) //v == 0,找v0第一个邻接点
{
int j;
for (j = 0; j < N; j++)
{
if (g->R[v][j] == 1)
return j;
}
return -1; //代表没有邻接点
}
// int u;//保存v的第一个邻接点
// u = fristAdj(g,0);//得到第一个
// u =====> 1
// u = nextAdj(g,0,u);//下一个
// u = nextAdj(g,0,u);//再下一个
// 3.获取下一个邻接点 v被搜索的顶点 u 前一个邻接点
int nextAdj(adjmatrix_t *g, int v, int u)
{
//获取下一个邻接点函数的使用,需要fristAdj函数的配合
int j;
for (j = u + 1; j < N; j++) //j = u+1 是因为第一个邻节点是u,那么继续找下一个从u+1位置开始遍历
{
if (g->R[v][j] == 1)
return j;
}
return -1;
}
// 4.深度搜索 visited 拜访,访问 v代表的是从那个顶点开始深度搜索
//visited是一个指向整型数组的指针,用来标记这个节点是否被访问
void DFS(adjmatrix_t *g, int v, int *visited)
{
//visited[v] == 0代表没被访问, 1代表被访问
int u; //用来保存邻接点
if (visited[v] == 0) //说明该顶点没有被访问
{
printf("V%d ", g->V[v]); //相当于已经被访问了
visited[v] = 1; //将标志位置1,变为已经被访问,已经被打印输出
}
//获取v的第一个邻接点
u = firstAdj(g, v);
while (u != -1) //如果u == -1代表,没有邻接点
{
if (visited[u] == 0) //没有被访问
{
DFS(g, u, visited);
}
u = nextAdj(g, v, u); //获取下一个邻接点
}
}
// 5.广度搜索
void BFS(adjmatrix_t *g, int v, int *visited)
{
int u;
//创建一个队列
linkqueue_t *p = createEmptyLinkQueue();
inLinkQueue(p, v);
while (!isEmptyLinkQueue(p))
{
v = outLinkQueue(p);
if (visited[v] == 0) //说明v未被访问
{
printf("V%d ", g->V[v]);
visited[v] = 1; //打印之后标记为已经访问
}
//开始遍历v所有能达到的顶点
u = firstAdj(g, v);
while (u != -1)
{
if (visited[u] == 0) //v到达的顶点没有被访问
{
inLinkQueue(p, u);
}
u = nextAdj(g, v, u); //在上面入列的u基础上,在找到下一个邻接点返回
}
}
}