【数据结构】图的存储结构—邻接表

简介: 【数据结构】图的存储结构—邻接表

前言


由于图的结构比较复杂,任意两个顶点之间都可能存在关系(边),无法通过存储位置表示这种任意的逻辑关系,所以,图无法采用顺序存储结构。这一点同其他数据结构(如线性表、树)不同。


因为图中的顶点具有相对概念,没有固定的位置,且顶点和顶点之间通过添加和删除边,维持着不同的关系。考虑图的定义,图是由顶点和边组成的。所以,分别考虑如何存储顶点和边。图常用的存储结构有邻接矩阵、邻接表、十字链表和邻接多重表。那么对于一般情况下该怎么存储图的数据结构呢?这里我们主要分两个章节详细介绍两种常用的图存储结构 — 邻接矩阵、邻接表


3fc85eae2a4349f7bd19be61cab7e4c1.gif


什么是邻接表?


在图论和计算机科学中,邻接表【Adjacency List】是用来表示有限图的无序表的集合。每个列表描述图中一个顶点的邻域集。这是计算机程序中常用的几种图形表示法之一。


1. 邻接表,存储方法跟树的孩子链表示法相类似,是一种顺序分配和链式分配相结合的存储结构。如这个表头结点所对应的顶点存在相邻顶点,则把相邻顶点依次存放于表头结点所指向的单向链表中。


2. 实际上就是一种由链表组成的图形数据结构,对其每一个顶点,都用链表来记录它的出边。  


3. 对于无向图来说,使用邻接表进行存储也会出现数据冗余,表头结点A所指链表中存在一个指向C的表结点的同时,表头结点C所指链表也会存在一个指向A的表点。  


邻接表:定义


对于无向图,n个顶点e条边的无向图的邻接表表示中,有n个顶点表结点和2e个边表结点。


对于有向图,vi的邻接表中每个表结点都对应于以vi为始点射出的一条边。因此,将有向图的邻接表称为出边表。


1、邻接表是图的一种链式存储方法


2、邻接表由一个顺序存储的顶点表和n个链式存储的边表组成。


顶点表由顶点结点组成


边表:


无向图:由 边 结点组成的一个单链表,表示所有依附于顶点vi的边。


有向图:由 弧 结点组成的一个单链表,表示所有以顶点vi为始点的弧。


链式存储结点:


图结点由2部分组成:第一个邻接点、下一个邻接点


734f61eefa18488abb27e075cc335d82.png


网结点有3部分组成:第一个邻接点、权值、下一个邻接点


d21501898c0f4c3cba5e6418c64280fa.png


无向图:对应的邻接表某行上边结点个数为该行表示结点的度。


如果无向图(网)中有e条边,则对应邻接表有2e个边结点。


ac3a2ece7c274c3f869fbe3cf5054f55.png


无向网邻接表


5ceb290834d6437d8d56622546a1a420.png


有向图:


邻接表:边表表示所有以vi为始点的弧。


对应的邻接表某行上边结点个数为该结点的出度。


在有向图的邻接表中不易找到指向该顶点的弧。


79e57a60e7844d8f903ed72ed7d4e0ae.png


逆邻接表:边表表示所有以vi为终点的弧。


对应的逆邻接表某行上边结点个数为该结点的入度。


3ff18c7436ea49ed91a3495bd0a0251f.png


如果有向图(网)中有e条边,则对应邻接表有e个边结点。

e0eb880424fe4c548fef816b4c79d574.png


邻接表:相关类


顶点结点类:

d3dc529de481451f82036f9300ae1625.png

//图的邻接表存储表示中的顶点结点类
public class VNode {
  public Object data;   //顶点信息
  public ArcNode firstArc;  //指向第一个依附于该顶点的弧
}

边(弧)结点类:


//图的邻接表存储表示中的边(弧)结点类
public class ArcNode {
  public int adjVex;    //该弧所指向的顶点位置
  public int value;   //边(或弧)的权值
  public ArcNode nextArc;  //指向下一条弧
}
邻接表类
//创建邻接表
public class ALGraph implements IGraph {
  private GraphKind kind;   //图的种类标志
  private int vexNum, arcNum;  //图的当前顶点数和边数
  private VNode[] vexs;   //顶点
    private void createUDN() {  //创建无向网
    }
  private void createDN() {  //创建有向网
    }
    // 给定顶点的值vex,返回其在图中的位置,若图中不包含该顶点的,则返回-1
  public int locateVex(Object vex) {}
    // 在图中插入边结点
  public void addArc(int v, int u, int value) {}
    // 返回v的第一个邻接点,若v没有邻接点则返回-1。其中 0 <= v <= vexNum
    public int firstAdjVex(int v) throws Exception {}
    // 返回v相对于w的下一个邻接点,若w是v的最后一个邻接点,则返回-1。其中 0 <= v,w <= vexNum
    public int nextAdjVex(int v, int w) throws Exception {}
}


