图的基本算法实现

 

图的基本算法实现

 

图的基本算法实现主要包括图的存储、图的遍历,求解图的最小生成树,以及求解最短路径等。

 

图的存储:

对于图的存储有多种方式,最常用的是邻接矩阵存储、邻接表存储,除此外还有十字邻接表存储,邻接多重表存储等;

 

邻接矩阵存储很简单,对于无权图,用0或1来代表两点是否邻接;如果是是有权图,那就用权值来代表两点邻接,不相邻接的则直接置为无穷大,其数据结构表示如下:

#define MAXV //最大顶点个数
#defind INF 32767   //正无穷 
typedef struct
{
   
}InfoType;                               //与顶点相关的其它数据

typedef struct
{
    int no;                                  //顶点的标号
    InfoType info;                      //与顶点相关的其它数据
}VertexType;                           //顶点类型

typedef struct
{
    int edges[MAXV][MAXV];    //邻接矩阵
    int vexnum,arcnum;           //点数,边数
    VertexType vexs[MAXV];    //端点数组
}MGraph;                               //图的邻接矩阵表示

 

邻接表存储是一种顺序分配与链式分配相组合的存储方法,在邻接表里,顺序对图中的顶点v建立一个单链表,每个单链表的结点表示依附于顶点v的边,每个单链表上附设一个表头结点。表头结点由顶点的名或其他信息及指向表结点的指针两部分组成,而表结点由指向下一个表结点的指针,与v相邻接的点在图中的位置,以及其它与边或弧有关的信息三部分组成;注意,表结点其实也就是弧,存储的可以是弧的起点,也可以是弧的终点。其数据结构表示如下:

typedef struct AInfo
{
    int arcinfo;
}ArcInfo; //与弧相关的信息,存储的可以是弧的权重,或其它信息

typedef struct ANode
{
    int end; //弧的终点
    struct ANode* next; //指向下一条弧的指针
    ArcInfo info; //与弧相关的信息
}ArcNode; //弧结点,亦即表结点

typedef struct
{
    string vertex;
}Vertex; //顶点信息

typedef struct Vnode
{
    Vertex data;
    ArcNode *firstarc;
}VNode;  //表头结点

typedef VNode AdjList[MAXV]; //定义邻接表

typedef struct
{
    AdjList adjlist;  //邻接表
    int n,e;  //顶点,边数
}ALGraph; //图的邻接表的表示

 

十字链表存储以及邻接多重表存储暂不详述。

 

图的构建:

对图的构建,我们首先采用邻接矩阵存储,然后用邻接矩阵来构建邻接表,算法实现如下:

 

邻接矩阵构建

void create_matrix_graph(MGraph& mg)
{
    int i,j;
    cout<<"/nplease input how many of vertexs in the graph:/n";
    cin>>mg.vexnum;

    for(i=0;i<mg.vexnum;i++)
        mg.vexs[i].no=i;

    cout<<"/nplease input the graph matrix:/n";
    for(i=0;i<mg.vexnum;i++)
        for(j=0;j<mg.vexnum;j++)
            cin>>mg.edges[i][j];
}

 

由邻接矩阵转换为邻接表

void Mat2List(const MGraph& mgraph, ALGraph& algraph)
{
    int i,j,n=mgraph.vexnum;
    algraph.n=n;
   
    for(i=0;i<n;i++)
    {
        algraph.adjlist[i].firstarc=NULL;
        ArcNode *p;
        for(j=0;j<n;j++)
        {
            if(mgraph.edges[i][j]!=0&&mgraph.edges[i][j]!=-1) //-1代表没有两顶点不相邻
            {
                p=(ArcNode*)malloc(sizeof(ArcNode));
                p->next=algraph.adjlist[i].firstarc;
                p->end=j;
                p->info.arcinfo=mgraph.edges[i][j];
                algraph.adjlist[i].firstarc=p;  //采用前插法插入
            }
        }
    }
}

 

再由邻接表转换为邻接矩阵:

void List2Mat(const ALGraph& algraph,MGraph& g)
{
    int i,j,n=algraph.n;
    g.vexnum=n;

    for(i=0;i<g.vexnum;i++)
        g.vexs[i].no=i;

    for(i=0;i<n;i++)
    {
        ArcNode* p=algraph.adjlist[i].firstarc;
        while(p!=NULL)
        {
            g.edges[i][p->end]=1;
            p=p->next;
        }
    }
}

 

