图的六个常用算法总结举例

本文章设计的知识有(每个都有例子)帮助那些像我一样刚开始接触图,对图相关的知识的实践有点难以一下手的bro们:

  1. 常用的深度优先搜索算法DFS,广度优先搜索算法BFS
  2. 最小生成树算法Prim,Kruskal算法
  3. 单源最短路径Dijkstra算法
  4. 全源最短路径Floyd算法

这些算法的思想在此就不做过多介绍,下面就分不同的部分进行一个一个说明实现算法及测试:

一,深度优先搜索算法DFS,广度优先搜索算法BFS

首先我是定义了一个头文件,里面包含存储图结构信息的一些结构体,DFS函数,BFS函数,释放内存等

//Graph.h
#pragma once
#include<stdlib.h>

/*
	邻接链表存储结构的创建
*/


#define MAX_SIZE 50

/*
	定义图边的存储结构
*/
typedef struct ArcNode {
	int adjV;
	struct ArcNode *next;
}ArcNode;

/*
	定义图顶点的存储结构
*/
typedef struct{
	char data;
	ArcNode *first;
}VNode;

/*
	定义图的存储结构(邻接链表)
	n : 几个顶点
	e : 几个边
*/
typedef struct {
	VNode adjList[MAX_SIZE];
	int n, e;//
}AGraph;

/*
	创建图的函数
	from:起始点
	to:终点
*/
void CreateGraph_LinkedList(AGraph &);//此处的传参使用引用,创建邻接链表

/*
	深度优先遍历DFS
	v:起始点
*/
void DFS(int, AGraph*);

/*
	广度优先遍历BFS
*/
void BFS(int, AGraph*);

/*
	释放空间
*/
void FreeMem(AGraph *);
void free_li(void *);
//Graph.cpp
#include"Graph.h"
#include<iostream>
#include<stdlib.h>

bool visit_D[MAX_SIZE] = { false };//顶点的访问标记,深度优先遍历
bool visit_B[MAX_SIZE] = { false };//顶点的访问标记,广度优先遍历
void CreateGraph(AGraph &G) {
	//接下来,为输入各顶点关系,然后对G进行处理
	int j = 0;
	while (j < G.e)
	{
		int from, to;
		std::cin >> from >> to;

		//创建图
		VNode* X = &(G.adjList[from]);
		ArcNode* p;
		if (X->first == NULL) {//判断第一个节点信息
			ArcNode* temp = (ArcNode*)malloc(sizeof(ArcNode));
			if (temp == NULL) {
				std::cout << "error" << std::endl;
				return;
			}
			temp->adjV = to;
			temp->next = NULL;
			X->first = temp;
		}
		else {//如果有信息,就指针向后移动
			p = X->first;
			while (p->next != NULL) {
				p = p->next;
			}
			ArcNode* temp = (ArcNode*)malloc(sizeof(ArcNode));
			if (temp == NULL) {
				std::cout << "error" << std::endl;
				return;
			}
			temp->adjV = to;
			temp->next = NULL;
			p->next = temp;
		}
		++j;
	}
	
}

void DFS(int v, AGraph* G) {
	visit_D[v] = true;
	std::cout << G->adjList[v].data << " ";//打印出顶点信息
	ArcNode *q = G->adjList[v].first;
	while (q != NULL) {
		if (!visit_D[q->adjV]) {
			DFS(q->adjV, G);
		}
		q = q->next;
	}
}
void BFS(int v, AGraph* G) {
	ArcNode *p;
	int que[MAX_SIZE], front = 0, rear = 0;//循环队列
	int j;
	std::cout << G->adjList[v].data<<" ";
	visit_B[v] = true;
	rear = (rear + 1) % MAX_SIZE;
	que[rear] = v;
	while (front != rear)
	{
		front = (front + 1) % MAX_SIZE;
		j = que[front];
		p = G->adjList[j].first;
		while (p != NULL) {
			if (!visit_B[p->adjV]) {//如果没有被访问
				std::cout << G->adjList[p->adjV].data << " ";
				visit_B[p->adjV] = true;
				rear = (rear + 1) % MAX_SIZE;
				que[rear] = p->adjV;
			}
			p = p->next;
		}
	}
}

