图的存储结构——邻接表_图的邻接表-程序员宅基地

技术标签: # 数据结构  链表  数据结构  

一、邻接表表示法

回忆在线性表时,顺序存储结构就存在预先分配内存可能造成存储空间浪费的问题,于是引出了链式存储结构,同样的,我们可以考虑对边或弧使用链式存储方式来避免空间浪费问题

邻接表是图的一种链式存储结构。

由两部分组成:表头结点表和边表

邻接表中每个单链表的第一个结点存放有关顶点的信息,把这一结点看成链表的表头,其余结点存放有关边的信息

(1)表头结点表:包括数据域和链域,数据域存储顶点的名称,链域用于指向链表中第一个结点(与顶点邻接的第一个顶点)

(2)边表:包括邻接点域(指示与顶点邻接的点在图中的位置,即数组下标)、数据域(存储和边相关的信息,如权值)、链域(指示与顶点邻接的下一条边的结点)

表头结点表:
在这里插入图片描述
边表:
在这里插入图片描述

无向图的邻接表

在这里插入图片描述

特点:

  1. 邻接表不唯一(边表顺序可以互换)
  2. 无向图中有n个顶点、e条边,则其邻接表需n个头结点和2e个边结点,复杂度为O(n+2e)<O(n^2),所以适合存储稀疏图
  3. 无向图中顶点Vi的度为第i个单链表中的结点数。

有向图的邻接表

在这里插入图片描述
特点:

  1. 顶点vi的出度为第i个单链表中的结点个数
  2. 顶点vi的入度为整个单链表中邻接点域值是i-1的结点个数(需要遍历整个整个邻接表,较麻烦)。

为了便于确定顶点的入度,可以建立一个有向图的逆邻接表,即对每个顶点vi建立一个链接所以进入vi的边的表

有向图的逆邻接表

与上图同步

在这里插入图片描述
特点:

  1. 顶点vi的入度为第i个单链表中的结点个数
  2. 顶点vi的出度为整个单链表中邻接点域值是i-1的结点个数

此时我们很容易就可以算出某个顶点的入度或出度是多少,判断两顶点是否存在弧也很容易实现。

二、图的邻接表存储表示

//边的结点结构
#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; //定义一个数组vertices,是vertex的复数形式
 	int vexnum,arcnum; //图的当前顶点数和弧数
}ALGraph;

三、采用邻接表表示法创建无向网

//创建无向图G 
bool CreateUDG(ALGraph &G){
     
 	int i,j,k,v1,v2;
 	cin>>G.vexnum>>G.arcnum;//输入总顶点数,总边数
 	for(i=0;i<G.vexnum;i++){
    //输入各顶点,构造表头结点表 
  	cin>>G.vertices[i].data;//输入顶点值 
  	G.vertices[i].firstarc=NULL;//初始化表头结点的指针域
 }//for
 //输入各边,构造邻接表
 	for(k=0;k<G.arcnum;k++){
    
 	cin>>v1>>v2;    //输入一条边依附的两个顶点 
 	i=LocateVex(G,v1);
 	j=LocateVex(G,v2); //确定顶点在G.vertices中的序号
 	ArcNode *p1=new ArcNode;  //生成一个新的边结点*p1
  	p1->adjvex=j;  //邻接点序号为j 
  //头插法将新结点*p1插入顶点vi的边表头部 
 	p1->nextarc=G.vertices[i].firstarc;
 	G.vertices[i].firstarc=p1; 
 	ArcNode *p2=new ArcNode;
 	p2->adjvex=i;  //邻接点序号为i
 	//头插法插入p2 ,因为是无向网,所以是两条 
 	p2->nextarc=G.vertices[i].firstarc;
 	G.vertices[i].firstarc=p2; 
 }//for
  return true; 
}//CreateUDG

算法时间复杂度是O(n+e);

int LocateVex(ALGraph G,VerTexType u){
    
 //在图G中查找顶点u,存在则返回顶点表中的下标;否则返回-1
 	int i;
 	for(i=0;i<G.vexnum;i++)
  	if(u==G.vertices[i])
   		return i;
  	return -1;
} 

注意:一个图的邻接矩阵表示是唯一的,但其邻接表表示不唯一,这是因为邻接表表示中,各边表结点的链接次序取决于建立邻接表的算法,以及边的输入次序

代码实现此邻接表:

在这里插入图片描述

#include<iostream>
#include<queue>
using namespace std; 
#define MVNum 100 //最大顶点数

typedef struct ArcNode{
    
 	int adjvex;  //该边所指向的顶点的位置 
 	struct ArcNode *nextarc;//指向下一条边的指针 
}ArcNode;

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


