一、概念
图(Graph)是由顶点的有穷非空集合和顶点之间边的集合组成,通常表示为:G(V,E),其中,G表示一个图,V是图G中顶点的集合,E是图G中边的集合。
注意:线性表中可以没有元素,称为空表。树中可以没有结点,叫做空树。但是在图中不允许没有顶点,可以没有边。
无向图
![](https://img.haomeiwen.com/i8897049/d4f84f221e108e7a.png)
![](https://img.haomeiwen.com/i8897049/210ce6062b8047e4.png)
有向图
![](https://img.haomeiwen.com/i8897049/9d708c9c9f9a3d3d.png)
![](https://img.haomeiwen.com/i8897049/b01104e15298adfa.png)
二、图的存储
1.邻接矩阵
使用数组的形式来储存数据。
![](https://img.haomeiwen.com/i8897049/091e577c2545de4a.png)
在有向图中,当一个顶点能够指向另外一个顶点,则矩阵中的值为1,否则为0。
![](https://img.haomeiwen.com/i8897049/d8b574f240ef866e.png)
在无向图中,边是相当于两个顶点相互指向。
#include<stdio.h>
#define GRAPH_SIZE 5
int graph[GRAPH_SIZE][GRAPH_SIZE];
void initGraph() {
for(int i=0; i<GRAPH_SIZE; i++) {
for(int j=0; j<GRAPH_SIZE; j++) {
graph[i][j] = 0;
}
}
graph[0][2] = 1;
graph[0][3] = 1;
graph[1][3] = 1;
graph[1][4] = 1;
graph[2][0] = 1;
graph[2][4] = 1;
graph[3][0] = 1;
graph[3][1] = 1;
graph[4][1] = 1;
graph[4][2] = 1;
}
void printGraph() {
printf("printGraph:\n");
for(int i=0; i<GRAPH_SIZE; i++) {
for(int j=0; j<GRAPH_SIZE; j++) {
printf("%d ", graph[i][j]);
}
printf("\n");
}
}
void main() {
initGraph();
printGraph();
}
2.邻接表与逆邻接表
使用顶点以及其弧的指向来储存数据。
![](https://img.haomeiwen.com/i8897049/1c4bd2451dd29ce1.jpg)
#include<stdio.h>
#include<malloc.h>
#define GRAPH_SIZE 4
struct EdgeNode { //边结点
int id; //弧头顶点
EdgeNode *next; //弧尾相同,下一条边指针(对应出度指针)
};
struct VertexNode { //顶点结点
int id;
EdgeNode *firstOut; //出度指针
};
VertexNode graph[GRAPH_SIZE];
EdgeNode* mallocEdgeNode() {
return (EdgeNode*)malloc(sizeof(EdgeNode));
}
void initGraph() {
for(int i=0; i<GRAPH_SIZE; i++) {
graph[i].id = i;
graph[i].firstOut = NULL;
}
EdgeNode *temp = mallocEdgeNode();
temp->id = 3;
temp->next = graph[0].firstOut;
graph[0].firstOut = temp;
temp = mallocEdgeNode();
temp->id = 0;
temp->next = graph[1].firstOut;
graph[1].firstOut = temp;
temp = mallocEdgeNode();
temp->id = 2;
temp->next = graph[1].firstOut;
graph[1].firstOut = temp;
temp = mallocEdgeNode();
temp->id = 0;
temp->next = graph[2].firstOut;
graph[2].firstOut = temp;
temp = mallocEdgeNode();
temp->id = 1;
temp->next = graph[2].firstOut;
graph[2].firstOut = temp;
}
void printGraph() {
printf("printGraph:\n");
for(int i=0; i<GRAPH_SIZE; i++) {
printf("VertexNode %d ==> ", i);
EdgeNode* edgeNode = graph[i].firstOut; //求出度
while(edgeNode != NULL) {
printf("%d ", edgeNode->id);
edgeNode = edgeNode->next;
}
printf("\n");
}
}
void main() {
initGraph();
printGraph();
}
#include<stdio.h>
#include<malloc.h>
#define GRAPH_SIZE 4
struct EdgeNode { //边结点
int id; //弧尾顶点
EdgeNode *next; //弧头相同,下一条边指针(对应入度指针)
};
struct VertexNode { //顶点结点
int id;
EdgeNode *firstIn; //入度指针
};
VertexNode graph[GRAPH_SIZE];
EdgeNode* mallocEdgeNode() {
return (EdgeNode*)malloc(sizeof(EdgeNode));
}
void initGraph() {
for(int i=0; i<GRAPH_SIZE; i++) {
graph[i].id = i;
graph[i].firstIn = NULL;
}
EdgeNode *temp = mallocEdgeNode();
temp->id = 1;
temp->next = graph[0].firstIn;
graph[0].firstIn = temp;
temp = mallocEdgeNode();
temp->id = 2;
temp->next = graph[0].firstIn;
graph[0].firstIn = temp;
temp = mallocEdgeNode();
temp->id = 2;
temp->next = graph[1].firstIn;
graph[1].firstIn = temp;
temp = mallocEdgeNode();
temp->id = 1;
temp->next = graph[2].firstIn;
graph[2].firstIn = temp;
temp = mallocEdgeNode();
temp->id = 0;
temp->next = graph[3].firstIn;
graph[3].firstIn = temp;
}
void printGraph() {
printf("printGraph:\n");
for(int i=0; i<GRAPH_SIZE; i++) {
printf("VertexNode %d <== ", i);
EdgeNode* edgeNode = graph[i].firstIn; //求入度
while(edgeNode != NULL) {
printf("%d ", edgeNode->id);
edgeNode = edgeNode->next;
}
printf("\n");
}
}
void main() {
initGraph();
printGraph();
}
3.十字链表
![](https://img.haomeiwen.com/i8897049/aae72dc8aa5f1b3f.jpg)
![](https://img.haomeiwen.com/i8897049/3e4c60861b852f41.jpg)
#include<stdio.h>
#include<malloc.h>
#define GRAPH_SIZE 4
struct EdgeNode { //边结点
int tailId; //弧尾顶点
int headId; //弧头顶点
EdgeNode *headLink; //弧头相同,下一条边指针(对应入度指针)
EdgeNode *tailLink; //弧尾相同,下一条边指针(对应出度指针)
};
struct VertexNode { //顶点结点
int id;
EdgeNode *firstIn; //入度指针
EdgeNode *firstOut; //出度指针
};
VertexNode graph[GRAPH_SIZE];
EdgeNode* mallocEdgeNode() {
return (EdgeNode*)malloc(sizeof(EdgeNode));
}
void initGraph() {
for(int i=0; i<GRAPH_SIZE; i++) {
graph[i].id = i;
graph[i].firstIn = NULL;
graph[i].firstOut = NULL;
}
EdgeNode *temp = mallocEdgeNode();
temp->tailId = 0; //弧尾顶点
temp->headId = 3; //弧头顶点
temp->headLink = graph[temp->headId].firstIn; //弧头相同,下一条边指针
graph[temp->headId].firstIn = temp;
temp->tailLink = graph[temp->tailId].firstOut; //弧尾相同,下一条边指针
graph[temp->tailId].firstOut = temp;
temp = mallocEdgeNode();
temp->tailId = 1; //弧尾顶点
temp->headId = 0; //弧头顶点
temp->headLink = graph[temp->headId].firstIn; //弧头相同,下一条边指针
graph[temp->headId].firstIn = temp;
temp->tailLink = graph[temp->tailId].firstOut; //弧尾相同,下一条边指针
graph[temp->tailId].firstOut = temp;
temp = mallocEdgeNode();
temp->tailId = 2; //弧尾顶点
temp->headId = 0; //弧头顶点
temp->headLink = graph[temp->headId].firstIn; //弧头相同,下一条边指针
graph[temp->headId].firstIn = temp;
temp->tailLink = graph[temp->tailId].firstOut; //弧尾相同,下一条边指针
graph[temp->tailId].firstOut = temp;
temp = mallocEdgeNode();
temp->tailId = 1; //弧尾顶点
temp->headId = 2; //弧头顶点
temp->headLink = graph[temp->headId].firstIn; //弧头相同,下一条边指针
graph[temp->headId].firstIn = temp;
temp->tailLink = graph[temp->tailId].firstOut; //弧尾相同,下一条边指针
graph[temp->tailId].firstOut = temp;
temp = mallocEdgeNode();
temp->tailId = 2; //弧尾顶点
temp->headId = 1; //弧头顶点
temp->headLink = graph[temp->headId].firstIn; //弧头相同,下一条边指针
graph[temp->headId].firstIn = temp;
temp->tailLink = graph[temp->tailId].firstOut; //弧尾相同,下一条边指针
graph[temp->tailId].firstOut = temp;
}
void printGraph() {
printf("printGraph:\n");
for(int i=0; i<GRAPH_SIZE; i++) {
printf("VertexNode %d ==> ", i);
EdgeNode* edgeNode = graph[i].firstOut; //求出度
while(edgeNode != NULL) {
printf("%d ", edgeNode->headId);
edgeNode = edgeNode->tailLink;
}
printf("\n");
printf("VertexNode %d <== ", i);
edgeNode = graph[i].firstIn; //求入度
while(edgeNode != NULL) {
printf("%d ", edgeNode->tailId);
edgeNode = edgeNode->headLink;
}
printf("\n\n");
}
}
void main() {
initGraph();
printGraph();
}
三、图的遍历
1.深度优先遍历
顺着一个顶点不断向下遍历,当遍历到的顶点与前面搜索过的顶点形成环的时候就停止。有点类似树的前序遍历。
#include<stdio.h>
#define GRAPH_SIZE 5
int graph[GRAPH_SIZE][GRAPH_SIZE];
bool visit[GRAPH_SIZE]; //顶点的访问标志
void initGraph() {
for(int i=0; i<GRAPH_SIZE; i++) {
for(int j=0; j<GRAPH_SIZE; j++) {
graph[i][j] = 0;
}
}
graph[0][2] = 1;
graph[0][3] = 1;
graph[1][3] = 1;
graph[1][4] = 1;
graph[2][0] = 1;
graph[2][4] = 1;
graph[3][0] = 1;
graph[3][1] = 1;
graph[4][1] = 1;
graph[4][2] = 1;
}
void printGraph() {
printf("printGraph:\n");
for(int i=0; i<GRAPH_SIZE; i++) {
for(int j=0; j<GRAPH_SIZE; j++) {
printf("%d ", graph[i][j]);
}
printf("\n");
}
}
void DFS(int index) {
visit[index] = true;
printf("visit: %d\n", index);
for(int i=0; i<GRAPH_SIZE; i++) {
if(graph[index][i] == 0 || visit[i] == true) {
continue;
}
DFS(i);
}
}
void main() {
initGraph();
printGraph();
for(int i=0; i<GRAPH_SIZE; i++) {
visit[i] = false;
}
DFS(0);
}
2.广度优先遍历
按层来不断进行遍历。
#include<stdio.h>
#include<malloc.h>
#define GRAPH_SIZE 5
struct GraphNode { //图的顶点结点
int id;
};
GraphNode graphNode[GRAPH_SIZE]; //图的顶点
int graph[GRAPH_SIZE][GRAPH_SIZE]; //图的边
bool visit[GRAPH_SIZE]; //顶点的访问标志
//----------Queue start----------
struct Node { //队列结点
int graphIndex; //图使用数组存储顶点结点,因此队列结点存储图的顶点索引即可,通过该索引可以在数组中找到图的顶点结点
Node* next;
};
struct Queue { //队列数据结构
Node* front;
Node* rear;
int size;
};
Node* mallocNode() { //申请结点内存
Node* node = (Node*)malloc(sizeof(Node));
return node;
}
void initQueue(Queue* queue) { //初始化队列
queue->front = NULL;
queue->rear = NULL;
queue->size = 0;
}
void enQueue(Queue* queue, int graphIndex) { //入队,插入队尾
Node* node = mallocNode();
node->graphIndex = graphIndex;
node->next = NULL;
if(queue->rear == NULL) {
queue->front = queue->rear = node;
} else {
queue->rear->next = node;
queue->rear = node;
}
queue->size++;
}
Node* deQueue(Queue* queue) { //出队,删除队头
Node* node;
if(queue->front == NULL) {
node = NULL;
} else {
node = queue->front;
queue->front = queue->front->next;
if(queue->front == NULL) {
queue->rear = NULL;
}
queue->size--;
}
return node;
}
//----------Queue end----------
void initGraph() {
for(int i=0; i<GRAPH_SIZE; i++) { //初始化顶点
graphNode[i].id = i;
}
for(int i=0; i<GRAPH_SIZE; i++) { //初始化边
for(int j=0; j<GRAPH_SIZE; j++) {
graph[i][j] = 0;
}
}
graph[0][2] = 1;
graph[0][3] = 1;
graph[1][3] = 1;
graph[1][4] = 1;
graph[2][0] = 1;
graph[2][4] = 1;
graph[3][0] = 1;
graph[3][1] = 1;
graph[4][1] = 1;
graph[4][2] = 1;
}
void printGraph() {
printf("printGraph:\n");
for(int i=0; i<GRAPH_SIZE; i++) {
printf("%d ", graphNode[i].id);
}
printf("\n\n");
for(int i=0; i<GRAPH_SIZE; i++) {
for(int j=0; j<GRAPH_SIZE; j++) {
printf("%d ", graph[i][j]);
}
printf("\n");
}
printf("\n");
}
void BFS(int index) { //使用队列,按照"从上到下,从左到右"规则,进行广度优先遍历
Queue queue;
initQueue(&queue);
enQueue(&queue, index); //入队
visit[index] = true;
while(queue.size != 0) { //循环结束条件:队列空
Node* deleteNode = deQueue(&queue); //出队
printf("%d ", deleteNode->graphIndex);
for(int i=0; i<GRAPH_SIZE; i++) {
if(graph[deleteNode->graphIndex][i] == 0 || visit[i] == true) {
continue;
}
enQueue(&queue, i);
visit[i] = true;
}
free(deleteNode); //释放被删除的结点内存
}
}
void main() {
initGraph();
printGraph();
for(int i=0; i<GRAPH_SIZE; i++) {
visit[i] = false;
}
BFS(0);
printf("\n");
}
四、最小生成树
当遍历的时候涉及边的权值问题,就要使用最小生成树来解决。
![](https://img.haomeiwen.com/i8897049/91aeb9737deb970a.png)
![](https://img.haomeiwen.com/i8897049/dfe554abcaa3d1c8.png)
1.普里姆算法(Prim)
过程:
1.选定一个点A,放入点集合。
2.关于A的所有的边放入 待选边集合。
3.从待选边集合选取权值最小的边 A - F (1),放入边集合。
4.边集合中提取出未在点集合的点F,放入点集合。
5.刷新待选边集合,列出所有A和F有关的边。
6.选出权值最小且不会与当前已选边形成闭环的边,然后放入边集合。
7.以此类推,得出所有边,既是最小生成树。
![](https://img.haomeiwen.com/i8897049/df7e8c0c718edfd6.png)
点集合 ==> 待选边集合(小根堆) ==> 最终边集合(取最小值) ==> 重复
2.克鲁斯卡尔算法(Kruskal)
过程:
1.列出所有边的集合。
2.依次选取权值最小的边,同时将涉及的点加入点集合。
3.选取的边不能与已选边构成闭合。
4.当点集合中出现了所有点以后,需要让所有点能连通,形成生成树(边 = 点 - 1)。
![](https://img.haomeiwen.com/i8897049/283d11cf40950a7e.png)
待选边集合(小根堆) ==> 最终边集合(取最小值) ==> 点集合 ==> 重复
五、最短路径
1.深度优先搜索算法(两点最短路径,有权图)
#include<stdio.h>
#define INF 9999999 //表示无穷大
#define GRAPH_SIZE 6 //定义图的顶点数量(顶点索引从1开始算起,这里定义的值为实际顶点数量加1)
int graph[GRAPH_SIZE][GRAPH_SIZE]; //图
bool visit[GRAPH_SIZE]; //顶点访问标志
int path[GRAPH_SIZE]; //路径
int pathIndex; //路径索引
int min = INF; //最小值,初始化为无穷大
void DFS(int cur, int to, int curDistance) { //cur:当前顶点,to:目标顶点,curDistance:当前路径距离
if(cur == to) { //找到目标顶点
for(int i=0; i<pathIndex; i++) { // 输出当前路径
printf("%d ", path[i]);
}
printf("==> %d\n", curDistance); //当前路径距离
if(curDistance < min) { //取最小值作为最终输出结果
min = curDistance;
}
return;
}
for(int i=1; i<=GRAPH_SIZE-1; i++) {
if(graph[cur][i] == INF || visit[i] == true) { //cur->i路径不可达,或者i已访问
continue;
}
visit[i] = true; //设置顶点i为已访问
path[pathIndex++] = i; //将顶点i加入路径,然后将路径索引加1
DFS(i, to, curDistance+graph[cur][i]); //DFS
visit[i] = false; // 上一步DFS探索完毕后, 设置顶点i为未访问
pathIndex--; //将路径索引减1,进行下一步DFS探索
}
}
void main() {
for (int i=1; i<=GRAPH_SIZE-1; i++) { //初始化边的权值,顶点索引从1开始算起
for (int j=1; j<=GRAPH_SIZE-1; j++) {
if(i == j) {
graph[i][j] = 0; //自己到自己,权值初始化为0
} else {
graph[i][j] = INF; //其它情况,权值初始化为无穷大
}
}
}
//初始化边的权值,顶点索引从1开始算起
graph[1][2] = 2;
graph[2][3] = 3;
graph[3][1] = 4;
graph[3][4] = 4;
graph[4][5] = 5;
graph[5][3] = 3;
graph[2][5] = 7;
graph[1][5] = 10;
for(int i=1; i<=GRAPH_SIZE-1; i++) { //初始化顶点访问标志,顶点索引从1开始算起
visit[i] = false;
}
pathIndex = 0; //路径索引初始化为0
visit[1] = true; //将位于起点的顶点访问标志设置为true,表示该顶点已访问
path[pathIndex++] = 1; //将位于起点的顶点索引保存到路径中,然后将路径索引加1
DFS(1, 5, 0);
printf("min: %d\n", min);
}
2.广度优先搜索算法(两点最短路径,无权图)
#include<stdio.h>
#include<malloc.h>
#define GRAPH_SIZE 6 //定义图的顶点数量(顶点索引从1开始算起,这里定义的值为实际顶点数量加1)
int graph[GRAPH_SIZE][GRAPH_SIZE]; //图
bool visit[GRAPH_SIZE]; //顶点访问标志
//----------Queue start----------
struct Node { //队列结点
int graphIndex; //图使用数组存储顶点结点,因此队列结点存储图的顶点索引即可,通过该索引可以在数组中找到图的顶点结点
int distance;
Node* next;
};
struct Queue { //队列数据结构
Node* front;
Node* rear;
int size;
};
Node* mallocNode() { //申请结点内存
Node* node = (Node*)malloc(sizeof(Node));
return node;
}
void initQueue(Queue* queue) { //初始化队列
queue->front = NULL;
queue->rear = NULL;
queue->size = 0;
}
void enQueue(Queue* queue, int graphIndex, int distance) { //入队,插入队尾
Node* node = mallocNode();
node->graphIndex = graphIndex;
node->distance = distance;
node->next = NULL;
if(queue->rear == NULL) {
queue->front = queue->rear = node;
} else {
queue->rear->next = node;
queue->rear = node;
}
queue->size++;
}
Node* deQueue(Queue* queue) { //出队,删除队头
Node* node;
if(queue->front == NULL) {
node = NULL;
} else {
node = queue->front;
queue->front = queue->front->next;
if(queue->front == NULL) {
queue->rear = NULL;
}
queue->size--;
}
return node;
}
//----------Queue end----------
int BFS(int cur, int to) { //使用队列,按照"从上到下,从左到右"规则,进行广度优先遍历
if(cur == to) { //当前顶点即是目标顶点
return 0;
}
Queue queue;
initQueue(&queue);
enQueue(&queue, cur, 0); //入队
visit[cur] = true;
while(queue.size != 0) { //循环结束条件:队列空
Node* deleteNode = deQueue(&queue); //出队
for(int i=1; i<=GRAPH_SIZE-1; i++) {
if(graph[deleteNode->graphIndex][i] == 0 || visit[i] == true) {
continue;
}
if(i == to) { //找到目标顶点
return deleteNode->distance+1;
}
enQueue(&queue, i, deleteNode->distance+1);
visit[i] = true;
}
free(deleteNode); //释放被删除的结点内存
}
}
void main() {
for (int i=1; i<=GRAPH_SIZE-1; i++) { //初始化边,顶点索引从1开始算起
for (int j=1; j<=GRAPH_SIZE-1; j++) {
graph[i][j] = 0; //0表示不可达,1表示可达
}
}
//初始化边,顶点索引从1开始算起
graph[1][2] = 1;
graph[2][3] = 1;
graph[3][1] = 1;
graph[3][4] = 1;
graph[4][5] = 1;
graph[5][3] = 1;
graph[2][5] = 1;
graph[1][5] = 1;
for(int i=1; i<=GRAPH_SIZE-1; i++) { //初始化顶点访问标志,顶点索引从1开始算起
visit[i] = false;
}
int distance = BFS(1, 3);
printf("min distance: %d\n", distance);
}
3.迪杰斯特拉算法(单源最短路径,有权图,权重无负值)
点集合 ==> 待选边集合 ==> 最终边集合 ==> 重复
顶点结点内容:距离源点总距离、上一个顶点结点
4.Bellman-Ford算法(单源最短路径,有权图,权重有负值)
第i次遍历(1 <= i <= 顶点数-1) ==> 经过一条边进行松弛 ==> 经过两条边进行松弛 ==> 重复
5.弗洛伊德算法(多源最短路径)
三重循环:
for(int k=1; k<=n; k++) {
for(int i=1; i<=n; i++) {
for(int j=1; j<=n; j++) {
if(i != j && j != k && i != k) {
if(a[i][j] > a[i][k] + a[k][j]) {
a[i][j] = a[i][k] + a[k][j];
}
}
}
}
}
网友评论