void free_li(void * pointer) {
	ArcNode *p = static_cast<ArcNode*>(pointer);
	if (p == NULL) {
		return;
	}
	free_li(static_cast<void *>(p->next));
	free(p);
}
void FreeMem(AGraph * G) {
	for (int i = 0; i < G->n;++i) {
		if (G->adjList[i].first != NULL) {
			free_li(static_cast<void *>(G->adjList[i].first));
		}
	}
	free(G);
}

接下来是测试代码:
该测试代码主要由两个基本图构成,如下:
该图的存储结构为
在这里插入图片描述

在这里插入图片描述
上图(有向图)测试输入数据为:

5 6
A B C D E
0 1
1 3
1 4
2 0
3 2
4 2
--------------以下为输出DFS,BFS
A B D C E
A B D E C

第二个测试案例为无向图,只需每个边输入两次即可(或者采用邻接矩阵)如下:
在这里插入图片描述
输入:

6 18
A B C D E F
0 1
1 0
0 2
2 0
0 3
3 0
1 2
2 1
1 4
4 1
2 4
4 2
2 3
3 2
3 5
5 3
4 5
5 4
------------------以下为输出DFS,BFS
A B C E F D
A B C D E F

#include"Graph.h"
#include<iostream>
using namespace std;

int main() {
	AGraph* G = (AGraph*)malloc(sizeof(AGraph));
	//输入图信息
	int n, e;
	cin >> n >> e;
	G->n = n; // 顶点个数
	G->e = e;
	//输入都有哪些顶点
	for (int i = 0; i < n; ++i) {
		char ver;
		cin >> ver;
		G->adjList[i].data = ver;
		G->adjList[i].first = NULL;
	}
	CreateGraph(*G);//引用
	//以上几步,将图的信息已经存储到G中
	//深度搜索
	DFS(0, G);//A B D C E
	cout << endl;
	//广度搜索
	BFS(0, G);//A B D E C
	//释放内存
	FreeMem(G);
	system("pause");
	return 0;
}

二,最小生成树Prim算法

#include<iostream>
#include<stdlib.h>
#include<vector>
#include<algorithm>
using namespace std;
#define MAX_NUM 20
vector<char> Vertical_prim;//顶点数组,处理顶点为字母,或者乱序的一些情况
int Edge[MAX_NUM][MAX_NUM];//存储边信息
/*
	设置顶点信息
	P_1:顶点个数
	P_2:边个数
*/
//有向图
void Init_Direct(int n,int e) {
	//初始化边信息数组都为最大值
	for (int i = 0; i < MAX_NUM; ++i) {
		for (int j = 0; j < MAX_NUM; ++j) {
			Edge[i][j] = 1024;
		}
	}
	//处理顶点信息
	for (int i = 0; i < n; ++i) {
		char ver;
		cin >> ver;
		Vertical_prim.push_back(ver);
	}
	//处理边信息,无向图需要双向赋值
	for (int j = 0; j < e; ++j) {
		int from = 0, to = 0, weight = 0;
		cin >> from >> to >> weight;
		Edge[from][to] = weight;
	}
}
//无向图
void Init_NoDirect(int n, int e) {
	//初始化边信息数组都为最大值
	for (int i = 0; i < MAX_NUM; ++i) {
		for (int j = 0; j < MAX_NUM; ++j) {
			Edge[i][j] = 1024;
		}
	}
	//处理顶点信息
	for (int i = 0; i < n; ++i) {
		char ver;
		cin >> ver;
		Vertical_prim.push_back(ver);
	}
	//处理边信息
	for (int j = 0; j < e; ++j) {
		int from, to, weight;
		cin >> from >> to >> weight;
		Edge[from][to] = Edge[to][from] = weight;
	}
}
/**
	最小生成树算法prim,利用顶点
	P_1:表示顶点个数
	P_2:存储边信息的二维矩阵
	P_3:起始顶点下表(保存于Vertical中)
	P_4:最小生成树的权重
	返回最小生成树的最小权值
*/
int prim(int n, int MGraph[][MAX_NUM], int v0) {
	int lowCost[MAX_NUM], vSet[MAX_NUM];
	int v, k, min;
	for (int i = 0; i < n; ++i) {
		lowCost[i] = MGraph[v0][i];
		vSet[i] = 0;
	}
	v = v0;
	vSet[v] = 1;
	int sum = 0;
	for (int i = 0; i < n - 1; ++i) {
		min = 1024;//1024只是代表一个较大值
		for (int j = 0; j < n; ++j) {
			if (0 == vSet[j] && lowCost[j] < min) {
				min = lowCost[j];
				k = j;
			}
		}
		vSet[k] = 1;
		v = k;
		sum += min;
		for (int j = 0; j < n; ++j) {
			if (0 == vSet[j] && MGraph[v][j] < lowCost[j]) {
				lowCost[j] = MGraph[v][j];
			}
		}
	}
	return sum;
}
void test_prim() {
	//无向图
	int num;
	int n, e;
	cin >> n >> e;
	Init_NoDirect(n, e);
	num = prim(n, Edge, 0);
	cout << num;
}

