数据结构——图

一、图的存储结构

1、邻接矩阵

用邻接矩阵表示法表视图,出了一个用于存储邻接矩阵的二维数组外,还需要用一个一位数组来存储顶点信息。

#define MaxInt 32767        //表示极大值
#define MVNum    100        //最大顶点数
typedef char VerTexType;    //假设顶点的数据类型为字符型
typedef int ArcType;        //假设边的权值类型为整型
typedef struct{
    VerTexType    vexs[MVNum];    //顶点表
    ArcType       arcs[MVNum][MVNum];    //邻接矩阵
    int            vexnum,arcnum;    //图的当前顶点数和变数
}AMGraph;

采用邻接矩阵创建无向图:

Status CreateUDN(AMGraph &G){
    cin>>G.vexnum>>G.arcnum;    //输入总顶点数,总边数
    
    for(int i=0;i<G.vexnum;i++)     //依次输入点的信息
        cin>>G.vexs[i];            

    for(int i=0;i<G.vexnum;i++)       //初始化邻接矩阵,边的权值均置为最大值
        for(int j=0;j<G.vexnum;j++)
            G.arcs[i][j]=MaxInt;        

    for(k=0;k<G.arcnum;k++)        //构造邻接矩阵
    {
        cin>>v1>>v2>>w;        //输入一条边依附的顶点及权值
        i=LocateVex(G,v1);    //确定v1,v2在G中的位置,即顶点数组的下标
        j=LocateVex(G,v2);
        G.arcs[i][j]=w;       //变<v1,v2>的权值为w
        //若为无向图则<v2,v1>的权值即<v1,v2>的权值,可以直接赋值
        //若为有向图则必须不相同,需要再输入<v2,v1>的权值
        G.arcs[j][i]=G.arcs[i][j];
        
    }

    return OK;
}

int LocateVex(AMGraph &G,VexTexType v){
    for(int i=0;i<G.vexnum;i++){
        if(G.vexs[i]==v)
            return i;
    }
    
    return MaxInt;
}

2、邻接表

邻接表是图的一种链式存储结构。在邻接表中,对图中每一个顶点Vi建立一个单链表,把与Vi相邻接的顶点放在这个链表中,把与Vi相邻接的顶点放在这个链表中。邻接表中每个单链表的第一个结点存放有关顶点的信息,把这个看成链表的表头,其余结点存放有关边的信息,这样邻接表便有两部分组成:表头结点表和边表

表头结点表:有所有表头结点一以顺序的形式存储,以便可以随机访问任一顶点的边链表。表头结点由数据域链域两部分组成。

边表:由表示图中顶点间关系的2n个边链表组成。边链表中边结点包括:邻接点域数据域链域。邻接点域指示与顶点Vi邻接的点在图中的位置;数据域存储和边有关的信息,如权值等;链域指示与顶点Vi邻接的下一条边的结点。

#define MVNum 100;               //最大顶点个数
typedef struct ArcNode            //边结点
{
    int adjvex;                     //该边所指向的顶点的位置
    struct ArcNode *nextarc;        //指向下一条边的指针
    OtherInfo    info;                //和边相关的信息
}ArcNode;

typedef struct VNode                //表头结点
{
    VerTexType data;                 //顶点信息
    ArcNode    *firstarc;            //指向第一条依附该顶点的边的指针
}VNode,AdjList[MVNum];                //AdjList表示邻接表类型

typedef struct                        //邻接表
{                            
    AdjList    vertices;    
    int vexnum,arcnum;        //图当前顶点数和边数。
}

 

采用邻接表创建无向图:

status CreateUDG(ALGraph &G){
    cin>>G.vexnum>>G.arcnum;    //输入中顶点数和总边数

    for(int i=0;i<G.vexnum;i++){
        cin>>G.vertices[i].data;       //输入顶点信息
        G.vertices[i].firstarc=NULL;        //初始化表头结点的指针域为NULL
    }

    for(k=0;k<G.arcnum;k++){            //输入各边,构造邻接表
        cin>>v1>>v2;             //输入边

        i=LocateVex(G,v1);    //确定边依附结点的位置
        j=LocateVex(G,v2);
        
        p1=new ArcNode;       生成一个新的边结点*p1
        p1->adjvex=j;        //邻接点序号为j
        p1->nextarc=G.vertices[i].firstarc;
        G.vertices[i].firstarc=p1;

        //若为无向图需要把j->i的也表示出来,若为有向图则不用
        p2=new ArcNode;
        p2->adjvex=i;
        p2->nextarc=G.vertices[j].firstarc;
        G.vertices[j].firstarc=p2;
    }

    return OK;
    
}

int Locatevex(ALGraph &G,ArcNode v)
{
    for(int i=0;i<G.vexnum;i++){
        if(G.vertices[i].data==v)
               return i;
    }
    

    return -1;
    
}

 

二、图的遍历

图的遍历是求解图的连通性问题、拓扑排序和关键路径的算法的基础。

