DS第6章学习小结

一、图

(一)     数学语言:G= (V, E)   G:graph  V:vertex  E:edge

(二)     基本术语:

  1. 无向完全图vs有向完全图

    完全图:两两相连

    顶点vex

    边edge

    弧arc

    无向完全图

    n

    n(n-1) /2

    /

    有向完全图

    n

    /

    n(n-1)

  2. 权&网——带权图称为网 
  3. 度、入度、出度——度(degree: the amount) 入度(in-degree) 出度(out-degree)——与顶点关联
  4. 连通、(强)连通图、(强)连通分量

    连通

    有路径

    连通图*区别完全图

    无向图中任两个顶点连通

    连通分量

    无向图中的极大连通子图;连通图的连通分量是其本身

    强-

    有向

(三)     存储结构:     

  • 邻接矩阵【二维数组】

           1)      存储方法AM

                     a)      一维数组:顶点信息

                     b)      二维数组:邻接关系          注:若有向图,出度看行,入度看列,度=行+列

           2)   

1 #define MaxVnum 100  //顶点数最大值
2 typedef char VexType;  //顶点的数据类型,根据需要定义
3 typedef int EdgeType;  //边上权值的数据类型,若不带权值的图,则为0或1
4 typedef struct{
5   VexType Vex[MaxVnum];
6   EdgeType Edge[MaxVnum][MaxVnum];
7   int vexnum,edgenum; //顶点数,边数
8 }AMGragh;
定义AMGraph
  • 边集数组

           1)      存储方法e[N*N]:通过数组存储每条边的起点和终点。如果是网,则增加一个权值域。

           2)    

1 struct Edge {
2     int u;
3     int v;
4     int w;
5 }e[N*N];
定义e[N*N]
  • 邻接表【链式存储】

           1)      存储方法AL

                     a)      顶点:顶点信息、指向第一个邻接点的指针

                     b)      邻接点:邻接点下标、指向下一个邻接点的指针

           2)    

 1 const int MaxVnum=100;//顶点数最大值
 2 
 3 typedef char VexType;//顶点的数据类型为字符型
 4 typedef struct AdjNode{ //定义邻接点类型
 5     int v; //邻接点下标
 6     struct AdjNode *next; //指向下一个邻接点
 7 }AdjNode;
 8 
 9 typedef struct VexNode{ //定义顶点类型
10     VexType data; // VexType为顶点的数据类型,根据需要定义
11     AdjNode *first; //指向第一个邻接点
12 }VexNode;
13 
14 typedef struct{//定义邻接表类型
15     VexNode Vex[MaxVnum];
16     int vexnum,edgenum; //顶点数,边数
17 }ALGragh;
定义ALGraph

二、图的应用

(一)     DFS(深度优先搜索)

有点类似于树的前序遍历,即从一个选定的点出发,选定与其直接相连且未被访问过的点走过去,然后再从这个当前点,找与其直接相连且未被访问过的点访问,每次访问的点都标记为“已访问”,就这么一条道走到黑,直到没法再走为止。无路可走时就从当前点原路返回,看是否存在与这个点直接相连且未被访问的点。重复上述步骤,直到没有未被访问的点为止。

基于上述,DFS很适合使用递归,其代码如下:

 1 #define MaxVnum 100  //顶点数最大值
 2 bool visited[MaxVnum];  //访问标志数组,其初值为"false"
 3 void DFS_AM(AMGragh G,int v)//基于邻接矩阵的深度优先遍历
 4 {
 5     int w;
 6     cout<<G.Vex[v]<<"\t";
 7     visited[v]=true;
 8     for(w=0;w<G.vexnum;w++)//依次检查v的所有邻接点
 9         if(G.Edge[v][w]&&!visited[w])//v、w邻接而且w未被访问
10             DFS_AM(G,w);//从w顶点开始递归深度优先遍历
11 }
DFS_AM
 1 #define MaxVnum 100;//顶点数最大值
 2 bool visited[MaxVnum];  //访问标志数组,其初值为"false"
 3 void DFS_AL(ALGragh G,int v)//基于邻接表的深度优先遍历
 4 {
 5     int w;
 6     AdjNode *p;
 7     cout<<G.Vex[v].data<<"\t";
 8     visited[v]=true;
 9     p=G.Vex[v].first;
10     while(p)//依次检查v的所有邻接点
11     {
12         w=p->v;//w为v的邻接点
13         if(!visited[w])//w未被访问
14             DFS_AL(G,w);//从w出发,递归深度优先遍历
15         p=p->next;
16     }
17 }
18 
19 void DFS_AL(ALGragh G)//非连通图,基于邻接表的深度优先遍历
20 {
21     for(int i=0;i<G.vexnum;i++)//非连通图需要查漏点,检查未被访问的顶点
22         if(!visited[i])//i未被访问,以i为起点再次广度优先遍历
23             DFS_AL(G,i);
24 }
DFS_AL

