Fork me on GitHub

数据结构与算法——图(邻接表)及深度遍历、广度遍历

1. 图是什么

从公司回家有三条路线,考虑路况和安全性怎么选择?不考虑安全和路况的话又怎么选择?

 

 

 

在计算机科学中,一个图就是一些顶点的集合,这些顶点通过一系列边结对(连接)。顶点用圆圈表示,边就 是这些圆圈之间的连线。顶点之间通过边连接。注意:顶点有时也称为节点或者交点,边有时也称为链接。 社交网络,每一个人就是一个顶点,互相认识的人之间通过边联系在一起, 边表示彼此的关系。这种关系可以 是单向的,也可以是双向的!同时,边可以是双向的,也可以是单向的!

 地图导航 - 起点、终点和分叉口(包括十字路口·、T 字路口等)都是顶点,导航经过的两顶点的路径就是边!

如上图所示,我们导航从一个点到另外一个点可以有条路径,路径不同,路况就不同,拥堵程度不同,所以导 致不同路径所花的时间也不一样,这种不同我们可以使用边的权重来表示,即根据每条边的实际情况给每一条 边分配一个正数或者负数值。考虑如下机票图,各个城市就是顶点,航线就是边。那么权重就是机票价格。

 

 

 树和链表也都是图的特例!

 

 

 如果我们有一个编程问题可以通过顶点和边表示,那么我们就可以将问题用图画出来,然后使用相应的图 算法来找到解决方案。

 

 

2. 图的表示

邻接列表

 在邻接列表实现中,每一个顶点会存储一个从它这里开始的相邻边的列表。比如,如果顶点 B 有一条边到 A、 C 和 E,那么 B 的列表中会有 3 条边

 

邻接列表只描述指向外部的边。B 有一条边到 A,但是 A 没有边到 B8,97所9以43B84没0有1出11现1在 A 的邻接列表中。 查找两个顶点之间的边或者权重会比较费时,因为遍历邻接列表直到找到为止。

 

邻接矩阵

由二维数组对应的行和列都表示顶点,由两个顶点所决定的矩阵对应元素数值表示这里两个顶点是否相连(如, 0 表示不相连,非 0 表示相连和权值)、如果相连这个值表示的是相连边的权重。例如,广西到北京的机票, 我们用邻接矩阵表示:

 

往这个图中添加顶点的成本非常昂贵,因为新的矩阵结果必须重新按照新的行/列创建,然后将已有的数据复制 到新的矩阵中。

 

邻接列表与邻接矩阵如何选择?看下表:

大多数时候,选择邻接列表是正确的。(在图比较稀疏的情况下,每一个顶点都只会和少数几个顶点相 连,这种情况下邻接列表是最佳选择。如果这个图比较密集,每一个顶点都和大多数其他顶点相连,那么邻接 矩阵更合适。)

注意: V 表示图中顶点的个数,E 表示边的个数。

 

3. 图的算法实现

 

邻接表结构的定义

 1 #define MaxSize 1024
 2 
 3 //与节点连接的边的定义
 4 typedef struct _EdgeNode 
 5 {
 6     int adjvex;                    //邻接的顶点
 7     int weight;                    //权重
 8     struct _EdgeNode *next;     //下一条边
 9 }EdgeNode;
10 
11 //顶点节点
12 typedef struct _VertexNode
13 {
14     char data;                    //节点数据
15     struct _EdgeNode *first;    //指向邻接第一条边
16 }VertexNode, AdjList;
17 
18 typedef struct _AdjListGraph 
19 {
20     AdjList *adjlist;
21     int vex;                    //顶点数
22     int edge;                    //边数
23 }AdjListGraph;

 

 

邻接表的初始化

1 /*图的初始化*/
2 void Init(AdjListGraph &G)
3 {
4     G.adjlist = new AdjList[MaxSize];
5     G.edge = 0;
6     G.vex = 0;
7 }

 

 

