首页 > 其他分享 >创建图的存储结构

创建图的存储结构

时间:2023-02-05 08:00:09浏览次数:40  
标签:vexnum 存储 arcs 创建 v1 v2 顶点 结构

引入

由于图的任意两个顶点之间都存在关系,自然无法采用诸如顺序存储结构这种适合一对一,物理地址连续的存储法,但可以采取邻接矩阵或邻接表作为图的存储结构。

邻接矩阵

假设i,j分别作为二维数组的横坐标与纵坐标,很显然,i,j两个下标恰好能表示图的两个顶点编号(顶点编号从0开始)。在人为规定的前提下,二维数组中的某元素的数值即可表示顶点之间有无边的存在,如在有向图中arr[i][j]==0表示i到j无弧,否则arr[i][j]==1表示有弧。这种二对一的关系与图的点边关系相类似。

另外,为了方便表示图的关系,在本案例中,arr[i][j]表示<i,j>或(i,j)的关系,当arr[i][j]==0时表示无边。

图的数据结构分析

首先是顶点,顶点具有自己的数据类型,此处以char作为顶点的数据类型(在实际应用中,假若顶点表示城市路口,那么顶点可以存储该路口的车流量,交通状况等等信息),定义 chat 的别名为VexType ,表示顶点数据类型,同时将定义一个一维数组存储顶点的数据,以该一维数组下标作为顶点的编号;

其次是图中的边或弧的信息,此处以无向网为例,故一律称顶点间的连线为边。很显然,二维数组的元素即可作为(i,j)边的数据域,比如数据域可以存储该边的权值或其他信息(结构体定义),若边的数据类型为 int,表示权值,定义 int 的别名为ArcType,那么二维数据的数据类型即为ArcType.

最后,一个图中还有最重要的信息即,图中的顶点个数vexnum以及边的个数arcnum.

代码实现

//文件名:AMGraph.h
#pragma once
#include<iostream>
using namespace std;

#define Maxw 999999  //定义最大权值
#define Mvnum 100  //最大顶点数
typedef char VerType;  //顶点数据类型
typedef int ArcType;  //边的数据类型
typedef struct AMGraph {
    int vexnum, arcnum;
    VerType vexs[Mvnum];  //顶点信息表
    ArcType arcs[Mvnum][Mvnum];   //邻接矩阵
}AMGraph;
//创建无向网
void CreateUDN(AMGraph& G);
//文件名为:AMGraph.cpp
#include"AMGraph.h"
void CreateUDN(AMGraph& G)
{
    //输入顶点与边的个数,图的第一部分信息
    cin >> G.vexnum >> G.arcnum;
    //判断合法性
    if (G.vexnum > Mvnum || G.arcnum > (G.vexnum - 1) * G.vexnum / 2)
    {
        cout << "所输入信息非法" << endl;
        return;
    }
    //紧接着输入顶点的信息,图的第二部分信息
    for (int i = 0;i < G.vexnum;i++)
    {
        cin >> G.vexs[i];
    }
    //将图的边初始化,权值全部置为0
    for (int i = 0;i < G.vexnum;i++)
    {
        for (int j = 0;j < G.vexnum;j++)
            G.arcs[i][j] = 0;
    }
    //输入权值
    for (int i = 0;i < G.arcnum;i++)
    {
        //输入v1,v2作为边(v1,v2)的顶点以及边之间的权值w
        //编号从0开始
        int v1, v2, w;
        //此处省略了查找v1,v2编号的过程
        cin >> v1 >> v2 >> w;
        //时刻关注合法性
        if (v1 == v2 || v1 >= G.vexnum || v2 >= G.vexnum 
            || w > Maxw||v1<0||v2<0)
        {
            i--;
            continue;
        }
        if (G.arcs[v1][v2] != 0)
        {
            i--;
            continue;
        }
        //输入边的权值
        G.arcs[v1][v2] = G.arcs[v2][v1] = w;
    }
    //创建完毕
}

注意事项:顶点编号V1、V2从0开始,在条件判断务必注意。

其余转化:

当我们需要存储无向图时,只需要将数组的元素值限定在0和1,0表示无边,1表示有边;

当我们需要存储有向网时,需要修改判定条件第八行: G.arcnum > (G.vexnum - 1) * G.vexnum / 2为 G.arcnum > (G.vexnum - 1) * G.vexnum;第45行代码改为 G.arcs[v1][v2] =w;(因为有向网的弧有方向性)。存储有向图的方式都差不多。

算法评估

优点:

  1. 能够快速判断两个顶点间是否存在边;
  2. 能够快速判断每个顶点的度,只需要限定横坐标为k,扫描G.arcs[k][j]不等于0的元素个数,(j从0开始);

缺点:

  1. 难以快速判断边的个数,需要从G.arcs[0][0]开始查找,时间复杂度为O(n^2);
  2. 对于有向图或有向网而言,难以快速判断顶点的出度与入度。对于顶点k而言,计算其入度需要扫描G.arcs[j][k]不等于0的元素个数,(j从0开始);计算其出度需要扫描G.arcs[k][j]不等于0的元素个数,(j从0开始);总之,所有顶点都需要扫描一边,时间复杂度为O(n^2);
  3. 空间复杂度高,O(n^2),且空间的浪费程度高。例如,对于无向图或者无向网而言,G.arcs[j][i]与G.arcs[i][j]等存储结果一样,且单独一个都能说明i与j之间的点边关系。

适用范围

1.图为稠密图;2.无需考虑出入度问题;

压缩版邻接矩阵(非必要掌握)

针对无向图或无向网而言,由于邻接矩阵是对称矩阵,且当i==j时,G.arcs[j][i]注定等于0,毫无存在意义。因此,以下利用压缩矩阵存储提高空间利用率。

