图的数据结构,系统学习图的基本概念、定义和建立,学会邻接矩阵、邻接表以及实现六度空间案例,遍历图的方式——广度、深度访问

news2024/11/13 11:15:42

1.图的定义和术语

:G = (V,E) Graph = (Vertex, Edge)

V:顶点(数据元素)的有穷非空集合;

E:边的有穷集合。

有向图:每条边都是有方向的

 

 无向图:每条边都是无方向的

 完全图:任意两点之间都有一条边相连

 

 无向完全图:n个顶点,n(n-1)/2条边

无向完全图:n个顶点,n(n-1)条边

稀疏图:有很少边或弧的图(e<nlog n)

稠密图:有较多边或弧的图像

:边或弧带权的图

邻接:有边或弧相连的两个顶点之间的关系。存在(vi, vj),则称vi和vj互为邻接点;存在<vi, vj>,则称vi邻接到vj,vj邻接于vi。

关联(依附):边或弧与顶点之间的关系。存在(vi, vj)或<vi, vj>,则称该边或者弧关联于vi和vj。

顶点的度:与该结点相关联的边的数目,记为TD(v)

在有向图中,顶点的度等于该顶点的入度出度之和

顶点v的入度是以V为终点的有向边的条数,记作ID(v)

 

 路径:接续的边构成的顶点序列

路径长度:路径上边或弧的数目/权值之和。

回路(环):第一个顶点和最后一个顶点相同的路径。

简单路径:除路径起点和终点可以相同外,其余顶点均不相同的路径。

连通图(强连通图):在无(有)向图G=(V,{E})中,若对任何两个顶点v、u都存在从v到u的路径,则称G是连通图(强连通图)。

 

 

 

 权和网

图中边或弧所具有的相关数称为。表明从一个顶点到另一个顶点的距离或耗费。

子图 设有两个图G=(V,{E})、G1=(V1,{E1}),若V1被包含于V,E1被包含于E,则称G1是G的子图。

连通分量(强连通分量)

无向图G的极大连通子图称为G的连通分量。

极大连通子图意思是:该子图是G连通子图,将G的任何不在该子图中的顶点加入,子图不再连通。

 

 极小连通子图:该子图是G的连通子图,在该子图中删除任何一条边,子图不在连通。

生成树:包含无向图G所有定点的极小连通子图。

生成森林:对非连通图,由各个连通分量的生成树的集合。

2.图的类型定义

图的抽象数据类型定义如下:

ADT Graph
{
    数据对象V:具有相同特性的数据元素的集合,称为顶点集;
    数据关系R: R = {VR}
        VR = {<v,w>|<v,w>|v,w∈V ^ p(v,w),
              <v,w>表示从v到w的弧,P(v,w)定义了弧<v,w>的信息
             }
}
​
基本操作P:
Create Graph():图的创建操作。
CreateGraph(&G,V,VR)
{
    初始条件:V是图的顶点集,VR是图中弧的集合。
    操作结果:按V和VR的定义构造图G。
}
​
DFSTraverse(G)
初始条件:图G存在。
操作结果:对图进行深度优先遍历。
​
BFSTraverse(G)
初始条件:图G存在。
操作结果:对图进行广度优先遍历。

3.图的建立

1.邻接矩阵

邻接矩阵的存储表示:用两个数组分别存储顶点表和邻接矩阵

存储形式

#define MVNum 100
typedef char VerTexType;//设顶点的数据类型为字符型
typedef int ArcType;//假设边的权值类型为整型
​
typedef struct
{
    VerTexType vexs[MVNum];//顶点表
    ArcType arcs[MVNum][MVNum];//邻接矩阵
    int vexnum, arcnum;//图的当前点数和边数
}AMGraph;//Adjacency Matrix Graph

顶点的结点结构

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

说明:例如,AdjList v;相当于:VNode v[MVNum];

图的邻接表存储表示

#define MVNum 100 //最大顶点数
typedef struct ArcNode
{
    //边结点
    int adjvex;//该边所指向的顶点的位置
    struct ArcNode * nextarc;//指向下一条边的指针
    OtherInfo info;//和边有关的信息
}ArcNode;

图的结构定义

typedef struct
{
    AdjList vertices;//vertices--vertex的复数
    int vexnum,arcnum;//图的当前顶点数和弧数
}ALGraph;

 

 2.邻接表

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

  1. 输入总顶点数和总边数。

  2. 建立顶点表 依次输入点的信息存入顶点表中 使每个表头结点的指针域初始化为NU儿L

  3. 创建邻接表 依次输入每条边依附的两个顶点 确定两个顶点的序号和j,建立边结点 将此边结点分别插入到和V对应的两个边链表的头部

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

