本文章设计的知识有(每个都有例子)帮助那些像我一样刚开始接触图,对图相关的知识的实践有点难以一下手的bro们:
- 常用的深度优先搜索算法DFS,广度优先搜索算法BFS
- 最小生成树算法Prim,Kruskal算法
- 单源最短路径Dijkstra算法
- 全源最短路径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>
上述即为几个常用的图算法,对那些有负权值的图,以及其他的图算法,后面用到或学习过,会在此慢慢补充。