邻接表的创建

 1 /*图的创建*/
 2 void Create(AdjListGraph &G)
 3 {
 4     cout << "请输入该图的顶点数以及边数:" << endl;
 5     cin>> G.vex >> G.edge;
 6     cout<<"请输入相关顶点:"<< endl;
 7     for(int i=0; i<G.vex; i++)
 8     {
 9         cin >> G.adjlist[i].data;
10         G.adjlist[i].first = NULL;
11     }
12     char v1=0, v2=0;                            //保存输入的顶点的字符
13     int i1, i2;                                    //保存顶点在数组中的下标
14     cout<<"请输入想关联边的顶点:"<< endl;
15     
16     for(int i=0; i<G.edge; i++)
17     {
18         cin >>v1 >>v2;
19         i1 = Location(G, v1);
20         i2 = Location(G, v2);
21         
22         if(i1!=-1 && i2!=-1)        //寻找到位置
23         {
24             EdgeNode *temp = new EdgeNode;
25             temp->adjvex = i2;
26             temp->next = G.adjlist[i1].first;
27             G.adjlist[i1].first = temp;
28         }
29     }
30 }
31 
32 /*通过顶点对应的字符寻找顶点在图中的邻接点*/
33 int Location(AdjListGraph &G, char c)
34 {
35     for(int i=0; i<G.vex; i++)
36     {
37         if(G.adjlist[i].data == c) 
38         {
39             return i;
40         }
41     }
42     return -1;
43 }

 

4.邻接表的深度遍历

深度优先遍历思想:

  • 首先以一个未被访问过的顶点作为起始顶点,沿当前顶点的边走到未访问过的顶点;
  • 当没有未访问过的顶点时,则回到上一个顶点,继续试探别的顶点,直到所有的顶点都被访问过。

 

使用深度优先搜索来遍历这个图的具体过程是:

1. 首先从一个未走到过的顶点作为起始顶点,比如 A 顶点作为起点。

2. 沿 A 顶点的边去尝试访问其它未走到过的顶点,首先发现 E 号顶点还没有走到过,于是访问 E 顶点。

3. 再以 E 顶点作为出发点继续尝试访问其它未走到过的顶点,接下来访问 D 顶点。

4. 再尝试以 D 顶点作为出发点继续尝试访问其它未走到过的顶点。

5. 但是,此时沿 D 顶点的边,已经不能访问到其它未走到过的顶点,接下来返回到 E 顶点。

6. 返回到 E 顶点后,发现沿 E 顶点的边也不能再访问到其它未走到过的顶点。此时又回到顶点 A(D->E->A),再以 A 顶点作为出发点继续访问其它未走到过的顶点,于是接下来访问 C 顶点。

7. 。。。。。。。。。。

8. 最终访问的结果是 A -> E -> D -> C -> B

  1 #include <iostream>
  2 
  3 using namespace std;
  4 
  5 #define MaxSize 1024
  6 
  7 //与节点连接的边的定义
  8 typedef struct _EdgeNode 
  9 {
 10     int adjvex;                    //邻接的顶点
 11     int weight;                    //权重
 12     struct _EdgeNode *next;        //下一条边
 13 }EdgeNode;
 14 
 15 //顶点节点
 16 typedef struct _VertexNode
 17 {
 18     char data;                    //节点数据
 19     struct _EdgeNode *first;    //指向邻接第一条边
 20 }VertexNode, AdjList;
 21 
 22 typedef struct _AdjListGraph 
 23 {
 24     AdjList *adjlist;
 25     int vex;                    //顶点数
 26     int edge;                    //边数
 27 }AdjListGraph;
 28 
 29 bool visited[MaxSize];            //全局数组,用来记录节点是否已被访问
 30 
 31 int Location(AdjListGraph &G, char c);
 32 
 33 /*图的初始化*/
 34 void Init(AdjListGraph &G)
 35 {
 36     G.adjlist = new AdjList[MaxSize];
 37     G.edge = 0;
 38     G.vex = 0;
 39     for (int i = 0; i < MaxSize; i++) 
 40     {
 41         visited[i] = false;
 42     }
 43 }
 44 
 45 /*图的创建*/
 46 void Create(AdjListGraph &G)
 47 {
 48     cout << "请输入该图的顶点数以及边数:" << endl;
 49     cin>> G.vex >> G.edge;
 50     cout<<"请输入相关顶点:"<< endl;
 51     for(int i=0; i<G.vex; i++)
 52     {
 53         cin >> G.adjlist[i].data;
 54         G.adjlist[i].first = NULL;
 55     }
 56     
 57     char v1=0, v2=0;//保存输入的顶点的字符
 58     int i1, i2; //保存顶点在数组中的下标
 59     cout<<"请输入想关联边的顶点:"<< endl;
 60     for(int i=0; i<G.edge; i++)
 61     {
 62         cin >>v1 >>v2;
 63         i1 = Location(G, v1);
 64         i2 = Location(G, v2);
 65         
 66         //寻找到位置
 67         if(i1!=-1 && i2!=-1)
 68         {
 69             EdgeNode *temp = new EdgeNode;
 70             temp->adjvex = i2;
 71             temp->next = G.adjlist[i1].first;
 72             G.adjlist[i1].first = temp;
 73         }
 74     }
 75 }
 76 
 77 /*通过顶点对应的字符寻找顶点在图中的邻接点*/
 78 int Location(AdjListGraph &G, char c)
 79 {
 80     for(int i=0; i<G.vex; i++)
 81     {
 82         if(G.adjlist[i].data == c) 
 83         {
 84             return i;
 85         }
 86     }
 87     return -1;
 88 }
 89 
 90 /*对图上的顶点进行深度遍历*/
 91 void DFS(AdjListGraph &G,int v)
 92 {
 93     int next = -1;
 94     if(visited[v]) return;
 95     cout<<G.adjlist[v].data<<" ";
 96     visited[v] = true;                //设置为已访问
 97     EdgeNode *temp = G.adjlist[v].first;
 98     
 99     while(temp)
100     {
101         next = temp->adjvex;
102         temp = temp->next;
103         if(visited[next] == false)
104         {
105             DFS(G, next);
106         }
107     }
108 }
109 
110 /*对所有顶点进行深度遍历*/
111 void DFS_Main(AdjListGraph &G)
112 {
113     for(int i=0; i<G.vex; i++)
114     {
115         if(visited[i] == false)
116         {
117             DFS(G, i);
118         }
119     }
120 }
121 
122 int main()
123 {
124     AdjListGraph G;
125     //初始化
126     Init(G);
127     //创建图
128     Create(G);
129     //深度遍历
130     DFS_Main(G);
131     system("pause");
132 }

 

 

