图的建立基本操作

#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>  // 添加头文件

#define MAX_VERTEX_NUM 100 //图中最大顶点数


//struct ArcNode* nextarc;
//ArcNode* firstarc;
//这两个是很有必要的,如果你没有这两个指针,你就无法判断当前的顶点是哪一个
// adj是邻接点,firstarc和nextarc分别代表当前和下一个点。

//图的邻接表存储结构定义
typedef struct ArcNode {
    int adjvex;              //邻接点在数组中的位置下标
    struct ArcNode* nextarc; //指向下一个邻接点的指针
} ArcNode;

typedef struct VNode {
    char data;             //顶点信息
    ArcNode* firstarc;     //指向第一个邻接点的指针
} VNode, AdjList[MAX_VERTEX_NUM];

//使用结构体嵌套的方式,把两个顶点还有边都给嵌套起来

typedef struct {
    AdjList vertices;      //邻接表,也就是顶点的意思
    int vexnum, arcnum;    //顶点数和弧数
    bool is_directed;      //是否是有向图
} ALGraph;

//查找顶点的第一个邻接点
int FirstAdjVex(ALGraph G, int v)
{
    if (G.vertices[v].firstarc != NULL) {
        return G.vertices[v].firstarc->adjvex;
    }
    else {
        return -1;  //返回“空”
    }
}

//查找顶点的下一个邻接点


int NextAdjVex(ALGraph G, int v, int w)
{
    ArcNode* p = G.vertices[v].firstarc;
    while (p != NULL && p->adjvex != w) {
        p = p->nextarc;
    }
    //先找到w先,题目说相对于w的下一个邻接点

    if (p != NULL && p->nextarc != NULL) {
        return p->nextarc->adjvex;
    }
    else {
        return -1;  //返回“空”
    }
}

//v是图中的某个顶点也就是 ArcNode* p,即G.vertices[v].firstarc;
//而p->adjvex != w这是v当前的邻接点
//那下一个邻接点自然就是 p->nextarc->adjvex了

//一般节点的下一个节点就只有一个,
// 不是DFS不会一直往下面找,下一个就到空了,所以说是合理的循环。
//题目的意思就是说,w是v的临接节点。

//插入一个新顶点
void InsertVex(ALGraph* G, char v)
{
    if (G->vexnum >= MAX_VERTEX_NUM) {
        printf("Error: The graph is full!\n");
        return;
    }
    G->vertices[G->vexnum].data = v;//存放点
    G->vertices[G->vexnum].firstarc = NULL;//存放其邻接点
    G->vexnum++;
}