规定,顶点编号i,j从0开始。

我们定义一个一维数组作为压缩矩阵的存储结构,其中一位数组的下标x,注定于顶点编号i、j间存在代数关系。我们选取上三角矩阵的元素作为压缩矩阵的存储元素,当我们依次从原来邻接矩阵的第零行开始,从左到右,从上到下选取元素。如下图,非白色部分为选取为压缩矩阵的元素。

 

 

规定i<j,那么x与i,j的关系式为:x=(2*G.vexnum-i-1)*i/2+j-i-1

公式解释,由于第一条边(0,1)的存储下标为x0=0,那么对于边(i,j)只需要知道其前面有多少个元素,即可知道其存储下标。对于第i行,第j列元素而言,其上i-1到0层的元素个数为:SUM=(G.vexnum-1)+(G.vexnum-2)+……+(G.vexnum-i)=(2*G.vexnum-i-1)*i/2;对于第i行的第j列的元素而言,其所在列前面的元素一共有j-i-1个,因此x=(2*G.vexnum-i-1)*i/2+j-i-1。

代码实现

typedef struct CAMGraph {
    int vexnum, arcnum;
    VerType vexs[Mvnum];  //顶点信息表
    ArcType arcs[Mvnum*(Mvnum-1)/2];
}CAMGraph;
void CreateUDN(CAMGraph& G)
{
    //输入顶点与边的个数,图的第一部分信息
    cin >> G.vexnum >> G.arcnum;
    //判断合法性
    if (G.vexnum > Mvnum || G.arcnum > (G.vexnum - 1) * G.vexnum / 2)
    {
        cout << "所输入信息非法" << endl;
        return;
    }
    //紧接着输入顶点的信息,图的第二部分信息
    for (int i = 0;i < G.vexnum;i++)
    {
        cin >> G.vexs[i];
    }
    //将图的边初始化,权值全部置为0
    for(int i=0;i<Mvnum*(Mvnum-1)/2;i++)
        G.arcs[i]=0;
    //输入权值
    for (int i = 0;i < G.arcnum;i++)
    {
        //输入v1,v2作为边(v1,v2)的顶点以及边之间的权值w
        //编号从0开始
        int v1, v2, w;
        //此处省略了查找v1,v2编号的过程
        cin >> v1 >> v2 >> w;
        //调整v1与v2
        if(v2<v1)
        {
            int t=v2;
            v2=v1;
            v1=t;
        }
        //时刻关注合法性
        if (v1 == v2 || v2 >= G.vexnum 
            || w > Maxw||v1<0)
        {
            i--;
            continue;
        }
        if (G.arcs[v1][v2] != 0)
        {
            i--;
            continue;
        }
        //输入边的权值
        G.arcs[v1][v2] = w;
    }
    //创建完毕
}

算法分析

优点:相对于邻接矩阵而言,提升了空间的利用率,同时也继承了邻接矩阵的所有优点。

缺点:几乎继承邻接矩阵的所有缺点,因为无论如何改进,空间复杂度依旧为O(N^2).

 

     

标签:vexnum,存储,arcs,创建,v1,v2,顶点,结构
From: https://www.cnblogs.com/writing-home/p/17092812.html

相关文章

  • [数据结构] 树、森林的遍历
    树的遍历树的遍历方式有先根遍历和后根遍历。在下面树的遍历中,采用的都是孩子兄弟表示法构建的树。树的先根遍历树的先根遍历步骤先根遍历就是先访问树的根节点,然后再......
  • 数据结构-实现逆波兰计算器
     packagecom.stack;importjava.util.ArrayList;importjava.util.List;importjava.util.Stack;publicclassPoland{publicstaticvoidmain(String[]a......
  • Java多线程01——多线程的创建
    1进程和线程进程:进程是并发执行程序在执行过程中,资源分配和管理的基本单位。进程可以理解为一个应用程序的执行过程,应用程序一旦执行,就是一个进程。线程:线程是进程的一个执......
  • 06 创建对象内存分析
    现在不太懂什么是栈,什么是堆?无关代码,个人此时喜好记录packagecom.zhan.base05Oop;publicclassTest06{publicstaticvoidmain(String[]args){P......
  • 方法和作用域中创建类
    1.方法中创建类类不仅可以在类的内部创建,也可以在方法和作用域中创建.publicclassParcel5{publicDestinationdestination(Strings){finalclass......
  • P63 类与对象的创建
    类与对象的关系类是一种抽象的数据类型,它是对某一类事物整体描述/定义,但是并不能代表某一个具体事物动物、植物、手机、电脑。。。Person类、Pet类、Car类等,这些类都是......
  • 04 类与对象的创建
    类与对象的创建代码packagecom.zhan.base05Oop;publicclassTest04{//类与对象的创建//一个项目应该只存在一个main方法,没必要每个类都有main方法......
  • 设计模式(四)----创建型模式之单例模式(二)
    1.1.3存在的问题1.1.3.1问题演示破坏单例模式:使上面定义的单例类(Singleton)可以创建多个对象,枚举方式除外。有两种方式,分别是序列化和反射。序列化反序列化Singlet......
  • Fabric2.x中Raft共识算法核心数据结构
    一、共识算法可插拔的代码体现Chain接口HyperledgerFabric的共识算法是可插拔的,在代码上体现为Chain接口,所有不同的共识算法均可根据Chain接口进行具体实现,目前fabric支......
  • [数据结构] 树、二叉树、森林的转换
    树树的表示方法双亲表示法用一组地址连续的存储单元来存放树中的各个节点,每一个节点中有一个数据域和一个指针域,数据域用来存储树中该节点本身的值;另一个指针域用来存储......