(二)     BFS(广度优先搜索)

有点类似于树的层序遍历,即从一个选定的点出发,将与其直接相连的点都收入囊中,然后依次对这些点去收与其直接相连的点。重复到所有点都被访问然后结束。

基于上述,BFS可以通过一个队列来实现,其代码如下:

 1 #define MaxVnum 100  //顶点数最大值
 2 bool visited[MaxVnum];  //访问标志数组,其初值为"false"
 3 void BFS_AM(AMGragh G,int v)//基于邻接矩阵的广度优先遍历
 4 {
 5     int u,w;
 6     queue<int>Q; //创建一个普通队列(先进先出),里面存放int类型
 7     cout<<G.Vex[v]<<"\t";
 8     visited[v]=true;
 9     Q.push(v); //源点v入队
10     while(!Q.empty()) //如果队列不空
11     {
12         u=Q.front();//取出队头元素赋值给u
13         Q.pop(); //队头元素出队
14         for(w=0;w<G.vexnum;w++)//依次检查u的所有邻接点
15         {
16             if(G.Edge[u][w]&&!visited[w])//u、w邻接而且w未被访问
17             {
18                cout<<G.Vex[w]<<"\t";
19                visited[w]=true;
20                Q.push(w);
21             }
22         }
23     }
24 }
BFS_AM
 1 #define MaxVnum 100;//顶点数最大值
 2 bool visited[MaxVnum];//访问标志数组,其初值为"false"
 3 void BFS_AL(ALGragh G,int v)//基于邻接表的广度优先遍历
 4 {
 5     int u,w;
 6     AdjNode *p;
 7     queue<int>Q; //创建一个普通队列(先进先出),里面存放int类型
 8     cout<<G.Vex[v].data<<"\t";
 9     visited[v]=true;
10     Q.push(v); //源点v入队
11     while(!Q.empty()) //如果队列不空
12     {
13         u=Q.front();//取出队头元素赋值给u
14         Q.pop(); //队头元素出队
15         p=G.Vex[u].first;
16         while(p)//依次检查u的所有邻接点
17         {
18             w=p->v;//w为u的邻接点
19             if(!visited[w])//w未被访问
20             {
21                cout<<G.Vex[w].data<<"\t";
22                visited[w]=true;
23                Q.push(w);
24             }
25             p=p->next;
26         }
27     }
28 }
29 
30 void BFS_AL(ALGragh G)//非连通图,基于邻接表的广度优先遍历
31 {
32     for(int i=0;i<G.vexnum;i++)//非连通图需要查漏点,检查未被访问的顶点
33         if(!visited[i])//i未被访问,以i为起点再次广度优先遍历
34                BFS_AL(G,i);
35 }
BFS_AL

 

遍历方式

存储结构

空间复杂度

时间复杂度

DFS

AM

O(V)

O(V2)

AL

O(V+E)

BFS

AM

O(V2)

AL

O(V+E)

(三)     最短路径【Dijkstra算法】

  • 单源最短路径问题:给定起点(源点),求到任意终点的最短路径
  • 多源最短路径问题:起点(源点)不确定,求任意两个顶点的最短路径
  • 对有权图而言,即是找权重和最小的路径
  • 对无权图而言,即是找边最少的路径