//删除一个顶点及其相关的弧
void DeleteVex(ALGraph* G, char v)
{
    int i, j;
    ArcNode* p, * q;
    i = LocateVex(*G, v);//找到顶点位置
    if (i == -1) {
        printf("Error: Vertex not found!\n");
        return;
    }
    //删除与该顶点相关的弧
    for (j = 0; j < G->vexnum; j++) {
        p = G->vertices[j].firstarc;//寻找每个图中的所有的第一个邻接点。
        while (p != NULL) {
            if (p->adjvex == i) {
                //如果邻接点是该下标的点
                q = p->nextarc;
 //因为是第一个邻接点,不存在有无前后的问题,所以直接接上下面一段就行了
                free(p);
                G->arcnum--;
                p = q;
               
            }
            else {
                //指针要动的只是邻接点为i的点
                //在它后面的点,直接下标往前移一位就行了
                if (p->adjvex > i) {
                    p->adjvex--;
                    //就是已经明确,我们要删除i点,
                    // 那么其他所有的邻接点下标位置都要往前移1位
                    //因为要删除i点了,那么所有的点的位置都要发生变化,就像数组那样
                    //删除一个元素,全部元素都要往前移动。

                    //至于前面的 if (p->adjvex == i) 
                    //那是因为直接将p点直接删除了,自然就不需要再管它的位置在哪里了
                }
                p = p->nextarc;//继续往下找,重复当前操作。
            }

 //           在这段代码中,adjvex属性表示一个弧所指向的顶点在图中的索引位置。
 //          当删除与指定顶点相关的弧时,如果不进行调整,
 //               那些位于指定顶点之后的顶点索引将会发生变化。
 //通过将p指向的弧的adjvex属性减1,可以确保顶点之间的编号连续性。
 //               也就是说,删除一个顶点之后,
 //               它之后的顶点的索引都需要减少1,以保持对应关系的正确性。
 //              

 //               例如,假设原来顶点i之后的顶点分别为i + 1、i + 2、i + 3,
 //               删除与顶点i相关的弧之后,i + 1变成了i,i + 2变成了i + 1,i + 3变成了i + 2,以此类推。

 //               这样做的目的是为了在删除操作之后,
 //               依然可以通过顶点的索引来正确访问和操作图的邻接信息。
        }
    }

    //删除该顶点本身
    //弧跟顶点是不一样的概念
    for (j = i + 1; j < G->vexnum; j++) {
        G->vertices[j - 1] = G->vertices[j];
    }
    for (j = 0; j < G->vexnum; j++) {
        p = G->vertices[j].firstarc;
        while (p != NULL) {
            if (p->adjvex > i) {
                p->adjvex--;
            }
            p = p->nextarc;
        }
    }
    G->vexnum--;


      //  第一次循环是在删除与指定顶点相关的弧时进行的。
      //当找到邻接点的索引大于被删除顶点的索引时,将其减1,以保持顶点之间的编号连续性。

      //  第二次循环是在删除顶点本身后进行的。对于每个顶点,
      //      遍历其邻接链表,如果邻接点的索引大于被删除顶点的索引,
      //      则将其减1,同样是为了保持邻接信息的正确性。

      //  这两次循环的目的都是使得删除一个顶点后,
      //      其他顶点的索引发生相应变化,以确保后续的访问和操作仍然有效。

}

//邻接表表示当前顶点中
//分支到的任何一个下一个的顶点。
//
//而每一次都指针指向下一个然后删除原先的p节点。
//也就等于断开了所有与当前节点的链接,
//也就成功消除了它的所有弧了。

//插入一条新弧
void InsertArc(ALGraph* G, char v, char w)
{
    int i, j;
    ArcNode* p, * q;
    i = LocateVex(*G, v);
    j = LocateVex(*G, w);
    if (i == -1 || j == -1) {
        printf("Error: Vertex not found!\n");
        return;
    }
    p = (ArcNode*)malloc(sizeof(ArcNode));
    p->adjvex = j;//邻接点是j
    p->nextarc = G->vertices[i].firstarc;//因为是插入,所以用next而非first
    G->vertices[i].firstarc = p;
    //p代替了 G->vertices[i].firstarc
    //就是因为p中还有adj这个j的值,
    /*就是为了插入新弧才代替 G->vertices[i].firstarc的*/
        //所以说原地址的 G->vertices[i].firstarc把它换成p就成功插入了。
    //p是新的头结点,所以原来的地址改成p的指针没毛病

  /*  首先,p->nextarc = G->vertices[i].firstarc; 
    将p的下一个指针指向顶点i的原第一个邻接边,这样p就成为了新的第一个邻接边。

        然后,G->vertices[i].firstarc = p;
    将顶点i的第一个邻接边指针指向p,即p成为了顶点i邻接表的新的第一个节点。*/

  /*  又忘了指针偏移了,你记住,变动链表以后一定要把指针的头指向新的头结点,不然全部都错了!*/

    G->arcnum++;
    if (!G->is_directed) {
        q = (ArcNode*)malloc(sizeof(ArcNode));
        q->adjvex = i;
        q->nextarc = G->vertices[j].firstarc;
        G->vertices[j].firstarc = q;
        G->arcnum++;
    }
}
//p->adjvex  放置末端的点
//G->vertices[i].firstarc;  放置前端的点