邻接表:基本操作


1)创建无向网


步骤:


输入顶点数和边数


构建顶点向量


根据图的边数,确定输入边的数目


根据输入的每条边生成结点,并在相应位置插入边结点


代码


// 无向网的创建算法
private void createUDN() {
    //初始化变量
    Scanner sc = new Scanner(System.in);
    System.out.println("请分别输入图的顶点数、图的边数:");
    vexNum = sc.nextInt(); //顶点数n
    arcNum = sc.nextInt(); //边数e
    //输入图中各顶点
    vexs = new VNode[vexNum];
    System.out.println("请分别输入图的各顶点:");
    // 构造顶点向量
    for (int v = 0; v < vexNum; v++)
        vexs[v] = new VNode(sc.next());
    //输入图中各边信息
    System.out.println("请输入各边的顶点及其权值:");
    for (int k = 0; k < arcNum; k++) {
        int v = locateVex(sc.next());   // 弧尾
        int u = locateVex(sc.next());   // 弧头
        int value = sc.nextInt();  // 权值
        addArc(v, u, value);   //加入边
        addArc(u, v, value);   //加入边
    }
}

2)创建有向网

// 创建有向网
  private void createDN() {
  Scanner sc = new Scanner(System.in);
  System.out.println("请分别输入图的顶点数、图的边数:");
  vexNum = sc.nextInt();
  arcNum = sc.nextInt();
  vexs = new VNode[vexNum];
  System.out.println("请分别输入图的各顶点:");
  for (int v = 0; v < vexNum; v++)
    // 构建顶点向量
    vexs[v] = new VNode(sc.next());
  System.out.println("请输入各边的顶点及其权值:");
  for (int k = 0; k < arcNum; k++) {
    int v = locateVex(sc.next());// 弧尾
    int u = locateVex(sc.next());// 弧头
    int value = sc.nextInt();
    addArc(v, u, value);  // 一个方向
  }
  }

3)顶点定位

// 给定顶点的值vex,返回其在图中的位置,若图中不包含该顶点的,则返回-1
  public int locateVex(Object vex) {
  for (int v = 0; v < vexNum; v++)
    if (vexs[v].data.equals(vex))
    return v;
  return -1;
  }

4)插入边


核心思想:往链式存储的边表头插入一个新结点。

// 在图中插入边结点
  public void addArc(int v, int u, int value) {
  ArcNode arc = new ArcNode(u, value);  //u为边的终点,生成边结点
  arc.nextArc=vexs[v].firstArc;   //v为边的起点,将边结点插入顶点v 的链表表头
  vexs[v].firstArc=arc;     //生成新表头
  }

5)第一个邻接点

// 已知图中的一个顶点v,返回v的第一个邻接点,如果v没有连接点,则返回-1,其中0 <= v < vexNum
  public int firstAdjVex(int v) throws Exception {
  if (v < 0 || v >= vexNum)
    throw new Exception("第" + v + "个顶点不存在!");
  VNode vex = vexs[v];
  if (vex.firstArc != null)
    return vex.firstArc.adjVex;
  else
    return -1;
  }

6)查询下一个邻接点

public int nextAdjVex(int v, int w) throws Exception {
  if (v < 0 || v >= vexNum)
    throw new Exception("第" + v + "个顶点不存在!");
  VNode vex = vexs[v];
  ArcNode arcvw = null;
        // 获得w结点在链表中所在的位置
  for (ArcNode arc = vex.firstArc; arc != null; arc = arc.nextArc)
    if (arc.adjVex == w) {
    arcvw = arc;
    break;
    }
        // 返回w的下一个邻接点
  if (arcvw != null && arcvw.nextArc != null)
    return arcvw.nextArc.adjVex;
  else
    return -1;
  }


小试牛刀


回顾前一章节,我们一起学习了图的邻接矩阵和邻接表。下面我们来练习练习,给出下图,试着画出图对应的邻接表和邻接矩阵吧!检验一下学习成果。

7b73113c95654dbfa659c3f9ca8b2330.png



你学会了吗?快来对对答案吧!


7b73113c95654dbfa659c3f9ca8b2330.png


对比邻接表与邻接矩阵


1、对于稀疏图,邻接表比邻接矩阵更节省存储空间。


邻接表的空间使用与图中的边和顶点的数量成正比,而对于以这种方式存储的邻接矩阵,其空间与顶点数的平方成正比。然而,通过使用由顶点对索引的哈希表而不是数组,可以更有效地存储邻接矩阵的空间,从而匹配邻接表的线性空间使用。


2、邻接表上很容易找到任意一个顶点的邻接点和 ,但若要判定任意两个邻接点是否有边相连,则需遍历单链表,不如邻接矩阵方便。


3、邻接表和邻接矩阵之间的另一个显著区别是它们执行操作的效率。