//图的结构定义 
typedef struct{
    
 	AdjList vertices; //定义一个数组vertices,是vertex的复数形式
 	int vexnum,arcnum; //图的当前顶点数和弧数
}ALGraph;

int LocateVex(ALGraph G,int u){
    
 //在图G中查找顶点u,存在则返回顶点表中的下标;否则返回-1
 	int i;
 	for(i=1;i<=G.vexnum;i++)
  	if(u == G.vertices[i].data)
   		return i;
  	return -1;
} 

//创建无向图G 
bool CreateUDG(ALGraph &G){
     
 	

 	cout << "请输入总结点数和总边数:" << endl; 
 	cin>>G.vexnum>>G.arcnum;//输入总顶点数,总边数
 	cout << " 输入各顶点值: " << endl; 
 	for(int i = 1;i <= G.vexnum;i++)
	{
    //输入各顶点,构造表头结点表 
  		cin>>G.vertices[i].data;//输入顶点值 
  		G.vertices[i].firstarc=NULL;//初始化表头结点的指针域
	}//for
	getchar();
 	//输入各边,构造邻接表
 
 	cout << "输入一条边依附的两个顶点值"<<endl; 
 	for(int k=1;k<=G.arcnum;k++){
    
 	char v1,v2;
 	cin>>v1>>v2;    //输入一条边依附的两个顶点 
 	getchar();
 	
 	int i=LocateVex(G,v1);
 	int j=LocateVex(G,v2); //确定顶点在G.vertices中的序号
 	ArcNode *p1=new ArcNode;  //生成一个新的边结点*p1
  	p1->adjvex=j;  //邻接点序号为j 
  //头插法将新结点*p1插入顶点vi的边表头部 
 	p1->nextarc=G.vertices[i].firstarc;
 	G.vertices[i].firstarc=p1; 
 	ArcNode *p2=new ArcNode;
 	p2->adjvex=i;  //邻接点序号为i
 	//头插法插入p2 ,因为是无向网,所以是两条 
 	p2->nextarc=G.vertices[j].firstarc;
 	G.vertices[j].firstarc=p2; 
 }//for
  return true; 
}//CreateUDG



int main()
{
    
	ALGraph G;
	ArcNode *p;
	CreateUDG(G);
	
	cout << "输出邻接表: " <<endl;
	
	for(int i = 1; i<= G.vexnum; i++)
	{
    
		cout << G.vertices[i].data;
		for(p = G.vertices[i].firstarc; p != NULL; p = p->nextarc)
			printf("->%d", p->adjvex);
		cout << endl; 
	} 
		return 0;
}

在这里插入图片描述

邻接表表示法优缺点:
(1)优点

  1. 便于增加和删除顶点。
  2. 便于统计边的数目,时间复杂度是O(n+e)
  3. 空间效率高

(2)缺点

  1. 不便于判断顶点之间是否有边
  2. 不便于计算有向图各顶点的度
版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/weixin_45895026/article/details/104101047

智能推荐

Umi2.x升级到Umi3.x_node 多少版本对应的umi3-程序员宅基地

文章浏览阅读6.9k次,点赞5次,收藏7次。Umi3.x升级版本之路(一)修改依赖扁平化配置import all from umi修正语法支持antd4.x修改依赖npm uninstall -S dva antdnpm uninstall -D umi-plugin-react npm install -D umi@3 @umijs/preset-react// package.json"engines": { "node": ">=10.13.0"}// tsconfig.json"paths": { "@/*":_node 多少版本对应的umi3

【论文阅读】【三维目标检测】在Range view上做3D目标检测_rangeview-程序员宅基地

文章浏览阅读3.3k次,点赞10次,收藏22次。文章目录BEV or Range ViewRangeDet: In Defense of Range View for LiDAR-based 3D Object DetectionRange Conditioned Pyramid InMeta-Kernel ConvolutionWeighted Non-Maximum SuppressionData Augmentation in Range View DataExperimentrange view是仅针对物理旋转式扫描的激光雷达的特殊view,例_rangeview

shell 实现并发,并控制并发数量_shell 并发-程序员宅基地

文章浏览阅读4k次,点赞4次,收藏26次。为了方便理解,一步步的来首先先看一下串行的:#! /bin/bashST=$(date +%s)for i in $(seq 1 10)do echo $i sleep 1 # 模拟程序、命令doneET=$(date +%s)TIME=$(( ${ET} - ${ST} ))echo "time: ${TIME}"输出结果:12345678910time: 10这就最原始的进程运行模拟,串行方式,无法有效利用计算机的资源,_shell 并发

Mybatis-puls自动分页Page无法分页解决_使用mybatis-plus中page进行分页不生效-程序员宅基地