图的遍历要比树的遍历复杂的多。因为图的任一顶点都可能和其余的顶点相邻接。所以访问某个顶点之后,可能沿着某条路径搜索之后,又回到该顶点上。为此,需要设一个辅助数组visited[n],其初始值置为‘false’或者0,一旦访问了该顶点,便置vistited[i]为“true”或1.

1、深度优先搜索(DFS)

(1)连通图的遍历

深度优先搜索类似于树的先序遍历,是树先序遍历的推广。

算法实现:

              1、 从图中某个顶点V出发,访问V,并置visited[v]的值为true。

               2、 依次检查v的所有邻接点w,如果visited[w]的值为true的值false,再从w出发进行递归遍历,直到图中所有的顶点都被访问过。

邻接矩阵的深度优先搜索:

void  DFS_AM(AMGraph G.int v)
{
    cout<<v;            //输出v,并置v为的访问标志组visited[v]=true.
    visited[v]=true;

    for(w=0;w<G.vexnum;w++)            //依次检查邻接矩阵v所在的行
        if(G.arcs[v][w]!=0&&visited[w]!=true)       //若是邻接点并未被访问则递归
            DFS_AM(G,w);

}

邻接表的深度优先搜索:

void DFS_AL(ALGraph G,int v)
{
    cout<<v;            //输出v,并置访问标志组visited[v]为true。
    visited[v]=true;

    p=G.vertices[v].firstarc;    //p指向v的边链表的第一个边结点

    while(p!=NULL){
        w=p->adjvex;    //表示w是v的邻接点
        if(visited[w]!=true)    //如果w为为被访问,则递归调用DFS_AL
            DFS_AL(G,w);
        p=p->nextarc;            //p指向下一个边结点
        
    }
    
}

(2)非连通图的遍历

上面的算法是连通图的遍历,输入一个结点从这个结点开始遍历,只能遍历该结点所在的连通分支,其他连通分支无法便利,所以只适用于连通图的遍历。

若要对非连通图进行遍历需要循环调用上面的算法,(注意:对非连通图的遍历就不能指定从哪一个结点开始了,因为指定一个结点只能遍历该结点所在的连通分支)

非连通图的遍历:

void  DFS(Graph G)
{
    for(int i=0;i<G.vexnum;i++)        //将所有访问标志组置为false
    {
        visited[v]=false;
    }
    
    sum=0;
    for(int i=0;i<G.vexnum;i++)    //循环遍历所有的结点
    {        
        if(visited[i]!=true){      //若该结点没有被访问,则从该结点开始遍历,调用上面的算法
            sum++;
            //若为临界矩阵非连通的遍历,则调用邻接矩阵的遍历算法
            DFS_AM(G,i);  
            //若为邻接表的非连通图的遍历,则调用邻接表的遍历的算法
            DFS_AL(G,i);
         }
    }

    cout<<"该图的连通分支数:"<<sum;      

}

此外,因为从一个点遍历就可以遍历完该点所在的连通分支,所以这个算法还可以计算图的连通分支数:

在第二个循环里,if条件进入多少次就说明有多少个连通分支,即遍历算法调用了多少次就有多少连通分支。

 

2、广度优先搜索(BFS)

广度优先搜索遍历类似于树的按层次遍历的过程。

算法步骤:

  1.                 从图中某个顶点v出发,访问v,并置visited[v]为true.然后将v进队
  2.                 只要队列不空,则重复下述操作:
  •                         队头顶点u出队;
  •                         依次检查u所有的邻接点w,如果visited[w]的值为false,则访问w,并置w的值为true,然后w进队

 

(1)连通图的遍历

      邻接矩阵连通图的的广度优先搜索

void BFS(AMGraph G,int v)
{
    cout<<v;        //访问第v个顶点,并置访问标志数组相应分量值为true。
    visited[v]=true;    
    
    InitQueue(Q);            //初始化队列Q,为空
    EnQueue(Q,v);        //v进队

    while(!QueueEmpty(Q))        //队列非空
    {
        DeQueue(Q,u);        //队头元素出队并置为u
        for(int i=0;i<G.vexnum;i++)
            if(visited[i]!=true&&G.arcs[u][i]!=0)    //如果i未被访问,并且与u相邻
            {    
                cout<<i;            //输出i
                visited[i]=true;        //置访问标记符尾true
                EnQueue(Q,i);            //i进队
            }    

    }

}

邻接表连通图的广度优先搜索:

void BFS_AL(ALGraph G,int v)
{
    cout<<v;        //访问第v个顶点,并置访问标志数组相应分量值为true。
    visited[v]=true;    
    
    InitQueue(Q);            //初始化队列Q,为空
    EnQueue(Q,v);        //v进队

    while(!QueueEmpty(Q))        //队列非空
    {
        DeQueue(Q,u);        //队头元素出队并置为u
        p=G.vertices[u].firstarc;        //将p置为u的第一个邻接点
        while(p!=NULL){                    //p不为NULL就循环
            w=p->adjvex;                    //记录下该结点的位置序号
            if(visited[w]!=true){            //如果该结点未被访问过
                cout<<w;                    //输出该结点
                visited[w]=true;                //置访问标志为true
                EnQueue(Q,w);                //将该结点进队
            }
            p=p->nextarc;                //指向下一个邻接点
        }
    }

}

