C++ 数据结构算法 学习笔记(25) - 图及其企业级应用
图的故事导入
故事情节
Jack 自从买车后,交通出行方便了,心自然就野了!身边的各种朋友自然就多了起来! 有一天晚上,一个年轻漂亮的女同事生日,Jack 受邀请准时爽约!Jack 亲睐此女已久,只是 介于家里的 LD 不敢越雷池一步,但是,一有机会,Jack 都会和此女接近,经常在一起,所以可 以说是就当个哥们吧! Jack 当晚开心的和女同事喝酒、聊天,忘记了家的存在,不知不觉时间一下子指向了 10 点! 此时,正沉浸在幻想中的 Jack 被老婆电话惊醒!不用接都知道,老婆的“圣旨”又来了! 这下麻烦来了,老婆电话一到,必须半小时内到家,否则,皮都可能被母老虎拔了,但现在自己 渴了酒,叫代驾又需要很久的时间,这一想, Jack 觉得自己麻烦来了!
做为程序员,Jack 肯定会选择第 3 条,那么我们如何用程序来做选择呢?
图的原理精讲
在计算机科学中,一个图就是一些顶点的集合,这些顶点通过一系列边结对(连接)。顶点用圆圈表示,边就 是这些圆圈之间的连线。顶点之间通过边连接。注意:顶点有时也称为节点或者交点,边有时也称为链接。 社交网络,每一个人就是一个顶点,互相认识的人之间通过边联系在一起, 边表示彼此的关系。这种关系可以 是单向的,也可以是双向的!
同时,边可以是双向的,也可以是单向的!
我们前面讲解的树和链表都是图的特例!
如果我们有一个编程问题可以通过顶点和边表示,那么我们就可以将你的问题用图画出来,然后使用相应的图 算法来找到解决方案。
图的表示
领接链表
在邻接列表实现中,每一个顶点会存储一个从它这里开始的相邻边的列表。比如,如果顶点 B 有一条边到 A、 C 和 E,那么 A 的列表中会有 3 条边
邻接列表只描述指向外部的边。B 有一条边到 A,但是 A 没有边到 B,所以B没有出现在A的邻接列表中。 查找两个顶点之间的边或者权重会比较费时,因为遍历邻接列表直到找到为止。
领接矩阵
由二维数组对应的行和列都表示顶点,由两个顶点所决定的矩阵对应元素数值表示这里两个顶点是否相连(如, 0 表示不相连,非 0 表示相连和权值)、如果相连这个值表示的是相连边的权重。例如,广西到北京的机票, 我们用邻接矩阵表示:
往这个图中添加顶点的成本非常昂贵,因为新的矩阵结果必须重新按照新的行/列创建,然后将已有的数据复制到新的矩阵中。
结论:大多数时候,选择邻接链表是正确的。(在图比较稀疏的情况下,每一个顶点都只会和少数几个顶点相连,这种情况下邻接列表是最佳选择。如果这个图比较密集,每一个顶点都和大多数其他顶点相连,那么邻接矩阵更合适。)
图的算法实现
邻接表结构的定义
#define MaxSize 1024
typedef struct _EdgeNode {//与节点连接的边的定义
int adjvex; //邻接的顶点
int weight; //权重
struct _EdgeNode* next; //下一条边
}EdgeNode;
typedef struct _VertexNode {//顶点节点
char data; //节点数据
struct _EdgeNode* first;//指向邻接第一条边
}VertexNode, AdjList;
typedef struct _AdjListGraph {
AdjList* adjlist;
int vex; //顶点数
int edge; //边数
}AdjListGraph;
邻接表的初始化
void Init(AdjListGraph& G) {
G.adjlist = new AdjList[MaxSize];
G.edge = 0;
G.vex = 0;
for (int i = 0; i < MaxSize; i++)
{
visited[i] = false;
}
}
邻接表的创建
void Create(AdjListGraph& G) {
cout << "Please enter the graph algorithm vertices and node number" << endl;
cin >> G.vex >> G.edge;
cout << "Please enter the vertices node data" << endl;
for (int i = 0; i < G.vex; i++) {
cin >> G.adjlist[i].data;
G.adjlist[i].first = NULL;
}
char v1 = 0, v2 = 0;//保存输入的顶点的字符
int i1, i2; //保存顶点在数组中的下标
int weight;
cout << "Please enter the related vertices data to connect them:" << endl;
for (int i = 0; i < G.edge; i++) {
cin >> v1 >> v2>>weight;
i1 = Location(G, v1);
i2 = Location(G, v2);
if (i1 != -1 && i2 != -1) {//寻找到位置
EdgeNode* temp = new EdgeNode;
temp->adjvex = i2;
temp->next = G.adjlist[i1].first;
temp->weight = weight;
G.adjlist[i1].first = temp;
}
}
}
/*通过顶点对应的字符寻找顶点在图中的邻接点*/
int Location(AdjListGraph& G, char c) {
for (int i = 0; i < G.vex; i++) {
if (G.adjlist[i].data == c) {
return i;
}
}
return -1;
}
深度优先遍历思想
- 首先以一个未被访问过的顶点作为起始顶点,沿当前顶点的边走到未访问过的顶点;
- 当没有未访问过的顶点时,则回到上一个顶点,继续试探别的顶点,直到所有的顶点都被访问过。
使用深度优先搜索来遍历这个图的具体过程是:
- 首先从一个未走到过的顶点作为起始顶点,比如 A 顶点作为起点。
- 沿 A 顶点的边去尝试访问其它未走到过的顶点,首先发现 E 号顶点还没有走到过,于是访问E顶点
- 再以 E 顶点作为出发点继续尝试访问其它未走到过的顶点,接下来访问 D 顶点。
- 再尝试以 D 顶点作为出发点继续尝试访问其它未走到过的顶点。
- 但是,此时沿 D 顶点的边,已经不能访问到其它未走到过的顶点,接下来返回到 E 顶点。
- 返回到 E 顶点后,发现沿 E 顶点的边也不能再访问到其它未走到过的顶点。此时又回到顶点 A(D->E->A),再以 A 顶点作为出发点继续访问其它未走到过的顶点,于是接下来访问 C 顶点
- 最终访问的结果是 A -> E -> D ->B
代码实现
void DHS(AdjListGraph& G, int v)
{
if (visited[v] == true) return;
int index = -1;
cout << G.adjlist[v].data << " ";
visited[v] = true;
EdgeNode* tmp = G.adjlist[v].first;
if (tmp)
{
index = tmp->adjvex;
DHS(G, index);
tmp = tmp->next;
}
}
void DHS_Main(AdjListGraph& G)
{
for (int i = 0; i < G.vex; i++)
{
if (visited[i] == false)
{
DHS(G, i);
}
}
}
广度优先遍历思想
- 首先以一个未被访问过的顶点作为起始顶点,访问其所有相邻的顶点;
- 然后对每个相邻的顶点,再访问它们相邻的未被访问过的顶点,直到所有顶点都被访问过,遍历结束
void BHS(AdjListGraph& G, int v)
{
int index = -1;
int cur = -1;
queue<int>q;
q.push(v);
while (!q.empty())
{
cur = q.front();
if (visited[cur] == false)
{
cout << G.adjlist[cur].data << " ";
visited[cur] = true;
}
q.pop();
EdgeNode* temp = G.adjlist[cur].first;
while (temp)
{
index = temp->adjvex;
q.push(index);
temp = temp->next;
}
}
}
void BHS_Main(AdjListGraph& G)
{
for (int i = 0; i < G.vex; i++)
{
if (visited[i] == false)
{
BHS(G, i);
}
}
}
完整代码
#include <iostream>
#include <string>
#include <queue>
using namespace std;
#define MAX_SIZE 1024
bool visited[MAX_SIZE];
typedef struct _EdgeNode
{
int adjvex;
int weight;
struct _EdgeNode* next;
}EdgeNode;
typedef struct _VertexNode
{
char data;
EdgeNode* first;
}VertexNode, AdjList;
typedef struct _AdjListGraph
{
int edge;
int vex;
AdjList* adjlist;
}AdjListGraph;
bool Init_Graph(AdjListGraph& G)
{
G.adjlist = new AdjList [MAX_SIZE];
if (!G.adjlist)
{
cout << "Failed to allocate memory for the AdjGraph" << endl;
return true;
}
G.edge = 0;
G.vex = 0;
for (int i = 0; i < MAX_SIZE; i++)
{
visited[i] = false;
}
return true;
}
int Location(AdjListGraph& G, char data)
{
for (int i = 0; i < G.vex; i++)
{
if (G.adjlist[i].data == data)
{
cout << "The Location is sucessfully found" << endl;
return i;
}
}
return -1;
}
bool Create_Graph(AdjListGraph& G)
{
cout << "Please enter the number of vertices and edge for the ListGraph" << endl;
cin >> G.vex >> G.edge;
cout << "Please enter the vex data for the ListGraph accrodingly" << endl;
for (int i = 0; i < G.vex; i++)
{
cin >> G.adjlist[i].data;
G.adjlist[i].first = NULL;
}
char v1, v2;
int i1, i2;
int weight;
cout << "Please enter the related data to connect them" << endl;
for (int i = 0; i < G.edge; i++)
{
cin >> v1 >> v2 >>weight;
i1 = Location(G, v1);
i2 = Location(G, v2);
if (i1 != -1 && i2 != -1)
{
EdgeNode* new_node = new EdgeNode;
new_node->adjvex = i2;
new_node->next = G.adjlist[i1].first;
new_node->weight = weight;
G.adjlist[i1].first = new_node;
}
}
return true;
}
void DHS(AdjListGraph& G, int v)
{
if (visited[v] == true) return;
int index = -1;
cout << G.adjlist[v].data << " ";
visited[v] = true;
EdgeNode* tmp = G.adjlist[v].first;
if (tmp)
{
index = tmp->adjvex;
DHS(G, index);
tmp = tmp->next;
}
}
void DHS_Main(AdjListGraph& G)
{
for (int i = 0; i < G.vex; i++)
{
if (visited[i] == false)
{
DHS(G, i);
}
}
}
void BHS(AdjListGraph& G, int v)
{
int index = -1;
int cur = -1;
queue<int>q;
q.push(v);
while (!q.empty())
{
cur = q.front();
if (visited[cur] == false)
{
cout << G.adjlist[cur].data << " ";
visited[cur] = true;
}
q.pop();
EdgeNode* temp = G.adjlist[cur].first;
while (temp)
{
index = temp->adjvex;
q.push(index);
temp = temp->next;
}
}
}
void BHS_Main(AdjListGraph& G)
{
for (int i = 0; i < G.vex; i++)
{
if (visited[i] == false)
{
BHS(G, i);
}
}
}
int main()
{
AdjListGraph G;
Init_Graph(G);
Create_Graph(G);
BHS_Main(G);
system("pause");
return 0;
}
图的导航-最短路径算法 (较复杂,可跳过)
从起点开始访问所有路径,则到达终点节点的路径有多条,其中路径权值最短的一条则为最短路径。最短路径算法有 深度优先遍历、广度优先遍历、Bellman-Ford 算法、弗洛伊德算法、 SPFA(Shortest Path Faster Algorithm)算法和迪 杰斯特拉算法等。
代码实现
#include <iostream>
#include <string>
#include <queue>
using namespace std;
#define MaxSize 1024
typedef struct _EdgeNode {//与节点连接的边的定义
int adjvex; //邻接的顶点
int weight; //权重
struct _EdgeNode* next; //下一条边
}EdgeNode;
typedef struct _VertexNode {//顶点节点
char data; //节点数据
struct _EdgeNode* first;//指向邻接第一条边
}VertexNode, AdjList;
typedef struct _AdjListGraph {
AdjList* adjlist;
int vex; //顶点数
int edge; //边数
}AdjListGraph;
bool visited[MaxSize]; //Global array to record whether the node is being visited
/*图的初始化*/
void Init(AdjListGraph& G) {
G.adjlist = new AdjList[MaxSize];
G.edge = 0;
G.vex = 0;
for (int i = 0; i < MaxSize; i++)
{
visited[i] = false;
}
}
int Location(AdjListGraph& G, char c);
void Create(AdjListGraph& G) {
cout << "Please enter the graph algorithm vertices and node number" << endl;
cin >> G.vex >> G.edge;
cout << "Please enter the vertices node data" << endl;
for (int i = 0; i < G.vex; i++) {
cin >> G.adjlist[i].data;
G.adjlist[i].first = NULL;
}
char v1 = 0, v2 = 0;//保存输入的顶点的字符
int i1, i2; //保存顶点在数组中的下标
int weight;
cout << "Please enter the related vertices data to connect them:" << endl;
for (int i = 0; i < G.edge; i++) {
cin >> v1 >> v2>>weight;
i1 = Location(G, v1);
i2 = Location(G, v2);
if (i1 != -1 && i2 != -1) {//寻找到位置
EdgeNode* temp = new EdgeNode;
temp->adjvex = i2;
temp->next = G.adjlist[i1].first;
temp->weight = weight;
G.adjlist[i1].first = temp;
}
}
}
/*通过顶点对应的字符寻找顶点在图中的邻接点*/
int Location(AdjListGraph& G, char c) {
for (int i = 0; i < G.vex; i++) {
if (G.adjlist[i].data == c) {
return i;
}
}
return -1;
}
int min_weights = 0x7FFFFFFF;
int steps = 0;
int path[MaxSize] = { 0 };
int shortest_path[MaxSize] = {0}; //Save the shortest path
//According the graph to do the deep visit function
void DFS(AdjListGraph& G, int start, int end, int weights)
{
int cur = -1;
if (start == end)
{
for (int i = 0; i < steps; i++)
{
cout << G.adjlist[path[i]].data << " "; //Print the posible path
}
cout << "The posible path length is : " << weights;
if (min_weights > weights)
{
min_weights = weights;
memcpy(shortest_path, path, steps*sizeof(int));
}
return;
}
visited[start] = true;
EdgeNode* temp = G.adjlist[start].first;
while (temp)
{
int weight = temp->weight;
cur = temp->adjvex;
if (visited[cur] == false)
{
visited[cur] = true;
path[steps++] = cur;
DFS(G, cur, end, weights + weight);
visited[cur] = false;
path[--steps] = 0;
}
temp = temp->next;
}
}
void BFS(AdjListGraph& G, int v)
{
queue<int> q;
int cur = -1;
int index =-1;
q.push(v);
while (!q.empty())
{
cur = q.front();
if (visited[cur] == false)
{
cout << G.adjlist[cur].data << " ";
visited[cur] = true;
}
q.pop();
EdgeNode* temp = G.adjlist[cur].first;
while (temp != NULL)
{
index = temp->adjvex;
temp = temp->next;
q.push(index);
}
}
}
void BFS_Main(AdjListGraph& G)
{
for (int i = 0; i < G.vex; i++)
{
if (visited[i] == false)
{
BFS(G, i);
}
}
}
int main()
{
//Initiallized the graph
AdjListGraph G;
Init(G);
Create(G);
char start, end;
cout << "Please enter the minumum path for the starting point and ending point" << endl;
cin >> start >> end;
DFS(G,Location(G,start),Location(G,end),0);
cout << "The shortest path length is" << min_weights << endl;
cout << "Path : ";
int i = 0;
while (i < MaxSize && shortest_path[i]>0)
{
cout << G.adjlist[shortest_path[i]].data << " ";
i++;
}
cout << endl;
system("pause");
return 0;
}