5.邻接表的广度遍历

广度优先遍历思想 

  • 首先以一个未被访问过的顶点作为起始顶点,访问其所有相邻的顶点;
  • 然后对每个相邻的顶点,再访问它们相邻的未被访问过的顶点,直到所有顶点都被访问过,遍历结束

 

 

 1 /*对图上的顶点进行广度遍历*/
 2 void BFS(AdjListGraph &G,int v) 
 3 {
 4     queue <int> q;
 5     int cur;
 6     int next;
 7     q.push(v);
 8     
 9     while (!q.empty())                                //队列非空
10     {
11         cur = q.front();                            //取队头元素
12         
13         //当前顶点还没有被访问
14         if (visited[cur] == false) 
15         { 
16             cout << G.adjlist[cur].data << " ";
17             visited[cur] = true;                    //设置为已访问
18         }
19         
20         q.pop(); //出队列
21         EdgeNode *tp = G.adjlist[cur].first;
22         while (tp != NULL) 
23         {
24             next = tp->adjvex;
25             tp = tp->next;
26             q.push(next);                          //将第一个邻接点入队
27         }
28     }
29 }
30 
31 /*对所有顶点进行广度遍历*/
32 void BFS_Main(AdjListGraph &G) 
33 {
34     for (int i = 0; i < G.vex; i++) 
35     {
36         if (visited[i] == false) 
37         {
38             BFS(G,i);
39         }
40     }
41 }

 

 

6.图的导航-最短路径算法

从起点开始访问所有路径,则到达终点节点的路径有多条,其中路径权值最短的一条则为最短路径。最短路径算法有 深度优先遍历、广度优先遍历、Bellman-Ford 算法、弗洛伊德算法、 SPFA(Shortest Path Faster Algorithm)算法和迪 杰斯特拉算法等。

 