邻接表的特点

  1. 方便找任一顶点的所有“邻接点”

  2. 节约稀疏图的空间:需要N个头指针 + 2E个结点(每个结点至少2个域)

  3. 方便计算任一顶点的“度”, 对无向图:是的 ;对有向图:只能计算”出度”; 需要构造“逆邻接表”(存指向自己的边)来方便计算度

  4. 不方便检查任意一对顶点间是否存在边

3.案例引入——六度空间理论

“六度空间”理论又称作六度分隔(Six Degrees of Separation)理论。这个理论可以通俗地阐述为:“你和任何一个陌生人之间所间隔的人不会超过六个,也就是说,最多通过六个人你就能够认识任何一个陌生人。”该理论产生于20世纪60年代,由美国心理学家米尔格兰姆提出。

六度空间理论验证

但是米尔格兰姆的理论从来没有得到过严谨的证明,虽然屡屡应验,虽然很多社会学家一直都对其兴趣浓厚,但它只是一种假说。现在,许多科学家对此进行研究,它们都不约而同地使用了网络时代的新型通讯手段对“小世界现象”进行验证。

把六度空间理论中的人际关系网络抽象成一个无向图G。用图G中的一个顶点表示一个人,两个人认识与否用代表这两个人的顶点之间是否有一条边来表示。从任一顶点出发用广度优先方法对图进行遍历,统计所有路径长度不超过7的顶点。

算法实现

  1. 将输入的关系建立成一个二维数组relation[人数] [人数],其中第i行第j列为1则代表i与j有关系

  2. 用一个数组visit[人数]记录第i个人是否已经访问

  3. 用一个数组queue[人数]记录需要遍历其广度的人的队列

  4. 从编号为1的人开始,记录下他的一层所有关系的广度,每遍历到一个人,如果这个人对应的visit数组中为0,则代表这个人还未访问过,将这个遍历到的人入queue数组,再将对应的visit中的数值改为1,如果到了relation数组第n维的最后一个人,用tail记录下这个人的编号,代表着此层的最后一个人。

  5. 遍历queue数组的人的关系,当在queue中遍历到此tail编号的人的广度时,层数加1

  6. 当层数为6时,停止该人的遍历,转到下个编号的人的遍历

C语言代码

#include<stdio.h>
#include<malloc.h>
#define MAX 1001
​
int count(int num, int N, int ** relation)
{
    //对第num个人进行六度空间内有关联的人的计数
    int queue[MAX];//queue数组模拟一个队列,记录需要遍历其关系的人(需要遍历第num个人认识的人)
    int front = -1,rear = -1;//fornt和rear模拟头指针,分别queue的队列头和尾
    int sum = 1;//记录所有在六度内认识的人。
    int level = 0;//记录当前的层数,便于按层遍历,当层数为6,退出
    int levellast = num;//记录每一层最后一个人的编号,当遍历到这个编号时,层次加1
    int visit[MAX] = {0};//记录对每个人是否已经访问过的情况,防止出现重复遍历,初始状态为全部没有遍历
    int last;
    visit[num] = 1;
    queue[++rear] = num;//参数num为queue第一个元素,是第一个需要遍历其关系的人
    while(front < rear)
    {
        //对待遍历队列进行遍历
        int t = queue[++front];//t为第一个需要遍历其关系的人
        for(int i = 1;i <= N; i++)
        {
            //开始遍历该人的关系(即数组的第二维)
            if(!visit[i] && relation[t][i] == 1)
            {
                //如果第num个人和第i个人有关系且i尚未访问
                queue[++rear] = i;//将该第i个人记录进入待访问数组,在下层进行访问
                visit[i] = 1;//将该点记录已经访问
                sum += 1;//认识的总人数+1
                last = i;//记录当下层为最后一个结点
            }
        }
        
        if(t == levellast)
        {
            //如果已经到了一层的最后一个结点
            level += 1;//层数+1
            levellast = last;
        }
        if(level == 6) break;
    }
    return sum;//返回总人数
}
​
int main(void)
{
    int num1, num2;//人数、关系数
    printf("请输入人数:");
    scanf("%d",&num1);//输入人数
    printf("请输入关系数:");
    scanf("%d",&num2);//输入关系数
    int **relation;//二维数组,记录两人之间的关系矩阵
    relation = (int**)malloc(sizeof(int*)*(num1 + 1));//分配空间
    for(int i = 0;i <= num1; i++)
    {
        //分配空间
        *(relation + i) = (int*)malloc(sizeof(int)*(num1 + 1));
    }
    
    for(int i = 0;i <= num2 - 1; i++)
    {
        //记录一个关系的两端结点
        int x,y;
        scanf("%d",&x);
        scanf("%d",&y);
        relation[x][y] = relation[y][x] = 1;//无向图的关系矩阵一定是对称的
    }//关系录入完成
​
    for(int i = 1;i <= num1; i++)
    {
        //循环输出每个人的六度空间内的认识人的比
        printf("%d:%.2f%%\n",i,((float)count(i, num1, relation)/num1)*100);
    }
}