//问:p->nextarc = G->vertices[i].firstarc; 为什么不直接写成p = G->vertices[i].firstarc;?

//这是因为G->vertices[i].firstarc是一个指向边表结构体的指针,
//而p是一个指向边表结构体的指针变量。
//如果将两者直接赋值,会导致p与G->vertices[i].firstarc指向同一块内存区域,
//任何一方对该内存区域的修改都会影响另一方。这可能不是我们期望的结果。
//
//正确的做法是让p指向与G->vertices[i].firstarc相同的内存地址,
//但是它们是两个不同的指针变量,互相独立,对其中一个指针变量的修改不会影响另一个。
//所以应该写成p->nextarc = G->vertices[i].firstarc,
//这样可以保证p指向与G->vertices[i].firstarc相同的内存地址,
//但是它们是两个不同的指针变量,互相独立,对其中一个指针变量的修改不会影响另一个。

//总结:指针的特殊性问题,直接赋值就表示指向同一个地点,p指针就等于所指向的目标的地址。
//不独立,修改p也就等于修改了原地址的值。所以必须要用next才能成为独立的指针。
//相当于我只是使用了原地址的值,但我是作为独立的一个变量


//删除一条弧
void DeleteArc(ALGraph* G, char v, char w)
{
    int i, j;
    ArcNode* p, * q;
    i = LocateVex(*G, v);
    j = LocateVex(*G, w);
    if (i == -1 || j == -1) {
        printf("Error: Vertex not found!\n");
        return;
    }
    p = G->vertices[i].firstarc;//从起点开始找
    q = NULL;
    while (p != NULL && p->adjvex != j) {
        q = p;
        //为什么要保存前面的顶点?
        //为了储存前面表的数据,方便后面衔接。
        p = p->nextarc;
    }
    if (p != NULL) {
        if (q == NULL) {
            G->vertices[i].firstarc = p->nextarc; 
            //只有一个点的情况
        }
       
        else {
            q->nextarc = p->nextarc;
        }
        free(p);
        G->arcnum--;
    }
    if (!G->is_directed) {
        p = G->vertices[j].firstarc;
        q = NULL;
        while (p != NULL && p->adjvex != i) {
            q = p;
            p = p->nextarc;
        }
        if (p != NULL) {
            if (q == NULL) {
                G->vertices[j].firstarc = p->nextarc;
            }
            else {
                q->nextarc = p->nextarc;
            }
            free(p);
            G->arcnum--;
        }
    }
}

//定位某个顶点的位置
int LocateVex(ALGraph G, char v)
{
    int i;
    for (i = 0; i < G.vexnum; i++) {
        if (G.vertices[i].data == v) {
            return i;
        }
    }
    return -1;  //未找到
}

int main()
{
    ALGraph G;
    G.is_directed = false;  // 设置图为无向图
    G.vexnum = 0;
    G.arcnum = 0;

    InsertVex(&G, 'A');
    InsertVex(&G, 'B');
    InsertVex(&G, 'C');
    InsertVex(&G, 'D');

    InsertArc(&G, 'A', 'B');
    InsertArc(&G, 'A', 'C');
    InsertArc(&G, 'B', 'D');
    InsertArc(&G, 'C', 'D');

    printf("The first adjacent vertex of A is %c\n", G.vertices[0].firstarc->adjvex + 'A');
    printf("The next adjacent vertex of A after B is %c\n", NextAdjVex(G, 0, LocateVex(G, 'B')) + 'A');

    DeleteArc(&G, 'B', 'D');
    DeleteVex(&G, 'C');

    return 0;
}

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

力扣114. 二叉树展开为链表(java,用树模拟链表)

Problem: 114. 二叉树展开为链表 文章目录 题目描述思路解题方法复杂度Code 题目描述 给你二叉树的根结点 root &#xff0c;请你将它展开为一个单链表&#xff1a; 1.展开后的单链表应该同样使用 TreeNode &#xff0c;其中 right 子指针指向链表中下一个结点&#xff0c;而左…

