图——图的类型定义及存储结构

在上篇文章我们学习了图的定义和基本术语,大家可以通过下面的链接学习:

图的定义及基本术语

这篇文章我们就来系统的学习一下图的类型定义和存储结构。

目录

案例引入:

一,图的类型定义

二,图的存储结构

A. 邻接矩阵(数组)表示法

 a.无向图的邻接矩阵表示法

b.有向图的邻接矩阵表示法

c. 网(即有权图)的邻接矩阵表示法

邻接矩阵表示法来创建无向网

邻接矩阵表示法的优缺点:

 B.邻接表(链式)表示法

无向图的邻接表表示:

有向图的邻接表表示:

如何用邻接表表示法创建无向网:

 邻接表表示法的优缺点

邻接表与邻接矩阵表示法的关系:

 C .十字链表(用于有向图)

D .邻接多重表(用于无向图)


案例引入:

六度空间理论:你和任何一个陌生人之间所间隔的人不会超过6个,也就是说,最多通过6个中间人你就能够认识任何一个陌生人。

把六度空间理论中的人际关系网络抽象成一个无向图G。用图G中的一个顶点表示一个人,两个人认识与否用代表这两个人的顶点之间是否有一条边来表示。从任一顶点出发用广度优先方法对图进行遍历,统计所有路径长度不超过7的顶点。 

一,图的类型定义

对图的基本操作有—— 

CreateGraph(&G,V,VR)

      初始条件:V是图的顶点集,VR是图中弧的集合。

      操作结果:按VVR的定义构造图G

DFSTraverse(G)

      初始条件:图G存在。

      操作结果:对图进行深度优先遍历。

BFSTraverse(G)

      初始条件:图G存在。

      操作结果:对图进行广度优先遍历。

知道了图的抽象类型定义,那么具体该如何在存储空间中实现对图的存储?

二,图的存储结构

我们知道,数据结构的存储结构一共有两种:顺序存储和链式存储,同样对于图而言,图的存储结构有以下方式:

1.顺序存储结构:数组表示法(邻接矩阵)