邻接表的创建是一个动态申请内存的过程,当我们使用完邻接表,应该手动地去将内存释放,释放实现如下:

void del_List(ALGraph* alg)
{
    int i,j,n=alg->n;
   
    for(i=0;i<n;i++)
    {
        ArcNode *s,*p=alg->adjlist[i].firstarc;
        s=p;
        while(p!=NULL)
        {
            delete p;
            p=s->next;
            s=p;
        }
    }
}

 

图的遍历:

图的遍历包括两种形式的遍历,一种是深度优先搜索遍历,一种是广度优先搜索遍历。

 

深度优先搜索遍历的过程是:从图中某个初始顶点v出发,首先访问该初始顶点v, 然后选择一个与顶点v相邻且没被访问过的顶点w为初始顶点,再从w出发进行深度优先搜索,址到与当前顶点v的所有顶点都被访问过为止。其实现算法如下:

void dfs(ALGraph* alg,int v)
{
    ArcNode *p;
    visited[v]=true;
    cout<<v<<" ";
    p=alg->adjlist[v].firstarc;

    while(p!=NULL)
    {
        if(!visited[p->end])   
            dfs(alg,p->end);
        p=p->next;
    }
}

 

广度优先搜索过程稍为复杂了些,它首先访问初始点v, 接着访问v的所有未被访问过的邻接点v0,v1,v2,..., 然后再按照v0,v1,...的次序,访问每个顶点的所有未被访问的邻接点,依次类推,直到完全访问。我们采用队列的方式实现,先将顶层结点压入队列,而后出队,将所有与其邻接的结点入队尾,再出队队首元素,将所有未被访问的结点入队尾,再出队队首元素,如此,直到队列为空。其算法实现过程如下:

void bfs(ALGraph* alg, int v)
{
    ArcNode *p;
    int queue[MAXV],front=0,rear=0,i,w;
    cout<<v<<" ";
    visited[v]=true;

    rear=(rear+1)%MAXV;
    queue[rear]=v;
   
    while(front!=rear)
    {
        front=(front+1)%MAXV;
        w=queue[front];
        p=alg->adjlist[w].firstarc;

        while(p!=NULL)
        {
            if(!visited[p->end])
            {
                cout<<p->end<<" ";
                visited[p->end]=true;
                rear=(rear+1)%MAXV;
                queue[rear]=p->end;
            }
            p=p->next;
        }
    }
}

 

最小生成树的构造:

最小生成树的构造算法一般采用普里姆算法(Prim)或克鲁斯卡尔算法(Kruskal), Prim算法的时间复杂度为O(n^2), 而Kruskalr的时间复杂度与选用的排序算法有关,可以是O(n^2),也可以是O(elog2(e)).

 

Prim算法是基于将图的顶点分成两部分, 一部分是已经加入候选队列中的点,而另一部分则是未加入的点,假设G=(V,E)是一个具有n个顶点的带权连通无向图,T=(U,TE)是G的最小生成树,其中,U是T的顶点集,TE是T的边集,则由G构造最小生成树的步骤如下:

1)初始化U={v0}, v0到其他顶点的所有边为候选边。

2)重复以下步骤n-1次,使得其他n-1个顶点被加入到U中:

从候选边中挑选一个权值最小的边输出,设该边在V-U的顶点是v, 将v加入U中,删除和v关联的边。考查当前V-U中的所有顶点vi, 修改候选边,若(v,vi)的权值小于原来和vi关联的候选边,则用(v,vi)来取代后者作为候选边。其实现算法如下:

void prim(ALGraph* alg,int v)
{   
    int cost[MAXV][MAXV],lowestcost[MAXV],i,j,k,n=alg->n,min,closest[MAXV];
    ArcNode *p;

    for(i=0;i<MAXV;i++)
        for(j=0;j<MAXV;j++)
        {
            cost[i][j]=i==j?0:INF;
        }
   
    for(i=0;i<n;i++)
    {
        p=alg->adjlist[i].firstarc;
        while(p!=NULL)
        {
            cost[i][p->end]=p->info.arcinfo;
            p=p->next;
        }
    }

    for(i=0;i<n;i++)
    {
        lowestcost[i]=cost[v][i];
        closest[i]=v;
    }

    for(i=1;i<n;i++)
    {
        min=INF;
        for(j=0;j<n;j++)
        {
            if(lowestcost[j]!=0&&lowestcost[j]<min)
            {
                min=lowestcost[j];
                k=j;
            }
        }

        cout<<"the weight of edge ("<<closest[k]<<","<<k<<"):"<<min<<"/n";
        lowestcost[k]=0;

        for(j=0;j<n;j++)
        {
            if(cost[k][j]!=0&&cost[k][j]<lowestcost[j])
            {
                lowestcost[j]=cost[k][j];
                closest[j]=k;
            }
        }
    }
}

 

Kruskal算法基于一种最简单的实现,首先将所有的边按权值大小进行排序,首先取权值最小的边,如果其在已选边中不构成回路,增加之,否则,考查下一条边;判断是否是回路的方式也很简单,最初先将每个点置于不同的聚类中,如果待加入的边的两端点所属的聚类不同,说明可以添加,然后修改两端点所属聚类,使其相同;如果待加入的边的两端点所属的聚类相同,说明可以构成回路,该边需要被抛弃。算法实现如下:

void kruskal(ALGraph* alg)
{
    struct edge
    {
        int start;
        int end;
        int weight;
    }edges[MAXV]; //边集
   
    int vset[MAXV],n=alg->n,i,j,k,e,m1,m2,q;//vset代表是判断是否形成回路的数组,e,代表边总数,k代表已选边数,q代表当前所选边,m1代表所选边的开始端点,m2代表所选边的结束端点
    ArcNode *p;
    bool exist;

    e=0;
    for(i=0;i<n;i++)
    {
        p=alg->adjlist[i].firstarc;
        while(p!=NULL)
        {
            exist=false;   
            for(j=0;j<e;j++)
            {
                if(edges[j].start==p->end&&edges[j].end==i)
                    exist=true;
            }   

            if(exist)
            {
                p=p->next;
                continue;
            }

            edges[e].start=i;
            edges[e].end=p->end;
            edges[e].weight=p->info.arcinfo;
            e++;

            p=p->next;
        }
    }//构建邻接表的边集

    for(i=0;i<e;i++)
    {
        for(j=i;j<e;j++)
        {
            if(edges[i].weight>edges[j].weight)
            {
                edge temp=edges[i];
                edges[i]=edges[j];
                edges[j]=temp;
            }
        }
    }//按权重的大小对邻接表进行排序

    for(i=0;i<n;i++)
        vset[i]=i;

    k=0;
    q=0;
    while(k<n-1)
    {
        m1=edges[q].start;
        m2=edges[q].end;

        if(vset[m1]!=vset[m2])//判断是否属于同一个聚类
        {
            cout<<"("<<m1<<","<<m2<<"):"<<edges[q].weight<<"/n";

            for(i=0;i<n;i++)
                if(vset[i]==vset[m2])
                    vset[i]=vset[m1];
            k++;
        }

        q++;
    }
}

 

求解图的最短路径

求解图的最短路径可以从两个角度着手,一个是着眼于一个顶点,求该顶点到其余各顶点的最短路径;另一个是着眼于两个顶点,求任意两个顶点间的最短路径。这样就有两个算法,一个是狄克斯特拉(Dijkstra)算法,一个是弗洛伊德(Floyd)算法。

 

Dijkstra算法

Dijkstra的算法的基本思想也是将图中顶点分为两组,一组是已求出的最短路径的顶点集合v1,一组是没有求出的最短路径的顶点集合v2。其先初始化一个顶点总数大小的距离数组,每次都将距离数组中最小距离的点v0加入到v1中,然后更新距离数组,如果经过v0发现路径会更短的话,就把其距离更新,用更小值来代替。对路径的求解,也使用一个数组,不过需要反向求出。其算法实现过程如下:

void dijkstra(ALGraph* alg, int v)
{
    int cost[MAXV][MAXV],dist[MAXV],i,j,n=alg->n,current,k,min,path[MAXV]; //cost是距离矩阵,dist是最短距离数组, n代表有多少点,current表示从dist中选取的点,k代表已选取了多少点,min,储存最小值,path代表路径数组
    bool flags[MAXV];
    ArcNode *p;

    for(i=0;i<MAXV;i++)
        for(j=0;j<MAXV;j++)
        {
            cost[i][j]=i==j?0:INF;
        }
   
    for(i=0;i<n;i++)
    {
        p=alg->adjlist[i].firstarc;
        while(p!=NULL)
        {
            cost[i][p->end]=p->info.arcinfo;
            p=p->next;
        }
    }  //初始化cost数组
    for(i=0;i<n;i++)
        path[i]=v;         //初始化路径数组

    for(i=0;i<n;i++)
        flags[i]=false;    //判断顶点i的最短距离已经被计算?
    flags[v]=true;
    for(i=0;i<n;i++)          //初始化最短距离数组
        dist[i]=cost[v][i];
    current=v;         //当前选取v点
    k=1;

    while(k<n)
    {
        min=INF;
        for(i=0;i<n;i++)
        {
            if(!flags[i]&&dist[i]<min)
            {
                current=i;
                min=dist[i];
            }    //求出最小的
        }

        flags[current]=true;

        for(i=0;i<n;i++)
            if(!flags[i]&&dist[current]+cost[current][i]<dist[i])
            {
                dist[i]=dist[current]+cost[current][i];
                path[i]=current;
            }           //更新距离数组

        k++;
    }

    int st[MAXV];
    int top=-1;
    for(i=0;i<n;i++)
    {
        if(i==v)
            continue;

        if(dist[i]==INF)
            cout<<"the closet dist between "<<v<<" and "<<i<<": no connect "<<"/n";
        else
        {
            cout<<"the closet dist between "<<v<<" and "<<i<<": "<<dist[i]<<"/n";
            j=i;
            while(j!=v)
            {
                top++;
                st[top]=path[j];
                j=path[j];
            }
            while(top>-1)
            {
                cout<<st[top]<<"->";
                top--;
            }
            cout<<i<<"/n";
        }            //输出
    }
}
             
Floyd算法求两点间的最短路径长度,它的基本思想可以用表达式A-1[i][j]=cost[i][j], Ak+1[i][j]=min{Ak[i][j],Ak[i][k]+Ak[k+1][j]}来代表。从顶点vi到顶点vj的最短路径上经过编号为k的顶点,可以分为两段,一段是从顶点vi到顶点vk的最短路径,另一段是从顶点vk+1到顶点vj的最短路径,此时最短路径长度等于这两段路径长度之和,与原先从顶点vi到顶点vj的路径上经过的顶点编号不大于k的最短路径长度,取两者之间最小值,并将路径数组更新,算法实现如下:

void floyd(ALGraph* alg)
{
    int cost[MAXV][MAXV],path[MAXV][MAXV],n=alg->n,k,i,j;
    ArcNode *p;

    for(i=0;i<MAXV;i++)
        for(j=0;j<MAXV;j++)
        {
            cost[i][j]=i==j?0:INF;
        }
   
    for(i=0;i<n;i++)
    {
        p=alg->adjlist[i].firstarc;
        while(p!=NULL)
        {
            cost[i][p->end]=p->info.arcinfo;
            p=p->next;
        }
    }
   
    for(i=0;i<n;i++)
        for(j=0;j<n;j++)
            path[i][j]=-1;

    k=0;
    while(k<n)
    {
        for(i=0;i<n;i++)
            for(j=0;j<n;j++)
                if(cost[i][k]+cost[k][j]<cost[i][j])
                {
                    cost[i][j]=cost[i][k]+cost[k][j];
                    path[i][j]=k;
                }
        k++;
    }
   
    int st[MAXV];
    int top=-1,q;
    for(i=0;i<n;i++)
        for(j=0;j<n;j++)
        {
            cout<<"the closet dist between "<<i<<" and "<<j<<": "<<cost[i][j]<<"/n";           
            cout<<i<<"->";
            q=j;
            while(path[i][q]!=-1)
            {
                top++;
                 st[top]=path[i][q];
                q=path[i][q];
            }
            while(top>-1)
            {
                cout<<st[top]<<"->";
                top--;
            }
            cout<<j<<"/n";
        }
}

 

嘿嘿,关于图的这一章终于结束,对于图我已经有个基本的认识了。

 

 

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值