Selenium 4.11 正式发布--再也不用手动更新chrome driver 了

Selenium 4.11.0 正式发布了&#xff0c;先来看一下主要特性。 Chrome DevTools支持的版本现在是&#xff1a;v113、v114和v115&#xff08;Firefox仍然对所有版本使用v85&#xff09; 通过Selenium Manager支持Chrome For Testing&#xff08;CfT&#xff09; Selenium Manag…

LeetCode Hot100 105.从前序与中序遍历序列构造二叉树

题目&#xff1a;给定两个整数数组 preorder 和 inorder &#xff0c;其中 preorder 是二叉树的先序遍历&#xff0c; inorder 是同一棵树的中序遍历&#xff0c;请构造二叉树并返回其根节点。 代码&#xff1a; class Solution {private Map<Integer, Integer> indexM…

midjourney过时了?如何使用基于LCM的绘图技术画出你心中的画卷。

生成 AI 艺术在近年来迅速发展&#xff0c;吸引了数百万用户。然而&#xff0c;传统的生成 AI 艺术需要等待几秒钟或几分钟才能生成&#xff0c;这对于快节奏的现代社会来说并不理想。 近日&#xff0c;中国清华大学和 AI 代码共享平台 HuggingFace 联合开发了一项新的机器学习…

性能压测工具:wrk

一般我们压测的时候&#xff0c;需要了解衡量系统性能的一些参数指标&#xff0c;比如。 1、性能指标简介 1.1 延迟 简单易懂。green:一般指响应时间 95线&#xff1a;P95。平均100%的请求中95%已经响应的时间 99线&#xff1a;P99。平均100%的请求中99%已经响应的时间 平…

2023年亚太杯数学建模A题水果采摘机器人的图像识别功能(matlab 部分代码)

对于1-4问针对的是附录1 中的数据 clc; close all; clear; % 图像文件夹路径 folder_path E:/新建文件夹/yatai/Attachment/Attachment 1/; % 图像文件列表 image_files dir(fullfile(folder_path, *.jpg)); % 假设所有图片都是jpg格式% 解析文件名中的数字&#xff0c;并转…

2023年汉字小达人市级比赛在线模拟题的使用顺序、建议和常见问题

今天是2023年11月25日&#xff0c;星期六&#xff0c;上午举办了2023年第八届上海小学生古诗文大会的复选活动&#xff08;复赛&#xff09;&#xff0c;结束了复选活动&#xff0c;很多学霸孩子们马上就开始投入到第十届汉字小达人的市级活动&#xff08;市级比赛&#xff09;…

STM32 配置中断常用库函数

单片机学习 目录 一、配置AFIO相关库函数 1.1函数GPIO_AFIODeInit 1.2函数GPIO_EventOutputConfig 1.3函数GPIO_EventOutputCmd 1.4函数GPIO_EXTILineConfig 二、配置EXTI相关库函数 2.1函数EXTI_DeInit 2.2函数EXTI_Init 2.3函数EXTI_StructInit 2.4函数 EXTI_Gener…

python_接口自动化测试框架

&#x1f4e2;专注于分享软件测试干货内容&#xff0c;欢迎点赞 &#x1f44d; 收藏 ⭐留言 &#x1f4dd; 如有错误敬请指正&#xff01;&#x1f4e2;交流讨论&#xff1a;欢迎加入我们一起学习&#xff01;&#x1f4e2;资源分享&#xff1a;耗时200小时精选的「软件测试」资…

02_MySQL体系结构及数据文件介绍

#课程目标 了解MySQL的体系结构了解MySQL常见的日志文件及作用了解事务的控制语句&#xff0c;提交和回滚能够查看当前数据库的版本和用户了解MySQL数据库如何存放数据能在使用SQL语句创建、删除数据库 #一、MySQL的体系结构 ##1、客户端(连接者) MySQL的客户端可以是某个客户…