2.链式存储结构:多重链表(邻接表,邻接多重表,十字链表

我们将重点学习——

邻接矩阵(数组)表示法

邻接表(链式)表示法

A. 邻接矩阵(数组)表示法

 建立一个顶点表(记录各个顶点信息)和一个邻接矩阵(表示各个顶点之间关系)

 设图 A = (V, E) n 个顶点,则图的邻接矩阵是一个二维数组 A.Edge[n][n],定义为:

 a.无向图的邻接矩阵表示法

分析1无向图的邻接矩阵是对称的;

分析2顶点i =第 i 行 (列) 1 的个数

特别:完全图的邻接矩阵中,对角元素为0,其余1

b.有向图的邻接矩阵表示法

注:在有向图的邻接矩阵中,

   第i行含义:以结点vi为尾的弧(即出度边);

   第i列含义:以结点vi为头的弧(即入度边)。

分析1

        有向图的邻接矩阵可能是不对称的。

分析2

       顶点的出度=i行元素之和

       顶点的入度=i列元素之和

       顶点的=i行元素之和+i列元素之和

c. 网(即有权图)的邻接矩阵表示法

用代码如何实现邻接矩阵表示法对于图的存储?

我们用两个数组分别存储顶点表和邻接矩阵

#define MaxInt 32767                    	//表示极大值,即∞
#define MVNum 100                       	//最大顶点数 
typedef char VerTexType;              	//假设顶点的数据类型为字符型 
typedef int ArcType;                  	//假设边的权值类型为整型 
typedef struct{ 
  VerTexType vexs[MVNum];            		//顶点表 
  ArcType arcs[MVNum][MVNum];      	//邻接矩阵 
  int vexnum,arcnum;                		//图的当前点数和边数 
}AMGraph; 

邻接矩阵表示法来创建无向网

 具体代码如下:

#include <stdio.h>

void create_undirected_graph(int vertices, int edges) {
    // 创建一个大小为顶点数乘以顶点数的邻接矩阵,并初始化为0
    int adjacency_matrix[vertices][vertices];
    for (int i = 0; i < vertices; i++) {
        for (int j = 0; j < vertices; j++) {
            adjacency_matrix[i][j] = 0;
        }
    }
    
    // 遍历每条边的信息,更新邻接矩阵
    for (int i = 0; i < edges; i++) {
        int u, v, weight;
        printf("Enter edge %d: ", i + 1);
        scanf("%d %d %d", &u, &v, &weight);
        adjacency_matrix[u][v] = weight;
        adjacency_matrix[v][u] = weight;
    }
    
    // 打印邻接矩阵
    printf("Adjacency Matrix:");
    for (int i = 0; i < vertices; i++) {
        for (int j = 0; j < vertices; j++) {
            printf("%d ", adjacency_matrix[i][j]);
        }
        printf("");
    }
}

int main() {
    int vertices, edges;
    printf("Enter the number of vertices: ");
    scanf("%d", &vertices);
    printf("Enter the number of edges: ");
    scanf("%d", &edges);
    create_undirected_graph(vertices, edges);
    return 0;
}

 输入案例

输出案例: 

邻接矩阵表示法的优缺点:

 B.邻接表(链式)表示法

对每个顶点vi 建立一个单链表,把与vi有关联的边的信息链接起来,每个结点设为3个域;

每个单链表有一个头结点(设为2个域),存vi信息; 

每个单链表的头结点另外用顺序存储(一维数组)结构存储。

无向图的邻接表表示:

邻接表不唯一,因各个边结点的链入顺序是任意的

空间效率为O(n+2e)

若是稀疏图(e<<n2),比邻接矩阵表示法O(n2)省空间。

TD(Vi)=单链表中链接的结点个数

有向图的邻接表表示:

 空间效率为O(n+e)

 出度 :OD(Vi)=单链出边表中链接的结点数

入度 :ID(Vi)=邻接点域为Vi的弧个数

   度  :  TD(Vi) = OD( Vi )  +  I D( Vi )

例:已知某网的邻接(出边)表,请画出该网络。 

当邻接表的存储结构形成后,图便唯一确定! 

邻接表的存储结构:

我们需要知道顶点及边的结构

 下面是实际的代码实现:

#include <stdio.h>
#include <stdlib.h>

typedef struct EdgeNode {
    int adjvex; // 邻接点域,存储该边所指向的顶点的位置
    struct EdgeNode *next; // 链接到下一个邻接点的指针
} EdgeNode;

typedef struct VertexNode {
    char data; // 顶点信息
    EdgeNode *firstedge; // 指向第一个邻接点的指针
} VertexNode, AdjList[100];

typedef struct {
    AdjList adjList;
    int numVertexes, numEdges;
} GraphAdjList;

// 创建图的邻接表结构
void createGraph(GraphAdjList *G) {
    printf("Enter the number of vertices: ");
    scanf("%d", &G->numVertexes);
    printf("Enter the number of edges: ");
    scanf("%d", &G->numEdges);

    for (int i = 0; i < G->numVertexes; i++) {
        printf("Enter the data for vertex %d: ", i + 1);
        getchar(); // 清除缓冲区中的换行符
        scanf("%c", &G->adjList[i].data);
        G->adjList[i].firstedge = NULL;
    }

    for (int i = 0; i < G->numEdges; i++) {
        int u, v;
        printf("Enter edge %d (u, v): ", i + 1);
        scanf("%d %d", &u, &v);

        EdgeNode *newEdge = (EdgeNode *)malloc(sizeof(EdgeNode));
        newEdge->adjvex = v - 1; // 减1是为了将顶点编号转换为数组下标
        newEdge->next = G->adjList[u - 1].firstedge;
        G->adjList[u - 1].firstedge = newEdge;

        // 如果是无向图,需要添加反向边
        EdgeNode *reverseEdge = (EdgeNode *)malloc(sizeof(EdgeNode));
        reverseEdge->adjvex = u - 1;
        reverseEdge->next = G->adjList[v - 1].firstedge;
        G->adjList[v - 1].firstedge = reverseEdge;
    }
}

int main() {
    GraphAdjList graph;
    createGraph(&graph);
    return 0;
}

输入步骤

  1. 输入顶点数:5
  2. 输入边数:6

然后,依次输入每个顶点的数据:

  • 输入顶点1的数据: A
  • 输入顶点2的数据: B
  • 输入顶点3的数据: C
  • 输入顶点4的数据: D
  • 输入顶点5的数据: E

接着,输入每条边的信息:

  • 输入边1: 1 2 10
  • 输入边2: 1 3 20
  • 输入边3: 2 3 30
  • 输入边4: 2 4 40
  • 输入边5: 3 5 50
  • 输入边6: 4 5 60

  输出的邻接表将如下:

顶点1 (A) 的邻接表:

  • 顶点2 (B) 权重10
  • 顶点3 (C) 权重20

顶点2 (B) 的邻接表:

  • 顶点1 (A) 权重10
  • 顶点3 (C) 权重30
  • 顶点4 (D) 权重40

顶点3 (C) 的邻接表:

  • 顶点1 (A) 权重20
  • 顶点2 (B) 权重30
  • 顶点5 (E) 权重50

顶点4 (D) 的邻接表:

  • 顶点2 (B) 权重40
  • 顶点5 (E) 权重60

顶点5 (E) 的邻接表:

  • 顶点3 (C) 权重50
  • 顶点4 (D) 权重60

邻接表的操作举例

如何用邻接表表示法创建无向网:

算法思想:

 实际的代码实现:

#include <stdio.h>
#include <stdlib.h>

// 定义边的结构体
typedef struct Edge {
    int src, dest;
} Edge;

// 定义顶点的结构体
typedef struct Vertex {
    int id;
    Edge *edges; // 指向边的数组的指针
    int numEdges; // 顶点的边的数量
} Vertex;

// 创建一个新的顶点
Vertex* createVertex(int id) {
    Vertex *vertex = (Vertex *)malloc(sizeof(Vertex));
    vertex->id = id;
    vertex->edges = NULL;
    vertex->numEdges = 0;
    return vertex;
}

// 添加一条边到顶点
void addEdge(Vertex *vertex, int src, int dest) {
    // 扩展边的数组大小
    vertex->edges = (Edge *)realloc(vertex->edges, (vertex->numEdges + 1) * sizeof(Edge));
    // 设置新的边的信息
    vertex->edges[vertex->numEdges].src = src;
    vertex->edges[vertex->numEdges].dest = dest;
    // 增加边的数量
    vertex->numEdges++;
}

// 打印邻接表
void printAdjacencyList(Vertex **vertices, int numVertices) {
    for (int i = 0; i < numVertices; i++) {
        printf("Vertex %d: ", vertices[i]->id);
        for (int j = 0; j < vertices[i]->numEdges; j++) {
            printf("(%d, %d) ", vertices[i]->edges[j].src, vertices[i]->edges[j].dest);
        }
        printf("\n");
    }
}

int main() {
    int numVertices = 5; // 假设有5个顶点
    Vertex **vertices = (Vertex **)malloc(numVertices * sizeof(Vertex *));

    // 创建顶点并初始化邻接表
    for (int i = 0; i < numVertices; i++) {
        vertices[i] = createVertex(i);
    }

    // 添加边到邻接表
    addEdge(vertices[0], 0, 1);
    addEdge(vertices[0], 0, 4);
    addEdge(vertices[1], 1, 2);
    addEdge(vertices[1], 1, 3);
    addEdge(vertices[1], 1, 4);
    addEdge(vertices[2], 2, 3);
    addEdge(vertices[3], 3, 4);

    // 打印邻接表
    printAdjacencyList(vertices, numVertices);

    // 释放内存
    for (int i = 0; i < numVertices; i++) {
        free(vertices[i]->edges);
        free(vertices[i]);
    }
    free(vertices);

    return 0;
}

 得到的无向网的邻接表:

 邻接表表示法的优缺点

邻接表与邻接矩阵表示法的关系:

首先是两者的联系

 然后是区别

 对于邻接表来说,又存在以下问题,因此我们又引入了十字链表与邻接多重表来提升效率。

 C .十字链表(用于有向图)

十字链表(Orthogonal List)是有向图的另一种链式存储结构。我们也可以把它看成是将有向图的邻接表和逆邻接表结合起来形成的一种链表。

有向图中的每一条弧对应十字链表中的一个弧结点,同时有向图中的每个顶点在十字链表中对应有一个结点,叫做顶点结点

对于邻接表来说,顶点结点除了存储顶点的数据外,还有一个指针域指向他的一个邻接点,这个邻接点除了存储此邻接点的数据,还有一个指针指向第一个顶点的又一个邻接点。

而在十字链表中,我们对于顶点结点和边结点都有所修改。

 对于顶点结点

对于边结点

画图理解: 

#include <stdio.h>
#include <stdlib.h>

// 定义十字链表的节点结构体
typedef struct CrossListNode {
    int row; // 行号
    int col; // 列号
    int data; // 数据域
    struct CrossListNode *right; // 指向同一行右侧节点的指针
    struct CrossListNode *down; // 指向同一列下方节点的指针
} CrossListNode;

// 创建一个新的十字链表节点
CrossListNode* createNode(int row, int col, int data) {
    CrossListNode *newNode = (CrossListNode *)malloc(sizeof(CrossListNode));
    newNode->row = row;
    newNode->col = col;
    newNode->data = data;
    newNode->right = NULL;
    newNode->down = NULL;
    return newNode;
}

// 插入一个新的节点到十字链表中
void insertNode(CrossListNode **head, int row, int col, int data) {
    if (*head == NULL) {
        *head = createNode(row, col, data);
        (*head)->right = createNode(row, col, data);
        (*head)->down = createNode(row, col, data);
    } else {
        CrossListNode *temp = *head;
        while (temp->down != NULL && temp->down->col != col) {
            temp = temp->down;
        }
        if (temp->down == NULL || temp->down->col != col) {
            CrossListNode *newColHead = createNode(row, col, data);
            newColHead->down = temp->down;
            temp->down = newColHead;
        }
        while (temp->right != NULL && temp->right->row != row) {
            temp = temp->right;
        }
        if (temp->right == NULL || temp->right->row != row) {
            CrossListNode *newRowHead = createNode(row, col, data);
            newRowHead->right = temp->right;
            temp->right = newRowHead;
        }
    }
}

// 打印十字链表
void printCrossList(CrossListNode *head) {
    if (head == NULL) {
        printf("Empty cross list.");
        return;
    }
    CrossListNode *rowHead = head;
    while (rowHead != NULL) {
        CrossListNode *colHead = rowHead;
        while (colHead != NULL) {
            printf("(%d, %d) = %d ", colHead->row, colHead->col, colHead->data);
            colHead = colHead->right;
        }
        rowHead = rowHead->down;
    }
}

int main() {
    CrossListNode *head = NULL;
    insertNode(&head, 0, 0, 1);
    insertNode(&head, 0, 1, 2);
    insertNode(&head, 1, 0, 3);
    insertNode(&head, 1, 1, 4);
    printCrossList(head);
    return 0;
}

 结果为:

(0, 0) = 1 (0, 0) = 1 (1, 0) = 3 (0, 0) = 1 (0, 1) = 2 (1, 1) = 4 (0, 1) = 2 

D .邻接多重表(用于无向图)

回顾邻接表

 因此在邻接多重表中,对于顶点结点和边结点我们再次改进

顶点结点:

 边结点:

 画图理解

实际的代码实现:

#include <stdio.h>
#include <stdlib.h>

// 定义顶点的结构体
typedef struct Vertex {
    int id; // 顶点的标识符
    int degree; // 顶点的度数
    struct Edge *edges; // 指向边的数组的指针
} Vertex;

// 定义边的结构体
typedef struct Edge {
    int dest; // 边的终点
    struct Edge *next; // 指向下一条边的指针
} Edge;

// 创建一个新的顶点
Vertex* createVertex(int id) {
    Vertex *vertex = (Vertex *)malloc(sizeof(Vertex));
    vertex->id = id;
    vertex->degree = 0;
    vertex->edges = NULL;
    return vertex;
}

// 添加一条边到顶点
void addEdge(Vertex *vertex, int dest) {
    Edge *newEdge = (Edge *)malloc(sizeof(Edge));
    newEdge->dest = dest;
    newEdge->next = vertex->edges;
    vertex->edges = newEdge;
    vertex->degree++;
}

// 打印邻接多重表
void printAdjacencyList(Vertex **vertices, int numVertices) {
    for (int i = 0; i < numVertices; i++) {
        printf("Vertex %d: ", vertices[i]->id);
        Edge *edge = vertices[i]->edges;
        while (edge != NULL) {
            printf("%d ", edge->dest);
            edge = edge->next;
        }
        printf("");
    }
}

int main() {
    int numVertices = 5; // 假设有5个顶点
    Vertex **vertices = (Vertex **)malloc(numVertices * sizeof(Vertex *));

    // 创建顶点并初始化邻接多重表
    for (int i = 0; i < numVertices; i++) {
        vertices[i] = createVertex(i);
    }

    // 添加边到邻接多重表
    addEdge(vertices[0], 1);
    addEdge(vertices[0], 4);
    addEdge(vertices[1], 2);
    addEdge(vertices[1], 3);
    addEdge(vertices[1], 4);
    addEdge(vertices[2], 3);
    addEdge(vertices[3], 4);

    // 打印邻接多重表
    printAdjacencyList(vertices, numVertices);

    // 释放内存
    for (int i = 0; i < numVertices; i++) {
        Edge *edge = vertices[i]->edges;
        while (edge != NULL) {
            Edge *temp = edge;
            edge = edge->next;
            free(temp);
        }
        free(vertices[i]);
    }
    free(vertices);

    return 0;
}

 结果如下:

Vertex 0: 4 1 Vertex 1: 4 3 2 Vertex 2: 3 Vertex 3: 4 Vertex 4:


到此图的类型定义和存储结构就结束啦,如果文章对你有用的话请个赞支持一下吧!

下篇文章将更新图的遍历内容。

  • 22
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值