源码实现

  1 #include <iostream>
  2 
  3 using namespace std;
  4 
  5 #define MaxSize 1024
  6 
  7 //与节点连接的边的定义
  8 typedef struct _EdgeNode 
  9 {
 10     int adjvex;                //邻接的顶点
 11     int weight;                //权重
 12     struct _EdgeNode *next; //下一条边
 13 }EdgeNode;
 14 
 15 //顶点节点
 16 typedef struct _VertexNode
 17 {
 18     char data;                    //节点数据
 19     struct _EdgeNode *first;    //指向邻接第一条边
 20 }VertexNode, AdjList;
 21 
 22 typedef struct _AdjListGraph 
 23 {
 24     AdjList *adjlist;
 25     int vex;                    //顶点数
 26     int edge;                    //边数
 27 }AdjListGraph;
 28 
 29 bool visited[MaxSize]={0};        //全局数组,用来记录节点是否已被访问
 30 
 31 int Location(AdjListGraph &G, char c);
 32 
 33 /*图的初始化*/
 34 void Init(AdjListGraph &G)
 35 {
 36     G.adjlist = new AdjList[MaxSize];
 37     G.edge = 0;
 38     G.vex = 0;
 39     for (int i = 0; i < MaxSize; i++) 
 40     {
 41         visited[i] = false;
 42     }
 43 }
 44 
 45 /*图的创建*/
 46 void Create(AdjListGraph &G)
 47 {
 48     cout << "请输入该图的顶点数以及边数:" << endl;
 49     cin>> G.vex >> G.edge;
 50     cout<<"请输入相关顶点:"<< endl;
 51     
 52     for(int i=0; i<G.vex; i++)
 53     {
 54         cin >> G.adjlist[i].data;
 55         G.adjlist[i].first = NULL;
 56     }
 57     
 58     char v1 = 0, v2 = 0;//保存输入的顶点的字符
 59     int i1, i2; //保存顶点在数组中的下标
 60     int weight = 0;
 61     cout<<"请输入想关联边的顶点及权重:"<< endl;
 62     
 63     for(int i=0; i<G.edge; i++)
 64     {
 65         cin >>v1 >>v2 >>weight;
 66         i1 = Location(G, v1);
 67         i2 = Location(G, v2);
 68         
 69         //寻找到位置
 70         if(i1!=-1 && i2!=-1)
 71         {
 72             EdgeNode *temp = new EdgeNode;
 73             temp->adjvex = i2;
 74             temp->next = G.adjlist[i1].first;
 75             temp->weight = weight;
 76             G.adjlist[i1].first = temp;
 77         }
 78     }
 79 }
 80 
 81 /*通过顶点对应的字符寻找顶点在图中的邻接点*/
 82 int Location(AdjListGraph &G, char c)
 83 {
 84     for(int i=0; i<G.vex; i++)
 85     {
 86         if(G.adjlist[i].data == c) 
 87         {
 88             return i;
 89         }
 90     }
 91     return -1;
 92 }
 93 
 94 int min_weights = 0x7FFFFFFF;         //最大的整数 2 的 32 次方-1
 95 int steps = 0;
 96 int path[MaxSize] = {0};            //保存走过的路径
 97 int shortest_path[MaxSize] = {0};    //保存最短的路径
 98 
 99 /*对图上的顶点进行深度遍历*/
100 void DFS(AdjListGraph &G,int start, int end, int weights)
101 {
102     int cur = -1;
103     //if(visited[start]) return;
104     cur = start;
105     
106     //已找到终点,不用继续遍历
107     if(cur == end)
108     {
109         //打印所经过的所有路径
110         for (int i = 0; i < steps; i++) /// 输出所有可能的路径
111         {
112             cout<<G.adjlist[path[i]].data<<" ";         //输出路径
113         }
114         
115         printf("\t\t 该路径对应的长度是:%d\n", weights);//输出对应路径长度
116         
117         if (min_weights > weights) //更新最小路径
118         {
119             min_weights = weights;
120             memcpy(shortest_path, path, steps*sizeof(int));
121         }
122     }
123     
124     visited[start] = true;        //设置为已访问
125     EdgeNode *temp = G.adjlist[start].first;
126     
127     while(temp)
128     {
129         int weight = temp->weight;
130         cur = temp->adjvex;
131         if(visited[cur] == false)
132         {
133             visited[cur] = true;                //标记城市 i 已经在路径中
134             path[steps++] = cur;                //保存路径到 path 数组中
135             DFS(G, cur, end, weights + weight);
136             visited[cur] = false;                //之前一步探索完毕后,取消对城市 i 的标记以便另一条路径选择顶点
137             path[--steps] = 0;
138         }
139         temp = temp->next;
140     }
141 }
142 
143 int main()
144 {
145     AdjListGraph G;
146     
147     //初始化
148     Init(G);
149     
150     //创建图
151     Create(G);
152     
153     char start, end;
154     cout<<"请输入要查询的最短路径的起点和终点:"<<endl;
155     cin >>start >>end;
156     
157     //求两点间的最短路径
158     DFS(G, Location(G, start), Location(G, end), 0);
159     cout<<"最小路径长度为:"<<min_weights<<endl;
160     cout<<"路径:";
161     int i=0;
162     
163     while(i<MaxSize && shortest_path[i]>0)
164     {
165         cout<<G.adjlist[shortest_path[i]].data<<" ";
166         i++;
167     }
168     cout<<endl;
169     
170     system("pause");
171 }

 

posted @ 2020-12-07 22:36  索智源  阅读(1347)  评论(0编辑  收藏  举报