基础概念:
邻接矩阵是一种用二维数组表示图的数据结构,其中矩阵中的每个元素表示两个节点之间是否存在边。对于无向图而言,邻接矩阵是对称的;对于有向图而言,则不一定对称。
在C语言中,可以使用二维数组来实现邻接矩阵,例如:
#define MAX_NODES 100
int graph[MAX_NODES][MAX_NODES];
其中,graph[i][j]
表示从节点 i
到节点 j
是否存在一条边。如果存在,则该值为1,否则为0。
邻接表是由顶点和与之相连的边组成的链表,在链表中存储了每个节点的邻居节点信息。对于稀疏图而言,邻接表比邻接矩阵更加节省空间,但查询某个节点的邻居节点需要遍历整个链表。
在C语言中,可以使用结构体和指针来实现邻接表,例如:
#define MAX_NODES 100
struct AdjNode {
int dest;
struct AdjNode* next;
};
typedef struct AdjNode AdjNode;
struct Graph {
AdjNode* adjLists[MAX_NODES];
};
typedef struct Graph Graph;
其中,Graph
结构体包含了一个数组 adjLists
,每个元素都是一个指向 AdjNode
结构体的指针。在 AdjNode
结构体中,dest
表示目标节点的编号,next
则是指向下一个邻居节点的指针。
关联矩阵是一种用二维数组表示图的数据结构,其中矩阵中的每个元素表示一个顶点和一条边之间的关系。在关联矩阵中,行代表顶点,列代表边,如果该顶点与该边相连,则值为1,否则为0。
在C语言中,可以使用二维数组来实现关联矩阵,例如:
#define MAX_NODES 100
#define MAX_EDGES 200
int matrix[MAX_NODES][MAX_EDGES];
其中,matrix[i][j]
表示顶点 i
是否与边 j
相连。如果相连,则该值为1;否则为0。
十字链表是在邻接表的基础上进一步扩展的数据结构,它可以表示有向图的结构,同时避免了邻接表中查询源点和目标点的缺点。在十字链表中,每个节点都有两个域,分别指向以该节点为起点的出边和以该节点为终点的入边。
在C语言中,可以使用结构体和指针来实现十字链表,例如:
#define MAX_NODES 100
struct Edge {
int from;
int to;
struct Edge* nextOut;
struct Edge* nextIn;
};
typedef struct Edge Edge;
struct Graph {
Edge* edges[MAX_NODES];
};
typedef struct Graph Graph;
其中,Graph
结构体包含了一个数组 edges
,每个元素都是一个指向 Edge
结构体的指针。在 Edge
结构体中,from
表示边的起点,to
表示边的终点,nextOut
则是指向下一条以该节点为起点的出边的指针,nextIn
则是指向下一条以该节点为终点的入边的指针。
前向星也是在邻接表的基础上进行扩展,它可以同时支持有向图和无向图,同时也解决了在邻接表中查询某个节点的所有出边或入边的效率问题。在前向星中,每个节点不再只存储一个指向第一个邻居节点的指针,而是存储一组指向所有邻居节点的指针,这些指针按照一定的顺序排列成一个链表。
在C语言中,可以使用结构体和数组来实现前向星,例如:
#define MAX_NODES 100
#define MAX_EDGES 200
struct Edge {
int to;
int weight; // 可选
int next; // 指向下一条出边或入边
};
typedef struct Edge Edge;
struct Graph {
Edge edges[MAX_EDGES];
int head[MAX_NODES]; // 存储每个节点的第一条边的编号
int edgeCount;
};
typedef struct Graph Graph;
其中,Graph
结构体包含了两个数组,edges
数组存储所有边的信息,head
数组存储每个节点的第一条边的编号,edgeCount
表示边的数量。在 Edge
结构体中,to
表示边的终点,weight
表示边的权值(可选),next
表示与该边起点相同的下一条边的编号。
邻接多重表是一种用于表示无向图的数据结构,它将每条边都存储为两个顶点之间的一条边,并且对于每个节点存储所有与该节点相连的边的信息。在邻接多重表中,每个节点都有一个指向第一条与该节点相连的边的指针,而每条边则包含了指向其起点和终点下一条边的指针。
在C语言中,可以使用结构体和指针来实现邻接多重表,例如:
#define MAX_NODES 100
#define MAX_EDGES 200
struct Edge {
int from;
int to;
int nextFrom; // 指向起点相同的下一条边
int nextTo; // 指向终点相同的下一条边
};
typedef struct Edge Edge;
struct Graph {
Edge edges[MAX_EDGES];
int head[MAX_NODES]; // 存储每个节点的第一条边的编号
int edgeCount;
};
typedef struct Graph Graph;
其中,Graph
结构体包含了两个数组,edges
数组存储所有边的信息,head
数组存储每个节点的第一条边的编号,edgeCount
表示边的数量。在 Edge
结构体中,from
和 to
分别表示边的起点和终点,nextFrom
和 nextTo
则分别指向与该边起点相同和终点相同的下一条边的编号。
在考研范围内,需要掌握以下图的基本操作:
// 创建邻接矩阵表示的无向图
void CreateGraph(Graph *G) {
int i, j, k, weight;
printf("请输入图的顶点数和边数(用空格隔开): ");
scanf("%d %d", &G->vertex_num, &G->edge_num);
printf("请输入%d个顶点的信息: ", G->vertex_num);
for (i = 0; i < G->vertex_num; ++i)
scanf("%s", &G->vertex[i]);
for (i = 0; i < G->vertex_num; ++i)
for (j = 0; j < G->vertex_num; ++j)
G->edge[i][j] = 0; // 初始化邻接矩阵
printf("请输入%d条边的信息(起点 终点 权值):\n", G->edge_num);
for (k = 0; k < G->edge_num; ++k) {
scanf("%d %d %d", &i, &j, &weight);
// 因为是无向图,所以需要对称赋值
G->edge[i][j] = weight;
G->edge[j][i] = weight;
//如果这里是创建有向图,那么就需要我们应该会改
}
}
// 在图G中插入顶点x
void InsertVertex(Graph *G, char x){
if(G->vertex_num == MAX_VERTEX_NUM){ // 图已满
printf("Error: 超出最大结点范围\n");
return;
}
G->vertex[G->vertex_num] = x;
G->vertex_num++;
//更新边信息
for(int i=0;i<G->vertex_num-1;++i){
G->edge[i][G->vertex_num-1] = 0; //初始化新顶点的出边
G->edge[G->vertex_num-1][i] = 0; //初始化新顶点的入边
}
}
// 将数组中从 start 开始的元素向前移动一位
void moveArray(char arr[], int start, int end) {
for (int i = start; i < end; ++i) {
arr[i] = arr[i+1];
}
}
// 在图G中删除顶点x
void DeleteVertex(Graph *G,char x){
int n = -1; //待删除结点在数组里的下标
for(int i=0;i<G->vertex_num;i++){
if(G->vertex[i]==x) {
n=i;
break;
}
}
if(n == -1){ // 如果节点不存在
printf("Error: 结点不存在\n");
return;
}
// 删除该节点对应的行和列
for(int i=n;i<G->vertex_num-1;++i){
moveArray(G->vertex, i, G->vertex_num - 1); // 将后面的节点前移
for(int j=0;j<G->vertex_num;++j){
G->edge[i][j] = G->edge[i+1][j]; // 前移一位
G->edge[j][i] = G->edge[j][i+1];
}
}
G->vertex_num--;
// 更新最后一行和最后一列
for(int i=0;i<G->vertex_num;++i){
G->edge[G->vertex_num][i] = 0; // 删除最后一列
G->edge[i][G->vertex_num] = 0; // 删除最后一行
}
}
//求图G中顶点x的第一个邻接点
char FirstNeighbor(Graph *G,char x){
int n = -1; //待查询结点在数组里的下标
for(int i=0;i<G->vertex_num;i++){
if(G->vertex[i]==x) {
n=i;
break;
}
}
if(n == -1){ // 如果查询节点不存在
printf("Error: 结点不存在\n");
}
int count=0;
int nx; //第一个邻接点在数组里的下标
//现在知道了他在第n个,所以我们查询边矩阵第n行中第一个1在哪就知道第一个邻接点是谁
for(int j=0;j<G->vertex_num;j++){
if(G->edge[n][j]==1) count++;
if(count==1){
nx=j;
break;
}
}
return G->vertex[nx];
}
//返回图G中顶点x除顶点y的下一个邻接点
char NextNeighbor(Graph *G,char x,char y){
int n = -1; //待查询结点在数组里的下标
for(int i=0;i<G->vertex_num;i++){
if(G->vertex[i]==x) {
n=i;
break;
}
}
if(n == -1){ // 如果查询节点不存在
printf("Error: 结点不存在\n");
}
int count=0;
int nx; //存放返回邻接点数组下标
for(int j=0;j<G->vertex_num;j++){
if(G->edge[n][j]==1){
if(G->vertex[j]!=y){
j=nx;
}
}
}
return G->vertex[nx];
}
//判断图G中x和y之间是否有边
int Adjacent(Graph *G,char x,char y){
int n1,n2; //分别存储x和y的下标
for(int i=0;ivertex_num;i++){
if(G->vertex[i]==x) {
n1=i;
break;
}
}
for(int i=0;ivertex_num;i++){
if(G->vertex[i]==x) {
n2=i;
break;
}
}
if(G->edge[n1][n2]==0 && G->edge[n2][n1]==0)
return -1;
else
return 1;
}
以上部分均选取第三部分比较难 的算法进行实现
#include
#include
#include
#define MAX_VERTEX_NUM 20 // 最大顶点数
typedef struct {
char vertex[MAX_VERTEX_NUM]; // 存放顶点信息
int edge[MAX_VERTEX_NUM][MAX_VERTEX_NUM]; // 存放边信息
int vertex_num; // 顶点数
int edge_num; // 边数
} Graph;
// 创建邻接矩阵表示的无向图
void CreateGraph(Graph *G) {
int i, j, k, weight;
printf("请输入图的顶点数和边数(用空格隔开): ");
scanf("%d %d", &G->vertex_num, &G->edge_num);
printf("请输入%d个顶点的信息: ", G->vertex_num);
for (i = 0; i < G->vertex_num; ++i)
scanf("%s", &G->vertex[i]);
for (i = 0; i < G->vertex_num; ++i)
for (j = 0; j < G->vertex_num; ++j)
G->edge[i][j] = 0; // 初始化邻接矩阵
printf("请输入%d条边的信息(起点 终点 权值):\n", G->edge_num);
for (k = 0; k < G->edge_num; ++k) {
scanf("%d %d %d", &i, &j, &weight);
// 因为是无向图,所以需要对称赋值
G->edge[i][j] = weight;
G->edge[j][i] = weight;
//如果这里是创建有向图,那么就需要我们应该会改
}
}
// 在图G中插入顶点x
void InsertVertex(Graph *G, char x){
if(G->vertex_num == MAX_VERTEX_NUM){ // 图已满
printf("Error: 超出最大结点范围\n");
return;
}
G->vertex[G->vertex_num] = x;
G->vertex_num++;
//更新边信息
for(int i=0;i<G->vertex_num-1;++i){
G->edge[i][G->vertex_num-1] = 0; //初始化新顶点的出边
G->edge[G->vertex_num-1][i] = 0; //初始化新顶点的入边
}
}
// 将数组中从 start 开始的元素向前移动一位
void moveArray(char arr[], int start, int end) {
for (int i = start; i < end; ++i) {
arr[i] = arr[i+1];
}
}
// 在图G中删除顶点x
void DeleteVertex(Graph *G,char x){
int n = -1; //待删除结点在数组里的下标
for(int i=0;i<G->vertex_num;i++){
if(G->vertex[i]==x) {
n=i;
break;
}
}
if(n == -1){ // 如果节点不存在
printf("Error: 结点不存在\n");
return;
}
// 删除该节点对应的行和列
for(int i=n;i<G->vertex_num-1;++i){
moveArray(G->vertex, i, G->vertex_num - 1); // 将后面的节点前移
for(int j=0;j<G->vertex_num;++j){
G->edge[i][j] = G->edge[i+1][j]; // 前移一位
G->edge[j][i] = G->edge[j][i+1];
}
}
G->vertex_num--;
// 更新最后一行和最后一列
for(int i=0;i<G->vertex_num;++i){
G->edge[G->vertex_num][i] = 0; // 删除最后一列
G->edge[i][G->vertex_num] = 0; // 删除最后一行
}
}
//求图G中顶点x的第一个邻接点
char FirstNeighbor(Graph *G,char x){
int n = -1; //待查询结点在数组里的下标
for(int i=0;i<G->vertex_num;i++){
if(G->vertex[i]==x) {
n=i;
break;
}
}
if(n == -1){ // 如果查询节点不存在
printf("Error: 结点不存在\n");
}
int count=0;
int nx; //第一个邻接点在数组里的下标
//现在知道了他在第n个,所以我们查询边矩阵第n行中第一个1在哪就知道第一个邻接点是谁
for(int j=0;j<G->vertex_num;j++){
if(G->edge[n][j]==1) count++;
if(count==1){
nx=j;
break;
}
}
return G->vertex[nx];
}
//返回图G中顶点x除顶点y的下一个邻接点
char NextNeighbor(Graph *G,char x,char y){
int n = -1; //待查询结点在数组里的下标
for(int i=0;i<G->vertex_num;i++){
if(G->vertex[i]==x) {
n=i;
break;
}
}
if(n == -1){ // 如果查询节点不存在
printf("Error: 结点不存在\n");
}
int count=0;
int nx; //存放返回邻接点数组下标
for(int j=0;j<G->vertex_num;j++){
if(G->edge[n][j]==1){
if(G->vertex[j]!=y){
j=nx;
}
}
}
return G->vertex[nx];
}
//判断图G中x和y之间是否有边
int Adjacent(Graph *G,char x,char y){
int n1,n2; //分别存储x和y的下标
for(int i=0;i<G->vertex_num;i++){
if(G->vertex[i]==x) {
n1=i;
break;
}
}
for(int i=0;i<G->vertex_num;i++){
if(G->vertex[i]==x) {
n2=i;
break;
}
}
if(G->edge[n1][n2]==0 && G->edge[n2][n1]==0)
return -1;
else
return 1;
}
int main() {
Graph G;
CreateGraph(&G);
return 0;
}
刚好最近复习数据结构与算法,顺便参加了英杰的新星计划,这是本周第一篇投稿。