4.遍历定义

从已给的连通图中某一顶点出发,沿着一生边访遍图中所有的顶点,且使每个顶点仅被访问一次,就叫做图的遍历,它是图的基本运算

 

 遍历实质:找每个顶点的邻接点的过程。

图的特点

图中可能存在回路,且图的任一顶点都可能与其它顶点相通,在访问完某个顶点之后可能会沿着某些边又回到了曾经访问过的顶点。

图常用的遍历

深度优先搜索(Depth_First Search——DFS)

广度优先搜索(Breadth_Frist Search——BFS)

5.深度优先遍历(DFS)

方法

  1. 在访问图中某一起始顶点v后,由y出发,访问它的任一邻接顶点w1

  2. 再从w1出发,访问与w1邻接但还未被访问过的顶点w2

  3. 然后再从w2出发,进行类似的访问,…

  4. 如此进行下去直至到达所有的邻接顶点都被访问过的顶点u为止。

  5. 接着,退回一步,退到前一次刚访问过的顶点,看是否还有其它没有被访问的邻接顶点。

  6. 如果有,则访问此顶点,之后再从此顶点出发,进行与前述类似的访问;

  7. 如果没有,就再退回一步进行搜索。重复上述过程,直到连通图中所有顶点都被访问过为止。

例如

 

 深度优先搜索遍历算法的实现

邻接矩阵表示的无向图深度遍历实现:

 

 通过邻接矩阵进行遍历,然后再通过辅助数组进行记录,如果遍历发现没法往下寻找结点,那么就逐步后退倒上一节点处在进行遍历,直至找到整个图中所有节点。

代码

void DFS(AMGraph G, int v)
{
    //图G为邻接矩阵类型
    cout << v;//访问第v个顶点
    visited[v] = true;//记录该结点
    for(w = 0;w < G.vexnum; w++)
    {
        //依次检查邻接矩阵v所在的行
        if((G.arcs[v][w] != 0)&&(!visited[w]))
            DFS(G,w);
        //w是v的邻接点,如果w未访问,则递归调用DFS
    }
}

DFS算法效率分析

用邻接矩阵来表示图,遍历图中每一个顶点都要从头扫描该顶点所在的行,时间复杂度为O(n^2)

用邻接表来表示图,虽然有2e个表结点,但只需扫描e个结点即可完成遍历,加上访问n个头结点的时间,时间复杂度为O(n+e)。

结论:

稠密图适行在邻接矩阵上进行深度遍历;

稀疏图通于在邻接表上进行深度遍历。

5.广度优先搜索(BFS)

层层遍历,不再是一条路走完的形式。

方法:从图的某一结点出发,首先依次访问该结点的所有邻接点Vi1, Vi2,....,Vin,再按这些顶点被访问的先后次序依次访问与它们相邻接的所有未被访问的顶点,重复此过程,直至所有顶点均被访问为止。

 

 通过队列的算法和辅助数组,对图进行广度优先遍历

按广度优先非递归遍历连通图G

void BFS(Graph G, int v)
{
    //按广度优先非递归遍历连通图G
    cout << v;
    visited[v] = true;//访问第v个顶点
    InitQueue(Q);//辅助队列Q初始化,置空
    EnQueue(Q, v);//v进队
    while(!QueueEmpty(Q))
    {
        //队列非空
        DeQueue(Q, u);//队头元素出队并置为u
        for(w = FirstAdjVex(G, u);w >= 0;w = NextAdjVex(G, u, w))
        {
            if(!visited[w])
            {
                //w为u的尚未访问的邻接顶点
                cout << w;
                visited[w] = true;
                EnQueue(Q, w);//w进队
            }
        }
    }
}

BFS算法效率分析

如果使用邻接矩阵,则BFS对于每一个被访问到的顶点,都要循环检测矩阵中的整整一行(n个元素),总的时间代价为O(n^2)。