测试的案例图如下(无向图):
在这里插入图片描述
测试输入为:

6 9
A B C D E F
0 1 1
0 2 2
0 3 3
1 2 7
1 4 8
2 4 4
2 3 6
4 5 9
3 5 5
--------------------输出结果
15

三,最小生成树Kruskal

#include<iostream>
#include<stdlib.h>
#include<vector>
#include<algorithm>
using namespace std;
#define MAXSIZE 50

typedef struct {
	int from, to;
	int weight;
}Road;//边,权结构体

vector<char> verc_Kruskal;//顶点信息

int v[MAXSIZE] = { 0 };
int getRoot(int p) {//获取某节点的根节点
	while (p != v[p]) {
		p = v[p];
	}
	return p;
}
int Kruskal(Road road[MAXSIZE],int n,int e) {
	int sum = 0;//统计最小生成树权值
	int a, b;
	for (int i = 0; i < n; ++i) {//对所有顶点的父节点进行初始化
		v[i] = i;
	}
	sort(road, road + e, [](Road &a, Road &b)->bool {
		return a.weight <= b.weight;
	});//按weidget进行升序排序,此处使用lambda表达式进行声明规则

	for (int i = 0; i < e; ++i) {
		a = getRoot(road[i].from);
		b = getRoot(road[i].to);
		if (a != b) {
			v[a] = b;
			sum += road[i].weight;
		}
	}
	return sum;
}
void test_kruskal() {
	int n, e;//分别为顶点数,边数
	cin >> n >> e;
	Road road[MAXSIZE];
	//输入顶点信息
	for (int i = 0; i < n; ++i) {
		char ver;
		cin >> ver;
		verc_Kruskal.push_back(ver);
	}
	//输入数据
	for (int i = 0; i < e; ++i) {
		int from, to, weidget;
		cin >> from >> to >> weidget;
		Road temp;
		temp.from = from;
		temp.to = to;
		temp.weight = weidget;
		road[i] = temp;
	}
	int sum = Kruskal(road,n,e);
	cout << sum;
}

测试用图如下:
在这里插入图片描述
输入数据如下:

6 9
A B C D E F
0 1 1
0 2 5
0 3 6
1 2 8
1 4 4
2 4 2
2 3 7
3 5 3
4 5 9
------------输出如下
16

四,单源最短路径Dijkstra算法

