数据结构【第十一天】:图(二)

图的存储结构

1.邻接矩阵

使用两个数组表示图,一个一维数组存储图中的顶点信息,一个二维数组(称为邻接矩阵)存储图中的边或弧的信息

无向图例子

无向图的边数组为对称矩阵即aij = aji

顶点的度为该顶点所在行的元素和

求顶点的邻接点就是该行中为1的顶点

有向图例子

有向图的边数组并不对称

从v0到v1的弧在数组中记为a01 = 1

顶点的入度为列和,出度为行和

网图例子(带权值的有向图)

顶点到自身的权值为0,不存在直接连线的权值为无穷

邻接矩阵存储结构定义

typedef char VertexType    //顶点类型
typedef int EdgeType       //边上的权值类型
const int MAXVEX = 100;       //最大的顶点数
const int INFINITY = 65535    //该数表示无穷大

typedef struct
{
    VertexType vexs[MAXVEX];            //顶点表
    EdgeType arc[MAXVEX][MAXVEX];        //边表,又称邻接矩阵
    int numVertexes, numEdges;        //顶点数和边数
}MGraph;

无向图创建

#include <iostream>

using namespace std;

void CreatMGragh(MGraph *G)
{
    int i,j,k,w;
    cout<<"输入顶点数和边数"<<endl;
    cin>>&G->numVertexes>>&G->numEdges;
    for(i=0; i<G->numVertexes; i++)
    {
        cin>>&G->vexs[i];    //输入顶点信息
    }
    for(i=0; i<G->numVertexes; i++)
        for(j=0; j<G->numVertexes; j++)
            G->arc[i][j] = INFINITY;    //邻接矩阵初始化
    for(k=0; k<G->numEdges; k++)
    {
        cout<<"输入边的下标i和上标j,并输入权值";
        cin>>&i;
        cin>>&j;
        cin>>&w;
        G->arc[i][j] = w;
        G->arc[j][i] = G->arc[i][j];    //由于是无向图,邻接矩阵对称
    }
}

创建的时间复杂度为O(n+n^2+e)

2.邻接表

邻接矩阵对于边比顶点少的图,会造成空间上的浪费,于是考虑将顶点存在一维数组中,而边采用链式存储结构保存,类似于树中的孩子表示法。

原则:
1.图中顶点采用一维数组存储

2.每个顶点v的所有邻接点构成一个线性表,采用单链表的形式,无向图称为顶点v的边表,而有向图则称为顶点v作为弧尾的出边表

无向图例子

顶点结点由数据域data和指针域firstedge(指向边表的第一个结点)组成

边表结点由adjvex邻接点域(存储某个顶点的邻接顶点在顶点表中的下标)和next指针域(指向边表中下一个结点指针)

有向图例子

有向图的逆邻接表:即每条边都是以该顶点v为弧头

带权值网图

在有向图的基础上邻接表的边表结点中添加一项权重即可

结点定义

typedef char VertexType;    //顶点类型
typedef int EdgeType       //边上的权值类型

typedef struct EdgeNode       //边表结点
{
    int adjvex;               //邻接点域,指向该顶点对应的下标
    EdgeType weight;          //权值
    struct EdgeNode *next;    //链域,指向下一个邻接点
}EdgeNode;        

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

typedef struct
{
    AdjList adjList;
    int numVertexes,numEdges;    //图中当前顶点数和边数
}GraphAdjList;

无向图的邻接表创建

void CreatALGraph(GrapAdjList *G)
{
    int i,j,k;
    EdgeNode *e;
    cout<<"输入顶点数和边数"<<endl;
    cin>>&G->numVertexes>>&G->numEdges;    //输入顶点数和边数
    for(i=0; i<G->numVertexes; i++)        
    {
        cin>>&G->adjList[i].data;    //输入顶点数据信息
        G->adjList[i].firstedge = NULL;    //将边表置为空表    
    }
    for(k=0; k<G->numEdges; k++)
    {
        cout<<"输入边的下标i和上标j,并输入权值";
        cin>>&i;
        cin>>&j;
        e = (EdgeNode*)malloc(sizeof(EdgeNode));    //向内存申请空间 生成边表结点
        e->adjvex = j;    //该边结点的邻接点域指向j
        e->next = G->adjList[i].firstedge;    //该节点的下一个指向当前顶点结点的下一个
        G->adjList[i].firstedge = e;        //更新当前顶点指针指向e
        //重新申请新的对称边结点 调换顶点顺序
        e = (EdgeNode*)malloc(sizeof(EdgeNode));    //向内存申请空间 生成边表结点
        e->adjvex = i;    //该边结点的邻接点域指向i
        e->next = G->adjList[j].firstedge;    //该节点的下一个指向当前顶点结点的下一个
        G->adjList[j].firstedge = e;        //更新当前顶点指针指向e    
    }
}

以上生成的办法应用了单链表创建中的头插法,对于n个顶点和e条边容易得出空间复杂度为O(n+e)

3.十字链表(有向图的优化)

将邻接表和逆邻接表结合,主要针对有向图,期望了解其入度与出度问题

顶点表结构
datafirstinfirstout

firstin表示入边表头指针(即该顶点为弧尾);

firstout表示出边表头指针(即该顶点为弧头);

边表结点结构
tailvexheadvexheadlinktaillink

tailvex是指弧起点在顶点表的下标

headvex是指弧终点在顶点表下的下标

taillink是指出边表指针域,指向起点相同的下一条边

headlink是指入边表指针域,指向中点相同的下一条边

若是网,可多加一个weight域存储权值

有向图例子

除结构复杂外,其创建图的算法的时间复杂度和邻接表是相同的