用邻接表来表示图,虽然有2个表结点,但只需扫描e个结点即可完成遍历,加上访问n个头结点的时间,时间复杂度为O(n+e)。

DFS与BFS算法效率比较

  1. 空间复杂度相同,都是O(n)(借用堆栈或队列)

  2. 时间复杂度只与存储结构,(邻接矩阵或邻接表)有关。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/459566.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

用 ChatGPT 进行阅读理解题目的问答

阅读理解出题 阅读理解题是语言学习过程中一种重要的练习方式。无论语文还是英语考试中&#xff0c;阅读理解题都占有相当大的分值。ChatGPT 作为一种大语言模型&#xff0c;在处理自然语言理解任务中具有很大的优势。广大教师和学生家长们&#xff0c;都可以尝试用 ChatGPT 进…

借灰姑娘的手,讲述js混淆加密的美丽

这个故事的主角是灰姑娘&#xff0c;她有一个重要的秘密&#xff0c;需要将其保护起来。但是&#xff0c;她发现她的网站上的 JavaScript 代码很容易被其他人阅读和修改&#xff0c;为了保护这个秘密&#xff0c;她需要采用一些混淆和加密技术。 以下是她使用的一些技术&#…

数据结构与算法学习:二叉树的后序遍历的递归与非递归实现,以及非递归实现中的流程控制的说明。

需求二叉树&#xff1a; 采用二叉树后序遍历非递归算法。设置一个指针p初始指向树根&#xff0c;p先入栈&#xff0c;而后使得p指向它的左孩子p->firstchild&#xff0c;重复操作&#xff0c;使得每个左孩子都依次入栈&#xff0c;同时初始化一个Treenode*类型的指针pre&…

GPT:你知道这五年我怎么过的么?

时间轴 GPT 首先最初版的GPT&#xff0c;来源于论文Improving Language Understanding by Generative Pre-Training&#xff08;翻译过来就是&#xff1a;使用通用的预训练来提升语言的理解能力&#xff09;。GPT这个名字其实并没有在论文中提到过&#xff0c;后人将论文名最后…

【软件测试】知识图

文章目录 第1章 软件测试概述1.1 软件、软件危机和软件工程1.1.1 基本概念1.1.2 软件工程的目标及其一般开发过程1.1.3 软件过程模型 1.2 软件缺陷与软件故障1.2.1 基本概念1.2.2 典型案例 1.3 软件测试的概念1.3.1 软件测试的定义1.3.2 软件测试的目的&#xff1a;保证软件产品…

备忘录设计模式解读

目录 问题引进 游戏角色状态恢复问题 传统方案解决游戏角色恢复 传统的方式的问题分析 备忘录模式基本介绍 基本介绍 备忘录模式的原理类图 对原理类图的说明 游戏角色恢复状态实例 应用实例要求 思路分析和图解(类图) 代码实战 备忘录模式的注意事项和细节 问题引…

了解网络攻击:类型、策略和技术

近年来&#xff0c;网络攻击变得越来越普遍&#xff0c;个人和企业都成为各种网络威胁的受害者。了解不同类型的网络攻击&#xff0c;以及网络罪犯使用的策略和技术&#xff0c;对于保护您的个人和企业数据免受这些威胁至关重要。 有几种不同类型的网络攻击&#xff0c;每种都…

Linux 查看进程和线程CPU和内存占用情况

文章目录 Linux 查看进程有哪些线程Linux 查看程序内存占用情况 top和free等命令Linux 查看进程、线程数量 Linux 查看进程有哪些线程 linux 下查看进程内的线程有哪些 首先通过进程名称&#xff0c;假设为SensorDev 找到pid号。 ps -p {pid} -T 可以得到该进程里面运行的各…

Mapbox多边形光效晕影特效的实现

相信很多大屏需要展示行政区的发光效果,像下图这样的: 这相比普通的多边形样式,边界有了渐变发光的效果,那么这篇章交给大家如何实现这样一个效果,让你的行政区,地块之类的多边形要素展示成发光的效果。 我们不依赖底层的webgl技术,也不用涉及到什么着色器的概念,我…

【LeetCode: 1143. 最长公共子序列 | 暴力递归=>记忆化搜索=>动态规划】

&#x1f680; 算法题 &#x1f680; &#x1f332; 算法刷题专栏 | 面试必备算法 | 面试高频算法 &#x1f340; &#x1f332; 越难的东西,越要努力坚持&#xff0c;因为它具有很高的价值&#xff0c;算法就是这样✨ &#x1f332; 作者简介&#xff1a;硕风和炜&#xff0c;…

