精华内容
下载资源
问答
  • 邻接矩阵和邻接表

    千次阅读 2020-07-05 19:30:41
    图的存储结构主要分两种,一种是邻接矩阵,...上面可以看出,无向图的边数组是一个对称矩阵。所谓对称矩阵就是n阶矩阵的元满足aij = aji。即矩阵的左上角到右下角的主对角线为轴,右上角的元和左下角相对应的元全都

    图的存储结构主要分两种,一种是邻接矩阵,一种是邻接表。

    1.邻接矩阵

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

    无向图

    这里写图片描述

    这里右图是把顶点作为矩阵内行和列的标头罗列出来。如果在两个顶点之间存在一条边,那么就把 1 放在这个位置上。如果边不存在,那么就赋值为 0。

    从上面可以看出,无向图的边数组是一个对称矩阵。所谓对称矩阵就是n阶矩阵的元满足aij = aji。即从矩阵的左上角到右下角的主对角线为轴,右上角的元和左下角相对应的元全都是相等的。

    从矩阵中很容易分析到图中信息:

    1)判断顶点之间是否有边很容易;

    2)要知道顶点的度,其实就是这个顶点在第i行(或第i列)的元素之和;

    3)求顶点vi的所有邻接点就是将矩阵中第i行元素扫描一遍,arc[i][j]为1就是邻接点。

    有向图

    上图图右是一个有向图。有向图是看入度和出度的(顶点的入边数和出边数),例如V2,就有2个出边。列代表着顶点的入边,列的元素相加代表顶点的入度,而行则代表该顶点所有的出边,元素相加则为顶点的出度。假如V2->V1的这个关系要写到邻接矩阵,首先先找到V1的列,然后在对应V2行的位置写上一个1,就可以了。

    网络

    网络是一个带权图。邻接矩阵如下图:

    有两种表示方法,没有边的两个顶点,可以用0表示,也可以用最大值表示。

    2.邻接表

    邻接矩阵是不错的一种图存储结构,但是,对于边数相对顶点较少的图,这种结构存在对存储空间的极大浪费。因此,找到一种数组与链表相结合的存储方法称为邻接表。

    邻接表的处理方法是这样的:
           1)图中顶点用一个一维数组存储,当然,顶点也可以用单链表来存储,不过,数组可以较容易的读取顶点的信息,更加方便。
           2)图中每个顶点vi的所有邻接点构成一个线性表,由于邻接点的个数不定,所以,用单链表存储,无向图称为顶点vi的边表,有向图则称为顶点vi作为弧尾的出边表(只管顶点的出边不管入边)。

    这里写图片描述

    从图中可以看出,顶点表的各个结点由data和firstedge两个域表示,data是数据域,存储顶点的信息,firstedge是指针域,指向边表的第一个结点,即此顶点的第一个邻接点。边表结点由adjvex和next两个域组成。adjvex是邻接点域,存储某顶点的邻接点在顶点表中的下标,next则存储指向边表中下一个结点的指针。

     

     

     

     

    展开全文
  • 图的邻接矩阵和邻接表的比较

    万次阅读 多人点赞 2018-08-16 15:39:11
    图的存储结构主要分两种,一种是邻接矩阵,一种是邻接表。1.邻接矩阵 图的邻接矩阵存储方式是用两...上面可以看出,无向图的边数组是一个对称矩阵。所谓对称矩阵就是n阶矩阵的元满足aij = aji。即矩阵的左上角到...

    图的存储结构主要分两种,一种是邻接矩阵,一种是邻接表。
    1.邻接矩阵
    图的邻接矩阵存储方式是用两个数组来表示图。一个一维数组存储图中顶点信息,一个二维数组(邻接矩阵)存储图中的边或弧的信息。
    设图G有n个顶点,则邻接矩阵是一个n*n的方阵,定义为:
    这里写图片描述
    看一个实例,下图左就是一个无向图。
    这里写图片描述
    从上面可以看出,无向图的边数组是一个对称矩阵。所谓对称矩阵就是n阶矩阵的元满足aij = aji。即从矩阵的左上角到右下角的主对角线为轴,右上角的元和左下角相对应的元全都是相等的。
    从上面可以看出,无向图的边数组是一个对称矩阵。所谓对称矩阵就是n阶矩阵的元满足aij = aji。即从矩阵的左上角到右下角的主对角线为轴,右上角的元和左下角相对应的元全都是相等的。
    从这个矩阵中,很容易知道图中的信息。
    (1)要判断任意两顶点是否有边无边就很容易了;
    (2)要知道某个顶点的度,其实就是这个顶点vi在邻接矩阵中第i行或(第i列)的元素之和;
    (3)求顶点vi的所有邻接点就是将矩阵中第i行元素扫描一遍,arc[i][j]为1就是邻接点;
    而有向图讲究入度和出度,顶点vi的入度为1,正好是第i列各数之和。顶点vi的出度为2,即第i行的各数之和。
    若图G是网图,有n个顶点,则邻接矩阵是一个n*n的方阵,定义为:这里写图片描述

    1.2 邻接表
    邻接矩阵是不错的一种图存储结构,但是,对于边数相对顶点较少的图,这种结构存在对存储空间的极大浪费。因此,找到一种数组与链表相结合的存储方法称为邻接表。
    邻接表的处理方法是这样的:
    (1)图中顶点用一个一维数组存储,当然,顶点也可以用单链表来存储,不过,数组可以较容易的读取顶点的信息,更加方便。
    (2)图中每个顶点vi的所有邻接点构成一个线性表,由于邻接点的个数不定,所以,用单链表存储,无向图称为顶点vi的边表,有向图则称为顶点vi作为弧尾的出边表。
    例如,下图就是一个无向图的邻接表的结构。
    这里写图片描述
    从图中可以看出,顶点表的各个结点由data和firstedge两个域表示,data是数据域,存储顶点的信息,firstedge是指针域,指向边表的第一个结点,即此顶点的第一个邻接点。边表结点由adjvex和next两个域组成。adjvex是邻接点域,存储某顶点的邻接点在顶点表中的下标,next则存储指向边表中下一个结点的指针。
    对于带权值的网图,可以在边表结点定义中再增加一个weight的数据域,存储权值信息即可。如下图所示。
    这里写图片描述
    3.两者区别
    对于一个具有n个顶点e条边的无向图
    它的邻接表表示有n个顶点表结点2e个边表结点
    对于一个具有n个顶点e条边的有向图
    它的邻接表表示有n个顶点表结点e个边表结点
    如果图中边的数目远远小于n2称作稀疏图,这是用邻接表表示比用邻接矩阵表示节省空间;
    如果图中边的数目接近于n2,对于无向图接近于n*(n-1)称作稠密图,考虑到邻接表中要附加链域,采用邻接矩阵表示法为宜。

    展开全文
  • 邻接表和邻接矩阵

    千次阅读 2018-09-19 16:20:31
    进入图论的大门(深渊)之前,不会存图可不行,来来来,邻接表和邻接矩阵拿去花

    进入图论的大门(深渊)之前,我们一定要掌握的两种必备手段——邻接表和邻接矩阵,此刻将成为我们的巨大帮手(其实做不来题还是做不来),下面让我们来学习一下,这两种存图方式在计算机中,该如何应用

    1.邻接矩阵:就是一个二维数组,大小为dis[n][n](n为顶点数),其中dis[i][j]表示顶点i到顶点j的距离,可以看出,邻接表的空间复杂度为O(n^2),那怎么存边呢?如下:

    for(int i = 0; i < m; i++)
    {
        cin>>u>>v>>w;
        dis[u][v] = w;
        dis[v][u] = w;    //不是双向图可以把这句注释掉
    }

    以上表示在有m条边的图中,顶点u->v有一条权值为w的边,双向图加上v->u权值也为w

    可以写一个邻接矩阵的初始化:

    #define inf 0x3f3f3f3f
    memset(dis,inf,sizeof dis);    //距离初始化为正无穷
    for(int i = 1; i <= n; i++)    //边从1开始编号,1~n
        dis[i][i] = 0;            //自己到自己的距离为0

    2.邻接表:邻接表是一部分人理解的难点,它的思想是,对每一个顶点,创建一个表,链接其所有出边,如下图: 

     假设存在这样的图:(u v w一组数表示顶点u到顶点v存在一条权值为w的边)

    4 5        //4个顶点,5条边
    1 4 9
    4 3 8
    1 2 5
    2 4 6
    1 3 7

    于是我们可以画一个这样的图(图片来源:啊哈算法):

     

    这是个什么意思呢?这里我们用五个三个数组u[],v[],w[],first[],next[]来记录每一条边的信息,其实邻接表就是记录边的信息啊,所以我们来看看这些数组是什么意思:

    首先我们对所有m条边进行编号,1~m,first[i]用来存顶点i的最后一条出边的编号next[i]用来存编号为i的边的上一条边的编号,u[],v[]是顶点集合,w[]是边权集合,u[i],v[i],w[i]表示第i条边是从顶点v[i] -> 顶点u[i],权值为w[i]的边,其实现在也说不明白,那我们就一步步模拟此过程:

    根据输入的顺序,可以将边编号1~5:

    4 5        //4个顶点,5条边
    1 4 9        //1
    4 3 8        //2
    1 2 5        //3
    2 4 6        //4
    1 3 7        //5

     第一条边存入:u[1] = 1,v[1] = 4,w[1] = 9,next[1] = first[u[1]],(先保存上一条边编号),first[u[1]] = 1;

    第二条边存入:u[2] = 4,v[2] = 3,w[2] = 8,next[2] = first[u[2]],first[u[2]] = 2;

    第三条边存入:u[3] = 1,v[3] = 2,w[3] = 5,next[3] = first[u[3]],first[u[3]] = 3;

    第四条边存入:u[4] = 2,v[4] = 4,w[4] = 6,next[4] = first[u[4]],first[u[4]] = 4;

    第五条边存入:u[5] = 1,v[5] = 3,w[5] = 7,next[5] = first[u[5]],first[u[5]] = 5;

     

    最后解释一下,这么存的妙处在什么地方:

    首先我们说了,first[i]表示顶点u[i]的最后一条出边的编号,注意是编号!那么,对于每一条输入的边,first[u[i]]就是以顶点u[i]为起点的边的编号,那么next[i] = first[u[i]]则表示,对于第i条边,next[i]保存的是以u[i]为起点的上一条边的编号,为何是上一条边?因为注意语句顺序,在next[i] = first[u[i]]执行完立即执行first[u[i]] = i,也就是将上一条输入的边的编号信息从first[]转移到next[]数组上了,first[]就可以存当前边的信息了,这样,我们通过first[]和next[]就可以遍历整个图了,过程如下图:

    比如我们要找顶点1的所有出边,首先找到first[1],然后根据next[first[1]]找到上一条边的编号,再通过next[next[first[1]]找到再上一条边的编号,一直继续搜索下去直到next[1] = -1为止,说明此时已遍历1号顶点所有出边(因为在没插入边的时候,head[1] = -1,而-1的值赋给了next[1],找到-1说明不存在上一条边了)

    是不是很神奇!但是我们这里为了组织边的时候看起来更具有整体性,不用五个数组存边,我们用一个结构体+head[]的形式,其中head[]就是上文的first[],代码如下:

    #define maxn 5250    //最大边数
    #define vertex 505   //最大点数
    
    struct node
    {
        int to,v,next;
    }e[maxn];
    
    int head[vertex+1],cnt;
    
    void init()            //初始化操作
    {
        cnt = 0;
        memset(head,-1,sizeof head);
    }
    
    void addedge(int u,int v,int w)    //加边操作
    {
        e[cnt].to = v;
        e[cnt].v = w;
        e[cnt].next = head[u];
        head[u] = cnt++;
    }
    
    for(int i = 1; i <= vertex; i++)    //遍历所有顶点的所有出边;
    {
        for(int j = head[i]; j != -1; j = e[j].next)    //从head[i],通过不断找next[j],来遍历顶点i的所有出边;
        {
            cout<<i<<" "<<e[j].to<<" "<<e[j].v<<endl;
        }
    }

    看起来很高大上?其实并没有,只是将v[]换成结构体的to成员,w[]换成结构体的v成员,next[]换成结构体的next成员,(结构体数组相当于成员数组的集合嘛),first[]换成head[]而已, 那么有人就要问了,为什么不用u[]呢?因为这样存边在大多情况下,不需要用到u[],具体原因可以具体考虑,当然加上u成员也无妨只是多一点空间开销嘛,比如在Bellman-Ford算法中u成员将会带来很大的便利,还是要看使用情况而定的

     

    当然,还有更直接,更接近于链表形式的邻接表表示法,那就是利用STL中的vector存一个图,这样的邻接表更易理解和使用,但是传说中有些题目可能卡vector而必须用数组模拟的方式写过,目前我还没有遇见这样的题目,但是这里还是介绍一下vector存图的方法(并且建议使用该方法):

    首先明确,vector实质上是一个动态数组的玩意儿,就是不定长数组,根据需要分配空间的意思,关于vector的使用方法,由于篇幅有限,只介绍几个常用的:

    #include<vector>
    using namespace std;
    
    vector<int> a;
    
    a[0];                //直接按下标访问元素(不会检查是否越界)
    a.push_back(3);    //将元素3插入数组最后
    a.pop_back();     //删除数组中最后一个元素
    a.insert(a.begin(),5);        //两个参数,插入位置,插入元素值
    a.erase(a.end());          //一个参数,要删除的位置
    a.empty();        //判断数组是否为空
    a.clear();        //清空数组
    a.size();        //返回数组此刻长度
    a.begin();        //迭代器指针
    a.end();           //迭代器尾指针
    a.cbegin();        //常量头指针
    a.cend();            //常量尾指针
    

    根据邻接表和vector的特性,我只需要将顶点i的出边,都存在一个数组里vector[i],就好了!那么数组vector是什么类型的呢?应该是“边”这种类型,我们可以设一个结构体类型,如下:

    #define maxn 5250    //最大点数;
    
    struct node
    {
        int to,v;    //to代表到哪个顶点,v代表权值
    };
    
    vector<node> e[maxn];    //每个点都有自己的邻接表,vector是一个node类型的数组

    那么怎么插入边呢?

    void addedge(int u,int v,int w)    //u->v = w;
    {
        node tmp;
        tmp.to = v;
        tmp.v = w;
        e[u].push_back(tmp);    //关于顶点u的邻接表
    }

    是不是特别好理解?这样邻接表就建好了,那要怎么遍历一个顶点边的信息呢?

    for(int i = 0; i < e[1].size(); i++)    //遍历1号顶点所有出边
    {
        cout<<1<<e[1][i].to<<e[1][i].v<<endl;
    }
    //输出应该会是(根据上图):
    1 4 9
    1 2 5
    1 3 7

    将完整的模块拼在一起就是:

    #define n 5250    //最大点数;
    #include<vector>
    using namespace std;
    
    struct node
    {
        int to,v;    //to代表到哪个顶点,v代表权值
    };
    
    vector<node> e[n+1];    //每个点都有自己的邻接表,vector是一个node类型的数组
    
    void init()
    {
        for(int i = 0; i <= n; i++)
            e[i].clear();
    }
    
    void addedge(int u,int v,int w)    //u->v = w,加边
    {
        node tmp;
        tmp.to = v;
        tmp.v = w;
        e[u].push_back(tmp);    //关于顶点u的邻接表
    }
    
    
    for(int i = 1; i <= n; i++)
    {
        for(int j = 0; j < e[i].size(); j++)    //遍历i号顶点所有出边
        {
            cout<<i<<e[i][j].to<<e[i][j].v<<endl;    //顶点i的第j条出边
        }
    }
    
    //根据上图,输出应该是:
    1 4 9
    1 2 5
    1 3 7
    2 4 6
    4 3 8

    当然,只有两个成员的结构体,我们可以考虑用pair代替(万能的STL啊!),注意make_pair(a,b);的快捷操作:

    #define n 5250    //最大点数;
    #include<vector>
    using namespace std;
    typedef pair<int,int> pir;    //pir.first代表到哪个顶点,pir.second代表权值
    
    vector<pir> e[n+1];    //每个点都有自己的邻接表,vector是一个pair类型的数组
    
    void init()
    {
        for(int i = 0; i <= n; i++)
            e[i].clear();
    }
    
    void addedge(int u,int v,int w)    //u->v = w,加边
    {
        e[u].push_back(make_pair(v,w));    //关于顶点u的邻接表
    }
    
    
    for(int i = 1; i <= n; i++)
    {
        for(int j = 0; j < e[i].size(); j++)    //遍历i号顶点所有出边
        {
            cout<<i<<e[i][j].to<<e[i][j].v<<endl;    //顶点i的第j条出边
        }
    }
    
    //根据上图,输出应该是:
    1 4 9
    1 2 5
    1 3 7
    2 4 6
    4 3 8

    对了,也许有人要问,为什么是e[i][j]呢?显然e[n]是一个二维数组啊,因为每一个点的出边本身是一个数组啊,不止有一条出边,所以相当于往动态数组里存数组,当然是二维数组了,可以访问e[i][j],表示,顶点i的第j条出边,关于pair,本身没什么好讲的,很简单就不介绍了.

    今天关于存图的方法,就写到这里,相信该是够用了吧

    展开全文
  • 图的邻接矩阵的实现

    万次阅读 多人点赞 2017-12-03 19:01:51
    对于图的一些基本概率和术语的内容汗牛充栋,故...仅仅总结下笔者学习图的邻接矩阵的相关知识邻接矩阵的实现原理邻接矩阵(adjacency matrix)是图ADT最基本的实现方式,使用二维数组A[n][n]来表示由n个顶点构成的图。

    对于图的一些基本概率和术语的内容汗牛充栋,故本文不会做过多解释。仅仅总结下笔者学习图的邻接矩阵的相关知识

    邻接矩阵的实现原理

    邻接矩阵(adjacency matrix)是图ADT最基本的实现方式,使用二维数组A[n][n]来表示由n个顶点构成的图。二维数组中的A[i][j]表示一条从来顶点i为起点到顶点j的弧。

    这里写图片描述

    对于无权图,存在(不存在)从顶点u到v的边,当且仅当A[u][v] =1(0)。上图a,b即为无向图和有向图的邻接矩阵实例。图c所示,矩阵各单元可从布尔型改为整型或浮点型,记录所对应边的权重。对于不存在的边,通常统一取值null。

    节点的实现

    public class Node<T> {
        private T data; // 存储的数据
        private int outDegree; // 出度
        private int inDegree; // 入度
        //如下四个属性用与遍历中
        private int status = 0;  //状态 0 undiscovered  1 discovered   2 visited   
        //status应该使用枚举的,这里笔者偷懒了
        private int parent = -1;
        private int dTime = -1; //开始遍历的时间
        private int fTime = -1; //结束遍历的时间
    
       public Node(T t) {
            this.data = t;
            this.outDegree = 0;
            this.inDegree = 0;
        }
    
        protected void addInDegree() {
            this.inDegree++;
        }
    
        protected void addOutDegree() {
            this.outDegree++;
        }
    
        protected void delInDegree() {
            this.inDegree--;
        }
    
        protected void delOutDegree() {
            this.outDegree--;
        }
    //节约篇幅,省略getter setter...
    }

    弧的实现

    public class Edge<T> {
        private T data; // 弧数据
        private int weight; // 权值
        private int type;  
        //应该使用枚举的,这里笔者偷懒了
        //弧类型:0 CROSS 跨边  1 TREE(支撑树) 
        //2 BACKWARD(该弧的起点和终点在支撑树中存在终点到起点的路径) 
        //3 FORWARD (该弧的起点和终点在支撑树中存在其他路径依然可以从起点到终点)
    
        public Edge(T data) {
            this(data, 1);
        }
        public Edge(T data, int weight) {
            this.data = data;
            this.weight = weight;
        }
        //节约篇幅,省略getter setter...
    
    }

    邻接矩阵的存储实现

    public class AdjacencyMatrix {
    
      private Edge[][] nodeGraphs;
      private Node[] allNodes;
      private Integer size=0;
      private int DEFAULT_CAPACITY = 10;
    
      //创建默认长度的邻接矩阵
      public AdjacencyMatrix() {
    
          nodeGraphs = new Edge[DEFAULT_CAPACITY][DEFAULT_CAPACITY];
          allNodes = new Node[DEFAULT_CAPACITY];
      }
    
      //创建指定长度的邻接矩阵
      public AdjacencyMatrix(int numbers) {
    
          nodeGraphs = new Edge[numbers][numbers];
          allNodes = new Node[numbers];
      }
    
      //添加新节点,并返回新节点索引
      public int addNode(Node node) {
          int returnIndex = size;
          //判断当前数组的容量,来决定是否扩容
          ensureCapacityInternal(size + 1);
          allNodes[size++] = node;
          return returnIndex;
      }
    
      private void ensureCapacityInternal(int minCapacity) {
    
          minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
          ensureExplicitCapacity(minCapacity);
      }
    
      private void ensureExplicitCapacity(int minCapacity) {
        //判断是否需要扩容
        if(minCapacity - allNodes.length > 0) {
            grow(minCapacity);
        }
      }
    
      private void grow(int minCapacity) {
    
          int oldCapacity = allNodes.length;
          //新数组的长度为(原数组长度+原数组长度/2)
          //右移n为即为除2的n次方
          int newCapacity = oldCapacity + (oldCapacity >> 1);
          if (newCapacity - minCapacity < 0)
              newCapacity = minCapacity;
          //将原数组复制到扩容后的数组中
          allNodes = Arrays.copyOf(allNodes, newCapacity);
          nodeGraphs = Arrays.copyOf(nodeGraphs, newCapacity);
            //对于二维中的数组执行扩容
            for(int i=0;i<nodeGraphs.length;i++) {
                if(nodeGraphs[i] != null) {
                  nodeGraphs[i] = Arrays.copyOf(nodeGraphs[i], newCapacity);
                }else {
                  nodeGraphs[i] = new Edge[newCapacity];
                }
          }
      }
    
      //获取出度
      public int getInDeep(int index) {
          if(allNodes[index] == null) {
              return -1;
          }
          return allNodes[index].getInDegree();
      }
    
      //获取入度
      public int getOutDeep(int index) {
          if(allNodes[index] == null) {
              return -1;
          }
          return allNodes[index].getOutDegree();
      }
    
      //删除第index边及其相关边
      public Node removeNode(int deleteIndex) {
    
          Node node = allNodes[deleteIndex];
          if(node == null) {
              throw new RuntimeException("节点为空");
          }
          //先删除节点
          allNodes[deleteIndex] = null;
          //删除所有相关边
          for(int i=0;i<size;i++) {
    
              //该节点作为一条弧的弧头(即指向该节点的弧)
              if(nodeGraphs[deleteIndex][i] != null) {
                  nodeGraphs[deleteIndex][i] = null;
                  allNodes[i].delInDegree();
              }
    
              //该节点作为一条弧的弧尾(即该节点出去的弧)
              if(nodeGraphs[i][deleteIndex] != null) {
                  nodeGraphs[i][deleteIndex] = null;
                  allNodes[i].delOutDegree();
              }
          }
          return node;
      }
    
      //设置一个从节点row为起点到col的弧
      public void setMatrix(int row, int col, Edge val) {
          nodeGraphs[row][col] = val;
          allNodes[row].addOutDegree();
          allNodes[col].addInDegree();
      }
    
      //重置状态
      public void reload() {
          for(int i=0;i<size;i++) {
              allNodes[i].setStatus(0);
              allNodes[i].setParent(-1);
          }
      }
    }

    性能

    时间性能:

    按照代码的实现方式,各顶点的编号可直接转换为其在邻接矩阵中对应的顶点,从而使得图中所有的静态操作接口,均只需O(1)时间。弧的静态和动态操作也仅需O(1)时间,其代价是邻接矩阵的空间冗余。
    邻接矩阵的不足主要体现在,顶点的动态操作接口均十分耗时。为了插入新的顶点,顶点集向量allNodes[]需要添加一个元素;弧集向量nodeGraphs[][]也需要增加一行,且每行都需要添加一个元素。顶点删除操作,亦与此类似。不难看出,这些恰恰也是数组结构固有的不足。
    但在通常的算法中,顶点的动态操作远少于其它操作。而且,即便计入向量扩容的代价,就分摊意义而言,单次操作的耗时亦不过O(n)

    空间性能:

    上述实现方式所用空间,主要消耗于邻接矩阵,亦即其中的二维弧集向量nodeGraphs[][]。每个Edge对象虽需记录多项信息,但总体不过常数。根据我们扩容的算法根据其数组长度始终不低于50%,故空间总量渐进地不超过O(n*n)= O(n^2)

    当然,对于无向图而言,仍有改进的余地。如上图a所示,无向图的邻接矩阵必为对称阵,其中除自环以外的每条边,都被重复地存放了两次。也就是说,近一半的单元都是冗余的。为消除这一缺点,可采用压缩存储等技巧,进一步提高空间利用率。

    使用邻接矩阵实现一个图

    现在使用刚刚笔者定义的Node,Edge和AdjacencyMatrix定义一个图

     public static void main(String[] args) {
    
    
           int aIndex = matrix.addNode(new Node<String>("a"));  //索引为0
           int bIndex = matrix.addNode(new Node<String>("b"));  //索引为1
           int cIndex =  matrix.addNode(new Node<String>("c"));  //索引为2
           int dIndex = matrix.addNode(new Node<String>("d"));  //索引为3
           int eIndex = matrix.addNode(new Node<String>("e"));  //索引为4
           int fIndex = matrix.addNode(new Node<String>("f"));  //索引为3
           int gIndex = matrix.addNode(new Node<String>("g"));  //索引为4
    
    
           matrix.setMatrix(aIndex, bIndex, new Edge<String>("这是边a到b的弧",1));
           matrix.setMatrix(aIndex, cIndex, new Edge<String>("这是边a到c的弧",1));
           matrix.setMatrix(bIndex, dIndex, new Edge<String>("这是边b到d的弧",1));
           matrix.setMatrix(bIndex, eIndex, new Edge<String>("这是边b到e的弧",1));
           matrix.setMatrix(cIndex, fIndex, new Edge<String>("这是边c到f的弧",1));
           matrix.setMatrix(cIndex, gIndex, new Edge<String>("这是边c到g的弧",1));
    
           //打印节点的出度和入度
           System.out.println("a:"+matrix.getInDeep(aIndex));
           System.out.println("a:"+matrix.getOutDeep(aIndex));
    
           System.out.println("b:"+matrix.getInDeep(bIndex));
           System.out.println("b:"+matrix.getOutDeep(bIndex));
    
           System.out.println("c:"+matrix.getInDeep(cIndex));
           System.out.println("c:"+matrix.getOutDeep(cIndex));
    
           System.out.println("d:"+matrix.getInDeep(dIndex));
           System.out.println("d:"+matrix.getOutDeep(dIndex));
     }
    展开全文
  • 图 图的定义 图(Graph)G由两个集合V和G组成,记作G = (V,G)。其中V是各顶点(结点)的有穷...在实际应用中,每条边可以标上具有某种含义的数值,该数值称为边上的权,这些权可以表示一个顶点到另一个顶点的距离或
  • 上面可以看出,无向图的边数组是一个对称矩阵。所谓对称矩阵就是n阶矩阵的元满足aij = aji。即从矩阵的左上角到右下角的主对角线为轴,右上角的元和左下角相对应的元全都是相等的。 这个矩阵中,很容易知道图中...
  • 图的两种存储方式:邻接表、邻接矩阵 图的存储结构主要分两种,一种是邻接矩阵,一种是邻接表。 1.邻接矩阵 图的邻接矩阵存储方式是用两个数组来表示图:一个一维数组存储图中顶点信息,一个二维数组(邻接矩阵)...
  • 邻接矩阵 邻接表 对比邻接矩阵与邻接表 十字链表 邻接多重表   前言 在线性表中,数据元素之间仅有线性关系,每个数据元素只有一个直接前驱和一个直接后继; 在树形结构中,数据元素之间有明显的层次关系,...
  • 图的邻接表和邻接矩阵

    千次阅读 2017-03-10 13:47:37
    图的存储结构主要分两种,一种是邻接矩阵,一种是邻接表。 1.邻接矩阵 图的邻接矩阵存储方式是用两个数组来表示图。一个一维数组存储图中顶点信息,... 上面可以看出,无向图的边数组是一个对称矩阵。所谓对称矩阵
  • 邻接矩阵 两个原子之间: 如果有结合,则为1; 如果没有结合,则为0。 那么,所有的原子间键都可以用下面的矩阵来表示。 这样的表示方法称为图的 "邻接矩阵"(adjacency matrix)。 当有两个键时,有时会用到2。 ...
  • c语言 构造邻接矩阵

    2020-07-27 16:54:45
    既然是矩阵那便为一个二维数组 AdjMatrix[MAX][MAX] max == 20;//或者30… 这是一个结构体类型的矩阵里面放着权值和边的信息 定义的结构体 #define INFINITY 999999 #define MAX_VERTEX_NUM 20 #define OK 1 #define...
  • 可以看出,无向图的邻接矩阵是对称矩阵。 邻接矩阵的优点:便于计算出各个顶点的度,以及判断两个顶点间是否有边。  缺点:矩阵中的大部分元素只是为了说明某些边不存在,造成了空间的浪费。 class GraphError( ...
  • 图图的定义有向图概念模板邻接矩阵邻接表无向图概念模板邻接矩阵邻接表简单图完全图 图的定义 图 GGG 由顶点集 VVV 和边集 EEE 组成,记为 G=(V,E)G=(V,E)G=(V,E),其中 V(G)V(G)V(G) 表示图 GGG 中顶点的有限非空集...
  • 邻接矩阵、邻接表、边缘列表都可以用来描述图 邻接矩阵 简单来说表示两个顶点之间是否有边 邻接表 简单来说就是储存跟一个顶点相连的顶点 边缘列表 简单来说就是边跟边的两个顶点 举个简单例子就很清楚了: 上图的...
  • 这里要总结的邻接矩阵时关于图的邻接矩阵;图的邻接矩阵(Adjacency Matrix)存储方式是用两个数组来表示图;一个一维数组存储图中顶点信息,一个二维数组(称为邻接矩阵)存储图中的边或弧的信息;
  • 图的邻接矩阵(C语言)

    万次阅读 多人点赞 2017-08-18 17:57:34
    邻接矩阵 无向图和有向图在邻接矩阵中的表示方法: 无向图和有向图大同小异,在这里只以无向图为例,代码部分通过简单调整即可对应编译有向图邻接矩阵数据类型定义#define MaxVertices 100 //定义最大容量 typedef...
  • C语言数据结构——图的邻接矩阵

    千次阅读 2019-07-22 11:41:04
    无向图和有向图在邻接矩阵中的表示方法: 无向图和有向图大同小异,在这里只以无向图为例,代码部分通过简单调整即可对应编译有向图 邻接矩阵数据类型定义 #define MaxVertices 100 //定义最大容量 typedef ...
  • 邻接矩阵的幂

    万次阅读 多人点赞 2017-04-09 11:13:47
    邻接矩阵幂的含义
  • 邻接矩阵无向图

    千次阅读 2017-12-29 21:15:58
    邻接矩阵   无向图和有向图在邻接矩阵中的表示方法:  无向图和有向图大同小异,在这里只以无向图为例,代码部分通过简单调整即可对应编译有向图 邻接矩阵数据类型定义 #define MaxVertices 100 //...
  • 在networkx中的邻接矩阵有两类(不是指数学意义上的)。数学意义上的无向图的邻接矩阵必定是对称矩阵,因此在networkx里只要给出上三角的即可,或者用元组表示 三种邻接矩阵 a=np.zeros((5,5))#第一种 a[0,1:]=[9,2,...
  • 图(邻接矩阵)的最短路径

    千次阅读 2019-02-01 22:53:11
    floyd算法适合稠密图(邻接矩阵)求各点到其他点的最短路径。邻接矩阵是图的一种简单的表示形式。dijkstra算法虽然适合邻接链表,算法题中也常用邻接矩阵实现。 宏定义点个数100,使用typedef定义边权重的类型int,...
  • 【数据结构】邻接矩阵--查看出度、入度

    千次阅读 多人点赞 2019-09-01 12:02:44
    邻接矩阵   邻接矩阵是为图服务的,记录了图间定顶点间的关系。图又分为有向图和无向图。  有向图:   概念:    图中的每条边都是由方向的 ,所有边都有方向的图称为有向图。   例图:  无向图: ...
  • 我觉得图这一章的学习内容更有难度,其实图可以说是树结构更为普通的表现形式,它的每个元素都可以与多个元素之间相关联,所以结构比树更复杂,然而越复杂的数据结构在现实中用途就越大了,功能与用途密切联系,所以...
  • 有向无环图(邻接矩阵和邻接表)

    千次阅读 2020-04-08 01:23:39
    一、图的定义 图是由顶点的有穷非空集合和顶点之间边的集合组成,通常表示为:  G=(V,E) 其中:G表示一个图,V是图G中顶点的集合,E是图G中顶点之间边的集合。 ... 图的遍历是在图中某...
  • C语言-图的存储结构(邻接矩阵

    千次阅读 多人点赞 2020-05-29 10:11:30
    查找函数(LocateVex查找坐标) 构建有向图(Directed Graph) ...输出邻接矩阵(print) 完整源代码: #include <stdio.h> #include <stdlib.h> #include <string.h> #define VertexMax .
  • 数据结构——图的邻接矩阵的遍历
  • 一、图的存储结构1.1 邻接矩阵 图的邻接矩阵存储方式是用两个数组来表示图。一个一维数组存储图中... 上面可以看出,无向图的边数组是一个对称矩阵。所谓对称矩阵就是n阶矩阵的元满足aij = aji。即矩阵的左...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 4,444
精华内容 1,777
关键字:

从邻接矩阵可以看出