在邻接表中,每个顶点的邻居可以有效地列出,在时间上与顶点的程度成正比。在邻接矩阵中,这个操作需要的时间与图中顶点的数量成正比,而顶点的数量可能明显高于度。此外,邻接矩阵允许测试两个顶点是否在固定的时间内彼此相邻;邻接表支持这一操作的速度较慢。


不同的数据结构也有着不同的操作。在邻接表中找到与给定顶点相邻的所有顶点就像读取邻接表一样简单。对于邻接矩阵,必须扫描整行,这就需要花费时间。


相关文章
|
1月前
|
存储 搜索推荐 算法
【数据结构】树型结构详解 + 堆的实现(c语言)(附源码)
本文介绍了树和二叉树的基本概念及结构,重点讲解了堆这一重要的数据结构。堆是一种特殊的完全二叉树,常用于实现优先队列和高效的排序算法(如堆排序)。文章详细描述了堆的性质、存储方式及其实现方法,包括插入、删除和取堆顶数据等操作的具体实现。通过这些内容,读者可以全面了解堆的原理和应用。
89 16
|
2月前
|
存储 安全 数据库
除了 HashMap,还有哪些数据结构可以实现键值对存储?
【10月更文挑战第11天】 除了`HashMap`,其他常见支持键值对存储的数据结构包括:`TreeMap`(基于红黑树,键有序)、`LinkedHashMap`(保留插入顺序)、`HashTable`(线程安全)、`B-Tree`和`B+Tree`(高效存储大量数据)、`SkipList`(通过跳跃指针提高查找效率)及`UnorderedMap`(类似`HashMap`)。选择合适的数据结构需根据排序、并发、存储和查找性能等需求。
|
3月前
|
存储 Java
java数据结构,线性表链式存储(单链表)的实现
文章讲解了单链表的基本概念和Java实现,包括头指针、尾节点和节点结构。提供了实现代码,包括数据结构、接口定义和具体实现类。通过测试代码演示了单链表的基本操作,如添加、删除、更新和查找元素,并总结了操作的时间复杂度。
java数据结构,线性表链式存储(单链表)的实现
|
2月前
|
存储 编译器 C++
【初阶数据结构】掌握二叉树遍历技巧与信息求解:深入解析四种遍历方法及树的结构与统计分析
【初阶数据结构】掌握二叉树遍历技巧与信息求解:深入解析四种遍历方法及树的结构与统计分析
|
2月前
探索顺序结构:栈的实现方式
探索顺序结构:栈的实现方式
|
3月前
|
存储 人工智能 C语言
数据结构基础详解(C语言): 栈的括号匹配(实战)与栈的表达式求值&&特殊矩阵的压缩存储
本文首先介绍了栈的应用之一——括号匹配,利用栈的特性实现左右括号的匹配检测。接着详细描述了南京理工大学的一道编程题,要求判断输入字符串中的括号是否正确匹配,并给出了完整的代码示例。此外,还探讨了栈在表达式求值中的应用,包括中缀、后缀和前缀表达式的转换与计算方法。最后,文章介绍了矩阵的压缩存储技术,涵盖对称矩阵、三角矩阵及稀疏矩阵的不同压缩存储策略,提高存储效率。
484 8
|
2月前
|
存储 算法
【数据结构】二叉树——顺序结构——堆及其实现
【数据结构】二叉树——顺序结构——堆及其实现
|
3月前
|
存储 算法 C语言
数据结构基础详解(C语言): 二叉树的遍历_线索二叉树_树的存储结构_树与森林详解
本文从二叉树遍历入手,详细介绍了先序、中序和后序遍历方法,并探讨了如何构建二叉树及线索二叉树的概念。接着,文章讲解了树和森林的存储结构,特别是如何将树与森林转换为二叉树形式,以便利用二叉树的遍历方法。最后,讨论了树和森林的遍历算法,包括先根、后根和层次遍历。通过这些内容,读者可以全面了解二叉树及其相关概念。
|
3月前
|
存储 算法 C语言
C语言手撕数据结构代码_顺序表_静态存储_动态存储
本文介绍了基于静态和动态存储的顺序表操作实现,涵盖创建、删除、插入、合并、求交集与差集、逆置及循环移动等常见操作。通过详细的C语言代码示例,展示了如何高效地处理顺序表数据结构的各种问题。
|
3月前
|
存储 Java
java数据结构,线性表顺序存储(数组)的实现
文章介绍了Java中线性表顺序存储(数组)的实现。线性表是数据结构的一种,它使用数组来实现。文章详细描述了线性表的基本操作,如增加、查找、删除、修改元素,以及其他操作如遍历、清空、求长度等。同时,提供了完整的Java代码实现,包括MyList接口和MyLinearList实现类。通过main函数的测试代码,展示了如何使用这些方法操作线性表。

热门文章

最新文章