数据结构与算法——图的基本概念

图的基本概念

图(graph)是由顶点的有穷非空集合和顶点之间边的集合组成,通常表示为:G(V,E),其中,G表示一个图,V是图G中顶点的集合,E是图G中边的集合。

  • 图中的数据称为顶点,有别于线性表的元素,树中的结点;
  • 在图结构中,不允许没有顶点,v为有穷非空;
  • 在图中任意两个顶点之间都可能有关系,顶点之间的逻辑关系用 边 来表示;边集可以是空集。

图的相关定义

无向边:若顶点v1和v2之间的边没有方向,则称这条边为无向边(Edge),用无序偶对(v0,v1)来表示。

无向图:图中任意两个顶点之间的边都是无向边;

有向边:若顶点从v0到v1的边有方向,则称这条边为有向边,也成为弧,用有序偶<v0,v1>来表示,v0为弧尾,v1为弧头;

有向图:图中任意两个顶点之间的边都是有向边

简单图:在图中不存在顶点到其自身的边,且同一条边不重复出现,则称这样的图为简单图

无向完全图:在无向图中,如何任意两个顶点之间都存在边,则称为无向完全图

有向完全图:在有向图中,如果任意两个顶点之间都存在方向互为相反的两条弧,则称该图为有向完全图

权:与图的边或弧相关的数叫做权

网:带权的图称为网

路径:图中顶点间存在路径,两个顶点存在路径则说明是连同的

环:如果路径最终回到起始点则称为环

简单路径:两个顶点间的连通不存在重复的路径

连通图:任意两个顶点都是连通的图

连通分量:无向图中的极大连通子图

强连通图:任意两个顶点都是连通的有向图

强连通分量:有向图中的极大连通子图称做有向图的强连通分量

连通图的生成树:一个连通图的生成树是一个极小的连通子图,它含有图中全部的n个顶点,但只有足以构成一棵树的n-1条边。

有向树:如果一个有向图恰有一个顶点的入度为0,其余顶点的入度均为1,则是一颗有向树

有向图的生成森林:由若干棵有向树组成,含有图中全部顶点,但只有足以构成若个棵不相交的有向树的弧。

图的存储结构

邻接矩阵

图的邻接矩阵存储方式是用两个数组来表示图;一个一维数组存储图中顶点信息,一个二维数组储存图中的边或弧的信息。

1. 无向图

2. 有向图

3. 有向网图

邻接表

上边的邻接矩阵是个不错的图存储结构,简单直接一目了然,但是如果边的数目比较少的时候,比如极端的情况下有N个顶点,但是只有一条边或者弧,那么边数组就会产生极大的浪费,我们只需要记录一条边或者弧却要使用一个N*N的二位数组来表示;面对这种情况,很容易想到用链表来代替上面的二维数组,这就是邻接表。

1. 无向图的邻接表结构(adjvex是顶点的下表)

2. 有向图的邻接表结构

在有向图的邻接表结构中,我们需要用两种表(邻接表和逆邻接表)才能完全的表示一个完整的有向图,这个还是挺麻烦的,不过后面会有另外的方式来解决这个问题。

3. 带权的网图邻接表结构

没啥好说的,就是在边链表Node中添加一个成员来表示权。

4. 来看下邻接表的数据结构如何表示:

#define NUM x
typedef T_v  VertexType; //顶点类型
typedef T_w  WeightType;//权的数据类型
//边表的结点
typedef struct EdgeNode
{
       int adjvex;//顶点下表
       WeightType weight;//权值
       struct EdgeNode* next;//下一个边表结点
}EdgeNode;

//顶点表结点
typedef struct VertexNode
{
       VertexType  data;
       EdgeNode* firstedge;
}VertexNode,AdjList[NUM];

typedef struct
{
       AdjList adjList;
       int numVertexes,numEdges; 
}GraphAdjList;

边集数组

边集数组是由两个一维数组构成;一个是存储顶点的信息;另一个是存储边的信息,这个边数组每个数据元素由一条边的起点下标、终点下标和权组成。

边数组元素的结构:

        begin:起点的下标
               end:终点的下标
               weight:权 值

图的遍历

1. 深度优先遍历

他从图中的某个顶点v出发,访问此顶点,然后从v的未访问的邻接点再出发进行访问,直至所有的顶点全部被访问。

a. 对用邻接矩阵表示的图进行遍历(主要理解其思想):

