邻接矩阵和邻接表

457 阅读7分钟

邻接矩阵与邻接表:

一:邻接矩阵:

图的邻接矩阵(Adjacency Matrix) 存储方式是用两个数组来表示图。一个一维数组存储图中顶点信息,一个二维数组(称为邻接矩阵)存储图中的边或弧的信息,无向图如图所示:

屏幕截图 2023-11-26 134639.png

屏幕截图 2023-11-26 135007.png

二维数组中值为1,则不存在该边,反之则存在,上图中对角线为0是因为不存在到自身的边或者不存在该边,我们能发现无向图的边数组是一个对称矩阵(满足ai j==aj i)。

对于这样的数组:

1.某个顶点的度,就是这个顶点vi在第i行(或者i列)的元素之和,比如v1=1+0+1+0=2.

2.顶点vi的所有邻接点就是将第i行元素扫描一遍,为1则是邻接点。

有向图:

屏幕截图 2023-11-26 140454.png 0. 主对角线上数值依然为0。但因为是有向图,所以此矩阵并不对称。 0. 有向图讲究入度与出度,顶点v1的入度为1,正好是第v1列各数之和。顶点v1的出度为2,即第v1行的各数之和。 0. 与无向图同样的办法,判断顶点vi 到vj是否存在弧,只需要查找矩阵中A [ i ] [ j ]是否为1即可。

网:每条边上带权的图叫网,那我们怎么样把这些权值储存下来呢:

屏幕截图 2023-11-26 141108.png

屏幕截图 2023-11-26 141125.png

如图所示:

wi j表示权值,无穷表示一个计算机允许的,大于所有边的权值的值。

那么我们可以创建一个由邻接矩阵创建的图:

下面是一个创建无向网图的示范:

#include <stdio.h>
#define INFINITY 65535
typedef int my_int;
typedef char my_char;
struct graph {
    int numsnodes;
    int numsedges;
    my_char vex[100];
    my_int arc[100][100];
​
};
void creategraph(struct graph*G) {
    printf("请输入顶点数和边数\n");
    scanf("%d %d", &G->numsnodes, &G->numsnodes);
    for (int i = 0; i < G->numsnodes; i++) {//读入顶点信息
        scanf("%d", &G->vex[i]);
    }
    for (int i = 0; i < G->numsnodes; i++) {//初始化为极限值
        for (int j = 0; j < G->numsnodes; j++) {
            G->arc[i][j] = INFINITY;
        }
    }
    for (int k = 0; k < G->numsedges; k++) {
        int i, j, w;
        printf("输入边的下标i,j以及权w\n");
        scanf("%d %d %d", &i, &j, &w);
        G->arc[i][j] = w;
        G->arc[j][i] = w;//无向图对称
    }
}
int main() {
    struct graph G;
    creategraph(&G);
    return 0;
}

如果想创建一个有向网图,只需要一点小小改动就行:

#include <stdio.h>
#define INFINITY 65535
typedef int my_int;
typedef char my_char;
struct graph {
    int numsnodes;
    int numsedges;
    my_char vex[100];
    my_int arc[100][100];
};
void creategraph(struct graph*G) {
    printf("请输入顶点数和边数\n");
    scanf("%d %d", &G->numsnodes, &G->numsnodes);
    for (int i = 0; i < G->numsnodes; i++) {//读入顶点信息
        scanf("%d", &G->vex[i]);
    }
    for (int i = 0; i < G->numsnodes; i++) {//初始化为极限值
        for (int j = 0; j < G->numsnodes; j++) {
            G->arc[i][j] = INFINITY;
        }
    }
    for (int k = 0; k < G->numsedges; k++) {
        int i, j, w;
        printf("输入边的下标i,j以及权w\n");
        scanf("%d %d %d", &i, &j, &w);
        G->arc[i][j] = w;
        // 在有向图中,我们不再需要这行代码
        // G->arc[j][i] = w;
    }
}
int main() {
    struct graph G;
    creategraph(&G);
    return 0;
}
在有向图中,边的方向是重要的,所以我们只在一侧设置权重。

二:邻接表:

数组和链表相结合的储存方式叫邻接表。

屏幕截图 2023-11-26 145022.png

屏幕截图 2023-11-26 145042.png

如图所示,以上分别是无向图和有向图的邻接表表示。

邻接表中,储存了每个顶点的指向边,如上图的1号顶点就指向了2和4两个点。这种邻接表适合经常需要查询它指向谁这种情况,那有没有一种邻接表可以反向操作,记录谁指向了它呢,那就是:

逆邻接表:

及对于上图,4号顶点后面不是2,而是1和5,道理与邻接表类似。

下面来看看邻接表代码实现:

方法一:

#include<stdio.h>
#include<stdlib.h>
typedef struct node {
    int vertex;
    struct node* next;
} node;
node* create_node(int v) {
    node* new_node = malloc(sizeof(node));
    new_node->vertex = v;
    new_node->next = NULL;
    return new_node;
}
typedef struct Graph {
    int num_of_vertices;
    node** adj_lists;
} Graph;
Graph* create_graph(int vertices) {
    Graph* graph = malloc(sizeof(Graph));
    graph->num_of_vertices = vertices;
    graph->adj_lists = malloc(vertices * sizeof(node*));
    for (int i = 0; i < vertices; i++)
        graph->adj_lists[i] = NULL;
    return graph;
}
void add_edge(Graph* graph, int src, int dest) {
    node* new_node = create_node(dest);
    new_node->next = graph->adj_lists[src];
    graph->adj_lists[src] = new_node;
}
void print_graph(Graph* graph) {
    for (int v = 0; v < graph->num_of_vertices; v++) {
        node* temp = graph->adj_lists[v];
        printf("\n Vertex %d\n: ", v);
        while (temp) {
            printf("%d -> ", temp->vertex);
            temp = temp->next;
        }
        printf("\n");
    }
}
int main() {
    Graph* graph = create_graph(5);
    add_edge(graph, 0, 1);
    add_edge(graph, 0, 4);
    add_edge(graph, 1, 2);
    add_edge(graph, 1, 3);
    add_edge(graph, 1, 4);
    add_edge(graph, 2, 3);
    add_edge(graph, 3, 4);
print_graph(graph);
return 0;
}

这个代码首先定义了一个节点和图的结构,然后通过add_edge函数添加边。在main函数中,我们创建一个图,并添加一些边,最后打印出邻接表。这个邻接表表示了一个有向图。如果想表示无向图,你可以在add_edge函数中添加一行add_edge(graph, dest, src);

下面是每个部分的详细解释:

  1. typedef struct node:定义了一个名为node的结构体,用于表示图中的一个顶点。每个node包含一个vertex(顶点的值)和一个指向下一个node的指针next
  2. create_node(int v):这是一个函数,用于创建一个新的node。它接受一个整数v作为参数,然后创建一个新的node,其中vertex的值为v,并返回这个新创建的node
  3. typedef struct Graph:定义了一个名为Graph的结构体,用于表示整个图。Graph包含一个num_of_vertices(顶点的数量)和一个指向node指针数组的指针adj_lists
  4. create_graph(int vertices):这是一个函数,用于创建一个新的Graph。它接受一个整数vertices作为参数,然后创建一个新的Graph,其中num_of_vertices的值为vertices,并返回这个新创建的Graph
  5. add_edge(Graph* graph, int src, int dest):这是一个函数,用于在图中添加一条边。它接受一个Graph指针和两个整数srcdest作为参数,然后在srcdest之间添加一条边。
  6. print_graph(Graph* graph):这是一个函数,用于打印图的所有顶点和它们的邻居。

是不是很麻烦,当然,确实麻烦,所以我们还可以用以下两种方式更简单的创建一个邻接表:

方法二:

#include <iostream>
#include <vector>
using namespace std;
struct Edge {
    int t;
    int next;
};
struct Edge edge[10000];
int idx = 0;
int head[10000];
void add(int a,int b) {
    idx++;
    edge[idx].t = b;
    edge[idx].next = head[a];
    head[a] = idx;
}
int main() {
    int n, m, x, y;
    cin >> n >> m;
    while (m--) {
        cin >> x >> y;
        add(x, y);
        add(y, x);
    }
    //打印k号节点的所以临边
    int k;
    cin >> k;
    for (int i = head[k]; edge[i].t!=0;i=edge[i].next) {
        cout << edge[i].t << " ";
    }
    return 0;
}
这个代码最关键的是对head数组的理解,head数组是储存一条边以其起点开头的连接的所有节点中最后一次更新的一条,举例,如果有1->2,1->3,那么head[1]储存的就是edge数组中最后一个被添加的,以一为起点的,即终点为3的在edge数组中的下标,而加入1->3这条边时,会将原来edge数组再开辟一个空间存储一个终点3,并且将该空间的另一个元素next存储原来以1为起点的最后加进去的终点的下标,事实上就是一个链式存储,一开始,以一为起点的点,邻接表中什么也没有,然后加进去个2,再加一个3,正如上面邻接表的图的1->2->4。所以遍历时,倒序遍历就行了。、

这个是短了一点,不过好像有点绕啊,有没有什么更好理解的呢,当然,请看方法三:

方法三:

vector<int>ad_list[10000];
没了,真的没了。。。。是不是很简单,so,我们为什么不直接用vector容器创建一个邻接表呢:
#include <iostream>
#include <vector>
using namespace std;
int main() {
    vector<int>list[1000];
    int n, m;
    cin >> n >> m;
    while (m--) {
        int x, y;
        cin >> x >> y;
        list[x].push_back(y);
        list[y].push_back(x);
    }
    int k;
    cin >> k;
    for (int i = 0; i < list[k].size(); i++) {
        cout << list[k][i] << " ";
    }
    return 0;
}
简单易懂,所以就不解释了。