#include<iostream>
#include<stdlib.h>
#include<vector>
#include<algorithm>
using namespace std;
#define MAX_ 20 //只是为了函数参数声明使用,具体的数组范围会根据实际情况而定
#define INF 65535
vector<char> ver_V;//存储顶点信息
int Medge[MAX_][MAX_];//存储边,权信息
int dist[MAX_];//起点到其余各顶点的最短路径值
int path[MAX_];//最短路径的路径信息
void dijkstra(int n, int MGraph[][MAX_], int v0) {
	int set[MAX_];//存储结点是否被访问,set[v]=1;//表示v结点已被访问
	int min, v;
	for (int i = 0; i < n; ++i) {
		dist[i] = MGraph[v0][i];
		set[i] = 0;
		if (MGraph[v0][i] < INF) {//此处的65535仅代表一个INF,很大的值
			path[i] = v0;
		}
		else {
			path[i] = -1;
		}
	}
	dist[v0] = 0;//因为一般v0都为0从第一个顶点开始,顶点到本身的距离为0
	set[v0] = 1;
	path[v0] = -1;
	for (int i = 0; i < n - 1; ++i) {
		min = INF;
		for (int j = 0; j < n; ++j) {
			if (0 == set[j] && dist[j] < min) {
				v = j;
				min = dist[j];
			}
		}
		set[v] = 1;
		for (int k = 0; k < n; ++k) {
			if (set[k] == 0 && (dist[v] + MGraph[v][k]) < dist[k]) {
				dist[k] = dist[v] + MGraph[v][k];
				path[k] = v;
			}
		}
	}
}
void Init_Direct_Dij(int n, int e) {//有向图
	//初始化边信息数组都为最大值
	for (int i = 0; i < MAX_; ++i) {
		for (int j = 0; j < MAX_; ++j) {
			Medge[i][j] = INF;
		}
	}
	//处理顶点信息
	for (int i = 0; i < n; ++i) {
		char ver;
		cin >> ver;
		ver_V.push_back(ver);
	}
	//处理边信息
	for (int j = 0; j < e; ++j) {
		int from, to, weight;
		cin >> from >> to >> weight;
		Medge[from][to] = weight;
	}
}
void print_trace(int des) {//0到des的最短路径
	int temp = path[des];
	if (temp != -1) {
		print_trace(temp);
	}
	if (0 == des) {
		cout << des<<"("<<ver_V.at(des)<<")";
	}
	else {
		cout << "->" << des << "(" << ver_V.at(des) << ")";
	}
}
void test_dijkstra() {
	int n, e;
	cin >> n >> e;
	Init_Direct_Dij(n, e);
	dijkstra(n, Medge, 0);
	cout<<"dist:: ";
	for (int i = 0; i < n; ++i) {
		cout << dist[i] << " ";
	}
	cout << endl;

	cout << "path:: ";
	for (int i = 0; i < n; ++i) {
		cout << path[i] << " ";
	}
	cout << endl;
	
	cout << "index:: ";
	for (int k = 0; k < n; ++k) {
		cout << k << " ";
	}
	cout << endl;
	print_trace(6);
}

测试图如下:
在这里插入图片描述
输入数据:

7 12
A B C D E F G
0 1 4
0 2 6
0 3 6
1 2 1
1 4 7
2 4 6
2 5 4
3 2 2
3 5 5
4 6 6
5 4 1
5 6 8
----------------结果输出
dist:: 0 4 5 6 10 9 16
path:: -1 0 1 0 5 2 4
index: 0 1 2 3 4 5 6
0(A)->1<B>->2<C>->5<F>->4<E>->6<G>
----------------举例分析
顶点的存储结构如下
下标:0 1 2 3 4 5 6
顶点:A B C D E F G
从结果数组path来分析:
A—>G(也就是0–>6,在dist数组中为dist[6]的值)点的最短距离为16,并且最短路径为A->B->C->F->E->G(0->1->2->5->4->6)

五,全源最短路径Floyd