#define NUM 5
boolean visited[NUM];
//邻接矩阵的深度优先递归算法
void DFS(MGraph G,int i)
{
       int j;
       visited[i] = true;//设置该顶点为已访问
       printf("%c",G.vexs[i]);
       //遍历边数组的i行
       for(j = 0; j < G.numVertexes; j++)
       {
            if( G.arc[i][j] == 1 && !visited[j] )//arc[i][j] 为1 表示 i  j,是一个边或者弧 
                   DFS( G, j );//遍历该顶点
       }
}

void DFSTraverse(MGraph G)
{
      int i;
      for(i = 0; i < numVertexes;i++)
             visited[i] = false;
      for(i = 0; i < G.numVertexes; i++)
             if(!visited[i])//对未访问过的顶点调用DFS,这个可以有效处理非连通图的问题
                  DFS(G,i);
}

b. 对用邻接表存储的图遍历:

void DFS(GraphAdjList GL, int i)
{
       EdgeNode *p;
       visited[i] = true;
       printf("%c",GL->adjList[i].data);
       p = GL->sdjList[i].firstedge;//p指向该顶点的边数组
       while(p)
       {
               if( !visited[p->adjvex] )//判断边数组中的顶点是否被遍历过,没有的话遍历
                     DFS( GL, p->adjvex );
               p = p->next;//指向边数组的下一个元素
       }
}

void DFSTraverse(GraphAdjList GL)
{
        int i;
        for(i = 0; i < GL->numVertexes;i++)
             visited[i] = false;
        for(i = 0; i < GL->numVertexes;i++)
             if(!visited[i])
                 DFS(GL,i);
}

2. 广度优先遍历

看图理解:

1. 从任一点开始(这里是A),对应下图中的第一行只有A
2. A的下一个顶点有两个  B 、F ,对应第二行 A出 B、F进
3. B的下一个顶点有三个:C、I、G,对应第三行 B 出,C、I、G进
4. ...以此类推,直到H出;
5. 综上得出遍历的顺序为:A、B、F、C、I、G、E、D、H

a. 邻接矩阵结构的广度优先遍历:

void BFSTraverse(MGraph G)
{
       int i,j;
       Queue Q;
       for(i = 0; i < G.numVertexes;i++)
             visited[i] = false;
       InitQueue(&Q);
       for(i = 0; i < G.numVertexes;i++)//遍历每一个顶点
       { 
               if(!visited[i])//未访问过就处理
               {
                     visited[i] = true;
                     printf("%c",G.vexs[i]);
                     EnQueue(&Q,i);//将此顶点入列
                     while(!QueueEmpty(Q))//当前队列不为空 
                     {
                             DeQueue(&Q,&i)//出队列(先进先出原则),赋值给i
                             for(j = 0; j < G.numVertexes;j++)//遍历所有顶点
                             {
                                      //遍历
                                      if(j = 0; j < G.numVertexes;j++)
                                      {
                                            if(G.arc[i][j] == 1 && !visited[j])//查找出队列顶点的下一个顶点
                                            {
                                                    visited[j] = true;
                                                    printf("%c",G.vexs[j]);
                                                    Enqueue(&Q,j);//入队列
                                            }   
                                      }

                              }
                     } 
               }
       }
}

b. 邻接表的广度遍历算法

void BFSTraverse(GraphAdjList GL)
{
       int i;
       EdgeNode* p;
       Queue Q;
       for(i = 0; i < GL->numVertexes;i++)
             visited[i] = false;
       InitQueue(&Q);
       for(i = 0; i < GL->numVertexes;i++)//遍历每一个顶点
       { 
               if(!visited[i])//未访问过就处理
               {
                     visited[i] = true;
                     printf("%c",GL->adjList[i].data);
                     EnQueue(&Q,i);//将此顶点入列
                     while(!QueueEmpty(Q))//当前队列不为空 
                     {
                             DeQueue(&Q,&i)//出队列(先进先出原则),赋值给i
                             p = GL->adjList[i].firstedge;//找到边链表
  
                              //遍历
                              while(p)
                              {
                                     if( !visited[p->adjvex] )//判断边链表中的结点是否被访问过                                     
                                     {
                                                    visited[p->adjvex] = true;
                                                    printf("%c",  GL->adjList[ p->adjvex ].data );
                                                    Enqueue(&Q,p->adjvex);//入队列
                                      } 
                                      p = p->next; 
                              }
                     } 
               }
       }
}

 

以上参考:

  • 大话数据结构

 

 

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值