4.邻接多重表(无向图的优化)

当更关注边的操作(删除、添加),而不太关注顶点的操作时,可采用邻接多重链表

顶点表结点结构不变,重新定义边表结点结构如下

边表结点结构
ivexilinkjvexjlink

ivex和jvex是某条边依附的两个顶点在顶点表中的位置下标。

ilink指向依附顶点ivex的下一条边

jlink指向依附顶点jvex的下一条边

无向图例子如下

若想遍历某个顶点的所有邻接点,采用firstedge->jvex(第一个)--ilink-->ivex(第二个)--jlink-->jvex(第三个)依次下去直到空

其与邻接表的区别仅在于同一条边在邻接表中用两个结点表示,而在邻接多重表中只用了一个结点表示。

5.边集数组(有向图/网表示)

使用两个一维数组构成,一个数组存储顶点信息,另一个存储边的信息,这个边数组每个元素包含起点的下标、终点的下标和权

这样的结构适合在对边依次进行处理的操作,不太适合对顶点相关的操作

有向图例子如下

图的遍历

1.深度优先遍历(利用递归实现)

也称深度优先搜索,简称DFS。类似于树的前序遍历,主要思想是从任意顶点,按一个方向(类似于先遍历左子树),遍历其邻接点、其邻接点的邻接点......直到尽头后,开始回退(类似于查看右子树)查看有无遗漏,直至回退到起点。

邻接矩阵的存储方式进行DFS(时间复杂度为O(n^2))

typedef int Bootlean;    //Boolean为布尔类型
Boolean visted[MAX];    //访问标志的数组

//邻接矩阵的深度优化递归算法
void DFS(MGraph G,int i)
{
    int j;    
    visited[i] = TRUE;    //该节点访问标志位置1    
    cout<<"G.vexs[i]";    //对结点进行操作
    for(j = 0; j<G.numVertexes; j++)    //循环访问所有顶点
    {
        if(G.arc[i][j] == 1&& !visited[j])    //若该顶点与原顶点是邻接关系,且未被访问
            DFS(G,j);                //对未访问的邻接顶点进行递归调用
    }
}

//邻接矩阵的深度遍历操作

void DFSTraverse(MGraph G)
{
    int i;
    for(i=0;i<G.numVertexes;i++)
        visited[i] = FALSE;
    for(i=0;i<G.numVertexes;i++)
        if(!visited[i])    //对未访问的顶点调用DFS
            DFS(G,i);               //如果是连通图,即任意两顶点之间均有边,该指令只执行一次 
}

邻接表的存储方式进行DFS(时间复杂度为O(n+e))

对于点多边少的稀疏图来说,邻接表结构使算法在时间效率上大大提高

typedef int Bootlean;    //Boolean为布尔类型
Boolean visted[MAX];    //访问标志的数组

//邻接表的深度优化递归算法
void DFS(MGraphAdjList GL,int i)
{
    EdgeNode *p;    
    visited[i] = TRUE;    //该节点访问标志位置1    
    cout<<GL->adjList[i].data;    //对结点进行操作
    p = GL->adjList[i].firstedge;
    while(p)    //p不为空
    {
        if(!visited[p->adjvex])    //未被访问的边表结点
            DFS(G,p->adjvex);      //对未访问的邻接顶点进行递归调用
        p = p->next;    //指向下个边表结点
    }
}

//邻接矩阵的深度遍历操作

void DFSTraverse(MGraphAdjList GL)
{
    int i;
    for(i=0;i<GL->numVertexes;i++)        //初始化所有顶点状态
        visited[i] = FALSE;
    for(i=0;i<G->numVertexes;i++)
        if(!visited[i])    //对未访问的顶点调用DFS
            DFS(GL,i);               //如果是连通图,即任意两顶点之间均有边,该指令只执行一次 
}

2.广度优先遍历(利用队列实现)

又称广度优先搜索(BFS),主要思想是利用队列这种结构,压入第一个顶点,弹出该顶点时,将其邻接顶点加在队尾,依次弹出和压入。类似于树的层序遍历。

邻接矩阵的广度优先遍历算法

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;
            std::cout<<G.vexs[i];    //对顶点的操作
            EnQueue(&Q,i);           //将该顶点压入队列
            while(!QueueEmpty(Q))    //如果队列不为空
            {
                DeQueue(&Q,&i);    //出队列,赋给i
                for(j=0;j<G.numVertexes;j++)    //循环所有顶点
                {
                    if(G.arc[i][j] == 1&& !visited[j])    //未被访问且和当前顶点为邻接顶点
                    {
                        visited[j] = TRUE;
                        std::cout<<G.vexs[j];    //对顶点操作
                        EnQueue(&Q,j);        //将此顶点压入队列
                    }
                }
            }        
        }
    }
}

邻接表的广度优先遍历算法

void BFSTraverse(GraphAdjList GL)
{
    int i,j;
    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;
            std::cout<<G.vexs[i];    //对顶点的操作
            EnQueue(&Q,i);           //将该顶点压入队列
            while(!QueueEmpty(Q))    //如果队列不为空
            {
                DeQueue(&Q,&i);    //出队列,赋给i
                p = GL -> adjList[i].firstedge;
                while(p)    //循环该顶点的所有边表结点
                {
                    if(!visited[j])    //未被访问
                    {
                        visited[j] = TRUE;
                        std::cout<<G.vexs[j];    //对顶点操作
                        EnQueue(&Q,j);        //将此顶点压入队列
                    }
                    p = p->next;    //下一个边表结点
                }
            }        
        }
    }
}

深度优先适合目标比较明确,以找到目标为主要目的,而广度优先遍历适合不断扩大范围时找到最优解的情况。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值