#include<iostream>
#include<stdlib.h>
#include<vector>
#include<algorithm>
using namespace std;
#define MAX_F 20 //只是为了函数参数声明使用,具体的数组范围会根据实际情况而定
#define INF_F 65535
vector<char> ver_V_F;//存储顶点信息
//A数组中最终保存着两顶点之间的最短路径值
int A[MAX_F][MAX_F];//A[0][1]的值表示如果在图中,0-->1有直接的边相连,则值为相应的权值,否则为INF_F(无穷大),另外A[i][i]=0;
int Path[MAX_F][MAX_F];//保存路径
int F_Graph[MAX_F][MAX_F];//图
void Init_(int n,int e) {//初始化图
	//初始化F_Graph
	for (int i = 0; i < n; ++i) {
		for (int j = 0; j < n; ++j) {
			if (i == j) {
				F_Graph[i][j] = 0;
			}
			else {
				F_Graph[i][j] = INF_F;
			}
		}
	}
	//初始化顶点信息
	for (int i = 0; i < n; ++i) {
		char ver;
		cin >> ver;
		ver_V_F.push_back(ver);
	}
	//初始化边,权
	for (int i = 0; i < e; ++i) {
		int from, to, weidget;
		cin >> from >> to >> weidget;
		F_Graph[from][to] = weidget;//有向图
	}
}
void Print_Trace(int u,int v) {//打印u-->v的最短路径
	if (Path[u][v] == -1) {
		cout << "<" << u<<","<<v<<">";
	}
	else {
		int mid = Path[u][v];
		Print_Trace(u, mid);
		Print_Trace(mid, v);
	}
}
void cout_info(int u, int v) {
	cout << u << "-->" << v << "最短路径为:" << A[u][v] << endl;
	cout << "最短路径为:";
	Print_Trace(u, v);
	cout << endl;
}
void Floyd(int n, int MGraph[MAX_F][MAX_F]) {
	int i, j, v;
	//初始化A数组和path数组
	for (i = 0; i < n; ++i) {
		for (j = 0; j < n; ++j) {
			A[i][j] = MGraph[i][j];
			Path[i][j] = -1;
		}
	}
	//core
	for (v = 0; v < n; ++v) {
		for (i = 0; i < n; ++i) {
			for (j = 0; j < n; ++j){
				if (A[i][j] > A[i][v] + A[v][j]) {
					A[i][j] = A[i][v] + A[v][j];
					Path[i][j] = v;
				}
			}
		}
	}
}
void test_Floyd() {
	int n, e;
	cin >> n >> e;
	Init_(n, e);
	Floyd(n, F_Graph);
	cout << "A:" << endl;
	for (int i = 0; i < n; ++i) {
		for (int j = 0; j < n; ++j) {
			cout << A[i][j] << " ";
		}
		cout << endl;
	}
	cout << "Path:" << endl;
	for (int i = 0; i < n; ++i) {
		for (int j = 0; j < n; ++j) {
			cout << Path[i][j] << " ";
		}
		cout << endl;
	}
	cout_info(0, 2);//打印0-->2的路径信息
}
/*
4 8
A B C D
0 1 5
0 3 7
1 2 4
1 3 2
2 0 3
2 1 3
2 3 2
3 2 1
A:
0 5 8 7
6 0 3 2
3 3 0 2
4 4 1 0
Path:
-1 -1 3 -1
3 -1 3 -1
-1 -1 -1 -1
2 2 -1 -1
调用cout_info(0,2)
0-->2最短路径为:8
最短路径为:<0,3><3,2>
*/

测试用图如下:
在这里插入图片描述
测试输入数据:

4 8
A B C D
0 1 5
0 3 7
1 2 4
1 3 2
2 0 3
2 1 3
2 3 2
3 2 1
----------------输出如下
A:
0 5 8 7
6 0 3 2
3 3 0 2
4 4 1 0
Path:
-1 -1 3 -1
3 -1 3 -1
-1 -1 -1 -1
2 2 -1 -1
---------------分析
调用cout_info(0,2)
0–>2最短路径(存储在A数组中)为:8
最短路径为(通过Path数组查找输出):<0,3><3,2>

上述即为几个常用的图算法,对那些有负权值的图,以及其他的图算法,后面用到或学习过,会在此慢慢补充。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值