Dijkstra算法(注:不适用于存在负值圈的图)有点类似于DFS算法,从源点开始将顶点一个一个往集合S里收(原则:在候选集合V-S里寻找离源点最近的点),保证从源点到该顶点“i”的当前最短路径是确定的,然后更新与“i”直接相连的点的最短路径。

 1 const int INF=1e7; // 无穷大10000000
 2 int dist[MaxVnum],p[MaxVnum];//最短距离和前驱数组
 3 bool flag[MaxVnum]; //如果s[i]等于true,说明顶点i已经加入到集合S;否则顶点i属于集合V-S
 4 
 5 void Dijkstra(AMGragh G,int u)
 6 {
 7     for(int i=0;i<G.vexnum;i++)
 8     {
 9         dist[i]=G.Edge[u][i]; //初始化源点u到其他各个顶点的最短路径长度
10         flag[i]=false;
11         if(dist[i]==INF)
12             p[i]=-1; //源点u到该顶点的路径长度为无穷大,说明顶点i与源点u不相邻
13         else
14             p[i]=u; //说明顶点i与源点u相邻,设置顶点i的前驱p[i]=u
15     }
16     dist[u]=0;
17     flag[u]=true;   //初始时,集合S中只有一个元素:源点u
18     for(int i=0;i<G.vexnum;i++)
19     {
20         int temp=INF,t=u;
21         for(int j=0;j<G.vexnum;j++) //在集合V-S中寻找距离源点u最近的顶点t
22             if(!flag[j]&&dist[j]<temp)
23             {
24                 t=j;
25                 temp=dist[j];
26             }
27         if(t==u) return ; //找不到t,跳出循环
28         flag[t]=true;  //否则,将t加入集合
29         for(int j=0;j<G.vexnum;j++)//更新与t相邻接的顶点到源点u的距离
30             if(!flag[j]&&G.Edge[t][j]<INF)
31                 if(dist[j]>(dist[t]+G.Edge[t][j]))
32                 {
33                     dist[j]=dist[t]+G.Edge[t][j];
34                     p[j]=t;
35                 }
36        }
37 }
Dijkstra

(四)     最小生成树【生成树:所有顶点均由边连接在一起,但不存在回路的图

  • 生成树的顶点数=图的顶点数
  • 生成树是图的极小连通子图
  • n个顶点的连通图的生成树有n-1条边
  1. Prim算法:以点为对象,寻找与点相连的最短边来构成最小生成树。
     1 const int INF=1e7; // 无穷大10000000
     2 const int N=100;
     3 bool s[N];
     4 int c[N][N],closest[N],lowcost[N];
     5 void Prim(int n, int u0, int c[N][N])
     6 {    //顶点个数n、开始顶点u0、带权邻接矩阵C[n][n]
     7     //如果s[i]=true,说明顶点i已加入最小生成树
     8     //的顶点集合U;否则顶点i属于集合V-U
     9     //将最后的相关的最小权值传递到数组lowcost
    10     s[u0]=true; //初始时,集合中U只有一个元素,即顶点u0
    11     int i,j;
    12     for(i=1;i<=n;i++)
    13     {
    14         if(i!=u0)
    15         {
    16             lowcost[i]=c[u0][i];
    17             closest[i]=u0;
    18             s[i]=false;
    19         }
    20         else
    21             lowcost[i]=0;
    22     }
    23     for(i=1;i<=n;i++) //在集合中V-u中寻找距离集合U最近的顶点t
    24     {
    25         int temp=INF;
    26         int t=u0;
    27         for(j=1;j<=n;j++)
    28         {
    29             if((!s[j])&&(lowcost[j]<temp))
    30             {
    31                 t=j;
    32                 temp=lowcost[j];
    33             }
    34         }
    35         if(t==u0)
    36             break;       //找不到t,跳出循环
    37         s[t]=true;     //否则,讲t加入集合U
    38         for(j=1;j<=n;j++) //更新lowcost和closest
    39         {
    40             if((!s[j])&&(c[t][j]<lowcost[j]))
    41             {
    42                 lowcost[j]=c[t][j];
    43                 closest[j]=t;
    44             }
    45         }
    46     }
    47 }
    Prim
  2. Kruskal算法:以边为对象,不断加入新的不构成回路的最短边来构成最小生成树。
posted @ 2020-06-14 17:01  海底捞不动  阅读(202)  评论(0编辑  收藏  举报