(2)非连通图的广度优先搜索

非连通图的广度优先搜索的实现类似于非连通图的深度优先搜索,只需要将调用的深度优先搜索算法改为广度优先搜索算法即可

void  BFS(Graph G)
{
    for(int i=0;i<G.vexnum;i++)        //将所有访问标志组置为false
    {
        visited[v]=false;
    }
    
    sum=0;
    for(int i=0;i<G.vexnum;i++)    //循环遍历所有的结点
    {        
        if(visited[i]!=true){      //若该结点没有被访问,则从该结点开始遍历,调用上面的算法
            sum++;
            //若为临界矩阵非连通的遍历,则调用邻接矩阵的遍历算法
            BFS_AM(G,i);  
            //若为邻接表的非连通图的遍历,则调用邻接表的遍历的算法
            BFS_AL(G,i);
         }
    }

    cout<<"该图的连通分支数:"<<sum;      

}

同理,也可以得到连通分支数。

 

三、最短路径

1、从某个单源点到其余各顶点的最短路径——迪杰斯特拉算法

迪杰斯特拉算法的的实现要求以下的辅助数据结构:

(1)一维数组S[i]:记录从源点V0到终点Vi是否已被确定最短路径长度,true表示确定,false表示尚未确定。

(2)一维数组Path[i]:记录从源点V0到Vi的当前最短路径上Vi的直接前驱顶点序号。其初始值为:如果从V0代Vi有弧,则          Path[i]=V0,否则为-1。

(3)一维数组D[i]:记录从源点V0到终点Vi的当前最短路径长度。其初始值为:如果从V0到为Vi有弧,则D[i]为弧上的权值,都则为极大值。

算法步骤:

1、初始化:

                   将源点V0加到S中,即S[V0]=true。

                    将V0到各个顶点的最短路径长度初始化为权值,即D[i]=G.arcs[V0][Vi].

                      如果V0和Vi之间有弧,则将Vi的前驱置为V0,即Path[Vi]=V0,否则Path[Vi]=-1;

2、循环n-1次,执行一下操作:

              选择下一条最短路径的终点Vk,使的:D[k]=Min{D[i] | i属于未被确定的顶点};

               将Vk加到S中,即S[Vk]=true;

                 根据条件更新从V0出发到集合V-S上上任一顶点的最短路径的长度,若条件D[K]+G.arcs[k][i]<D[i]成立,则更新D[i]=D[k]+G.arcs[k][i],同时更改Vi的前驱为Vk,Path[i]=K.

void ShortestPath_DIJ(AMGraph G,int V0)
{
    for(int i=0;i<G.vexnum;i++)        //n个顶点依次初始化
    {
        S[i]=false;            //S初始化为空集
        D[i]=G.arcs[v0][i];    //将V0到各个终点的最短路径长度初始化为弧上的权值
        if(D[i]!=MaxInt)        //如果V0和i之间有弧,则将i的前置初始化为V0
            Path[i]=V0;
        else                      //如果V0和i之间无弧,则将i的前置初始化为-1
            Path[i]=-1;
    }
    S[V0]=true;        //将V0加入S
    D[V0]=0;           //V0到自己的最短路径为0
    //------------初始化结束,开始主循环,每次求得V0到某个顶点V的最短路径,将V加入S集合---
    for(int i=1;i<G.vexnum;i++)    //对其余N-1个顶点,依次进行计算
    {
        int min=MaxInt;        //用来求出一个当前最短的路径确定其终点
        int locate;                //记录终点的位置序号
        for(int j=0;j<G.vexnum;j++)        //循环n次
        {
             if(S[j]!=true&&D[j]<min)    //找出未被确定的且路径最小的顶点
             {
                min=D[j];
                locate=j;       //记录其位置序号
             }
        }

         S[locate]=true;            //将该顶点加入S,即确定其最短路径

        for(int j=0;j<G.vexnum;j++)    //更新从V0出发到集合V-S上所有顶点的最短路径长度
        {
            if(S[j]!=true&&D[j]>D[locate]+G.arcs[locate][j])
            {
                D[j]=D[locate]+G.arcs[locate][j];        //更新D[j]
                Path[j]=locate;                           //更新j的前驱为locate
            }
        }
    }
}

       迪杰斯特拉算法就是先初始化,然后N-1次循环,每次循环线确定当前最短的一条路径,将其终点加入S确定下来,然后由于确定了一个最短路径,需要更新其余的为确定的顶点的当前最短路径,如经过刚刚确定的顶点更短则更新他的当前最短路径D和其直接前驱Path。

 

 

2、每一对顶点之间的对短路径——弗洛伊德算法(Floyd算法)
Floyd算法

 

四、最小生成树

1、普里姆算法(Prim)——归并顶点,与边无关,适用于稠密图

 

 

 

 

 

 

 

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值