vue2-006——使用脚手架搭建vue2项目+项目结构分析

一、创建项目&#xff1a;vue create 项目名 D:\EnyiWang\Documents\myStudy\vue>vue create vue_testVue CLI v5.0.8 ? Please pick a preset: Default ([Vue 2] babel, eslint)Vue CLI v5.0.8 ✨ Creating project in D:\EnyiWang\Documents\myStudy\vue\vue_test. &am…

Java基准测试工具JMH的简介与使用

JMH是一套Java基准测试工具&#xff0c;用于对Java执行进行基准测试以及生成测试报告。平时应用于Java一些基础Api或者一些工具类这种离开网络因素的纯系统测试。 使用方式 maven引入&#xff1a; <dependency><groupId>org.openjdk.jmh</groupId><art…

postman和Jmeter做接口测试的区别(经验之谈)

接口测试的目的 API 测试作为集成测试的一部分&#xff0c;经过被测应用的接口&#xff08;API&#xff09;来确定是否在功能、可靠性、性能和安全方面达到预期的软件测试。因为 API 都没有 GUI 界面&#xff0c;API 测试都是在通信层进行的。 1.建立接口用例集 Postman功能…

QT visual stdio加载动态库报错126问题

报错126是找不到指定的模块 QT 查看构建目录&#xff0c;将依赖的动态库放到该目录下即可成功 visual stdio将依赖的动态库放到运行目录 在vs中使用导出类的动态库时&#xff0c;不但需要将对应的.dll放到对应的目录下&#xff0c;还需要将该动态库对应的.lib添加到如下配置才…

AJAX技术-04-- 跨域说明

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 文章目录 1 同源策略同源策略介绍规定要求 请求协议://域名:端口号 关于同源策略练习关于同源策略总结 2.JSONPJSONP原理说明关于JSONP优化 3.CORS介绍介绍不允许跨域说明跨域…

JavaScript解构数组

还记得之前我们是如何读取到数组里面的元素的么&#xff1f; const arr [2, 3, 4]; const a arr[0]; const b arr[1]; const c arr[2];然后通过这个方式去读取数组中的数据&#xff1b; 现在我们可以使用解构赋值的方法去实现 const [x, y, z] arr; console.log(x, y, …

Python 哈希表的实现——字典

哈喽大家好&#xff0c;我是咸鱼 接触过 Python 的小伙伴应该对【字典】这一数据类型都了解吧 虽然 Python 没有显式名称为“哈希表”的内置数据结构&#xff0c;但是字典是哈希表实现的数据结构 在 Python 中&#xff0c;字典的键&#xff08;key&#xff09;被哈希&#x…

接口测试之文件上传

在日常工作中&#xff0c;经常有上传文件功能的测试场景&#xff0c;因此&#xff0c;本文介绍两种主流编写上传文件接口测试脚本的方法。 首先&#xff0c;要知道文件上传的一般原理&#xff1a;客户端根据文件路径读取文件内容&#xff0c;将文件内容转换成二进制文件流的格…

Halcon [fill_up_shape],[close_circle],[dilation_circle]和[shape_trans]图像处理时填充区别

文章目录 文章专栏前言两者的区别fill_up_shapeshape_transclose_circledilation_circle 总结 文章专栏 我的Halcon开发 CSDN专栏 前言 本文用的案例是&#xff1a;Example: %HALCONEXAMPLES%/hdevelop/Applications/Completeness-Check/ball.hdev 两者的区别 [shape_trans]是…

1、Mysql架构与历史

Mysql逻辑架构 最上层是服务并不是Mysql所独有的&#xff0c;大多数基于网络的客户端/服务器的工具或者服务都有类似的架构&#xff0c;比如连接处理&#xff0c;授权认证&#xff0c;安全等。 第二层是Mysql比较有意思的部分。大多数Mysql的核心服务都在这一层&#xff0c;…