文章浏览阅读3.3k次,点赞3次,收藏5次。一开始使用Page时发现数据能出来但是无法分页,只能全部显示。打印数据出来也显示0。最后查了许多资料发现这个插件需要一个工具类的支持才可以实现。检查了一下代码发现也没有问题。最后更改完成测试,好使了。_使用mybatis-plus中page进行分页不生效

dpi与dp的关系_px、dp、sp、dpi之间的区别和转换-程序员宅基地

文章浏览阅读2k次。px、dp、sp、dpi之间的区别和转换区别:px (pixels)像素 -- 是像素,就是屏幕上实际的像素点单位。(一般UI人员在ps中经常使用)dp/dip 设备独立像素,android layout经常使用的尺寸单位,与设备屏幕有关,dp是虚拟像素,在不同的像素密度的设备上会自动适配。即与像素密度无关。sp 放大像素,主要是处理字体的大小dpi:Android支持四种不同的dpi模式:ldp..._dpi dp

2021-1-31: c语言中 %f 和 %lf 的区别_%if-程序员宅基地

文章浏览阅读3.4k次,点赞7次,收藏14次。%f和%lf分别是float类型和double类型用于格式化输入输出时对应的格式符号。其中:float,单精度浮点型,对应%f。double,双精度浮点型,对应%lf。在用于输出时:float类型可以使用%lf格式,但不会有任何好处。double类型如果使用了%f格式可能会导致输出错误。在用于输入时:double 类型使用了%f格式,会导致输入值错误。float类型使用double类型不仅会导致输入错误,还可能引起程序崩溃。所以在输入输出时,一定要区分好double和float,而使用对_%if

随便推点

使用JavaScript制作动态网页-2_javascript实现同个窗口的动态网页-程序员宅基地

文章浏览阅读1.8k次,点赞2次,收藏15次。使用JavaScript制作动态网页-2表单验证<!DOCTYPE html><html lang="en"><head> <meta charset="UTF-8"> <title>表单验证</title></head><body> <form action="..._javascript实现同个窗口的动态网页

Ubuntu20.04解决应用中心打不开的问题_snap商店打不开-程序员宅基地

文章浏览阅读1.5w次,点赞9次,收藏76次。Ubuntu20.04软件中心打不开 尝试了很多方法 Ubuntu 20.04 默认把软件中心换成了 snap, 感觉 snap 应用老出状况, snap 应用不但体积大, 安装好的应用还不时就崩溃, 所以如果要把电脑里的所有 snap 应用全部替换了, snapd 也卸载了. 下面这三句可以有效的解决 sudo apt install ubuntu-software sudo sn..._snap商店打不开

C语言-数据结构-栈-实验报告_数据结构栈的应用实验报告-程序员宅基地

文章浏览阅读6.5k次,点赞8次,收藏70次。实验报告内容:一、实验目的、要求:(1)熟练掌握栈的特点(先进后出FILO)及基本操作,如入栈、出栈等,栈的顺序存储结构和链式存储结构,以便在实际问题背景下灵活应用。(2)编写适当的主函数和相关函数,使实验题目运行出正确结果。(3)当场编程、调试、编译。(4)程序具有一定的健壮性、可读性,尽量简洁。(5)程序运行完成后分别存盘,上交实验报告,要求写出实验体会二、实验内容:(1)实验题目(2)主要函数的算法设计思想(3)程序清单(3)测试数据、实验结果及结论(4)实验体会(实验中存在的_数据结构栈的应用实验报告

qRadioButton-程序员宅基地

文章浏览阅读384次。#ifndef TESTRADIOBUTTON_H#define TESTRADIOBUTTON_H#include #include "ui_testradiobutton.h"class testRadioButton : public QMainWindow{ Q_OBJECTpublic: testRadioButton(QWidget *paren_radiobutton

C# WinForm 封装自定义组件(控件)Dll_把winfrom用户自定义控件封装成dll-程序员宅基地

文章浏览阅读2w次,点赞6次,收藏22次。封装自定义控件很简单,没什么技术含量,这里通过封装自定义的数字文本框实例简单总结一下:【1】新建自定义控件库 -- Windows Forms Control Library【2】添加自定义组件 -- Component Class【3】继承TextBox,添加KeyPress事件,代码如下:using System;usi_把winfrom用户自定义控件封装成dll

mysql关闭slave_mysql 关闭slave-程序员宅基地

文章浏览阅读619次。mysql版本:5.6.14一、修改 my.cnf 文件,增加skip-slave-start参数即可[mysqld]#主从log-bin=mysql-binserver-id=148skip-slave-start二、重启mysql/etc/init.d/mysql restart三、验证slave是否启动mysql> SHOW SLAVE STATUS\G****************..._mysql disable slave