Springcloud连接nacos集群,nacos地址配置为nginx,报错:requst nacos server failed

先说下版本&#xff1a; Spring cloud&#xff1a; Hoxton.SR12 spring.cloud.alibaba&#xff1a; 2.2.9.RELEASE spring.boot&#xff1a; 2.3.12.RELEASE Linux Centos7 nacos-server&#xff1a;2.1.0 nginx&#xff1a; 1.20.2 环境说明&#xff1a; nacos正常搭建三个集…

Burpsuite双层代理以及抓https与app包设置

Burp Suite是一款用于Web应用程序安全测试的集成式平台。它由PortSwigger Ltd.开发&#xff0c;是一个功能强大的工具&#xff0c;用于发现Web应用程序的漏洞和安全问题&#xff0c;例如跨站点脚本&#xff08;XSS&#xff09;、SQL注入、会话劫持等。它包括多个模块&#xff0…

酒厂酒业IP网络广播系统建设方案-基于局域网的新一代交互智慧酒厂酒业IP广播设计指南

酒厂酒业IP网络广播系统建设方案-基于局域网的新一代交互智酒业酒厂IP广播系统设计指南 由北京海特伟业任洪卓发布于2023年4月25日 一、酒厂酒业IP网络广播系统建设需求 随着中国经济的快速稳步发展&#xff0c;中国白酒行业也迎来了黄金时期&#xff0c;产品规模、销售业绩等…

NLP 与 Python:构建知识图谱实战案例

概括 积累了一两周&#xff0c;好久没做笔记了&#xff0c;今天&#xff0c;我将展示在之前两周的实战经验&#xff1a;如何使用 Python 和自然语言处理构建知识图谱。 网络图是一种数学结构&#xff0c;用于表示点之间的关系&#xff0c;可通过无向/有向图结构进行可视化展示…

【2023团体程序设计天梯赛CCCC】GPLT2023,L1~L2部分(PTA,L1-089~L1-096,L2-045~L2-048)题解代码复盘

文章目录 概要L1-089 最好的文档 5L1-090 什么是机器学习 5L1-091 程序员买包子 10L1-092 进化论 10L1-093 猜帽子游戏 15L1-094 剪切粘贴 15L1-095 分寝室 20L1-096 谁管谁叫爹 20L2-045 堆宝塔 25L2-046 天梯赛的赛场安排L2-047 锦标赛 25L2-048 寻宝图 25L3-035 完美树&…

GIII EDI 需求分析

G-III成衣集团是一家美国服装公司&#xff0c;拥有30多个授权和自有品牌如&#xff1a;Calvin Klein、Tommy Hilfiger、Guess以及Levi’s等&#xff0c;在全球拥有众多的零售合作伙伴和销售渠道&#xff0c;并致力于提供时尚、高质量和价格合理的服装产品。 GIII EDI 需求 传…

版本控制工具之Git基本操作

Git 相对较新的版本控制工具&#xff0c;特点为分布式。 每一台客户端都具有完整的版本备份&#xff0c;所有的版本提交都不需要依赖中心服务器。只有在多人协同时&#xff0c;服务器会处理并发情况。 一、Git 环境安装 &#x1f449;链接&#xff1a;https://blog.csdn.net/w…

「速通Shell」初次走近Shell,Shell是什么?

目录 初次走进ShellShell是什么Shell工作原理 Shell分类Shell的优势 第一个Shell脚本Hello WorldShell执行方式绝对路径执行相对路径执行脚本命令执行系统命令执行 总结 对于开发者来说&#xff0c;除了掌握Java、C/C等主要编程语言外&#xff0c;还需要掌握支撑性的工具语言和…

Vagrant 安装

系列文章目录 文章目录 系列文章目录前言一、安装地址二、安装步骤注意事项三、常用命令四、问题总结 前言 例如&#xff1a;随着人工智能的不断发展&#xff0c;机器学习这门技术也越来越重要&#xff0c;很多人都开启了学习机器学习&#xff0c;本文就介绍了机器学习的基础内…

机器人方向的人工智能工具是助手还是平替

本文内容严格按创作模板发布&#xff1a; 近日育碧开发了人工智能工具 Ghostwriter&#xff0c;可以一键生成游戏NPC对话。不少游戏开发者担心AI写手工具的出现会让自己“饭碗”不保&#xff0c;但Swanson表示这个工具只是为了提供第一稿的 barks来减少对话生成工作的繁琐度。…