图的建立基本操作

news2024/11/16 9:44:10

#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;
}

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

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

相关文章

2.前端--HTML标签1【2023.11.25】

1.基本语法规范 HTML 标签是由尖括号包围的关键词&#xff0c;例如 <html>。HTML 标签通常是成对出现的&#xff0c;例如 和 &#xff0c;我们称为双标签。有些特殊的标签必须是单个标签&#xff08;极少情况&#xff09;&#xff0c;例如 <br />我们称为单标签。 …

2022年12月 Scratch(三级)真题解析#中国电子学会#全国青少年软件编程等级考试

Scratch等级考试(1~4级)全部真题・点这里 一、单选题(共25题,每题2分,共50分) 第1题 默认小猫角色和气球角色都是显示状态,小猫程序如下图所示,气球没有程序,点击绿旗,舞台上最终显示的效果是?( ) A:可能出现6个不同位置的小猫和6个小球 B:可能出现6个不同位…

Python基于jieba+wordcloud实现文本分词、词频统计、条形图绘制及不同主题的词云图绘制

目录 序言&#xff1a;第三方库及所需材料函数模块介绍分词词频统计条形图绘制词云绘制主函数 效果预览全部代码 序言&#xff1a;第三方库及所需材料 编程语言&#xff1a;Python3.9。 编程环境&#xff1a;Anaconda3&#xff0c;Spyder5。 使用到的主要第三方库&#xff1a;…

P12 C++静态关键字static

目录 01 前言 02 静态变量static 03 extern关键字 04 静态函数 最后的话 01 前言 static 关键字在 C 中有两个意思&#xff0c;这个取决于上下文。 第一种情况是在类或结构体外部使用 static 关键字&#xff0c;另一种是在类或者结构体内部使用 static。 类外面的 static…

vscode运行c++程序如何支持c++11

参考https://zhuanlan.zhihu.com/p/269244754 更改setting.json文件

[环境配置]vscode免密ssh的设置流程

测试环境&#xff1a; windows 11 ubuntu16.04 vmware 第一步&#xff1a;生成密钥 cmd打开输入&#xff1a;ssh-keygen -t rsa 一路回车后可以在C:\Users\用户名\.ssh路径看到id_rsa.pub&#xff0c;我们打开这个文件&#xff0c;用记事本打开即可&#xff0c;然后复制里…

二十一、数组(6)

本章概要 数组排序Arrays.sort的使用并行排序binarySearch二分查找parallelPrefix并行前缀 数组排序 根据对象的实际类型执行比较排序。一种方法是为不同的类型编写对应的排序方法&#xff0c;但是这样的代码不能复用。 编程设计的一个主要目标是“将易变的元素与稳定的元素…

王者荣耀——Java

代码如下&#xff1a; sxt Background package sxt;import java.awt.*; //背景类 public class Background extends GameObject{public Background(GameFrame gameFrame) {super(gameFrame);}Image bg Toolkit.getDefaultToolkit().getImage("C:\\Users\\24465\\Desk…

Hibernate 脏检查和刷新缓存机制

刷新缓存: Session是Hibernate向应用程序提供的操作数据库的主要接口,它提供了基本的保存,更新,删除和加载java对象的方法,Session具有一个缓存,可以管理和追踪所有持久化对象,对象和数据库中的相关记录对应,在某些时间点,Session会根据缓存中对象的变化来执行相关SQL语句,将对…

【负载均衡】这些内容你需要知道下

&#x1f604;作者简介&#xff1a; 小曾同学.com,一个致力于测试开发的博主⛽️&#xff0c;主要职责&#xff1a;测试开发、CI/CD 如果文章知识点有错误的地方&#xff0c;还请大家指正&#xff0c;让我们一起学习&#xff0c;一起进步。 &#x1f60a; 座右铭&#xff1a;不…

【刷题宝典NO.4】

目录 公交站间的距离 生命游戏 公交站间的距离 https://leetcode.cn/problems/distance-between-bus-stops/ 环形公交路线上有 n 个站&#xff0c;按次序从 0 到 n - 1 进行编号。我们已知每一对相邻公交站之间的距离&#xff0c;distance[i] 表示编号为 i 的车站和编号为 …

Kotlin学习——流程控制,when,循环,range工具 kt里的equals if实现类似三元表达式的效果

Kotlin 是一门现代但已成熟的编程语言&#xff0c;旨在让开发人员更幸福快乐。 它简洁、安全、可与 Java 及其他语言互操作&#xff0c;并提供了多种方式在多个平台间复用代码&#xff0c;以实现高效编程。 https://play.kotlinlang.org/byExample/01_introduction/02_Functio…

KVM虚拟机的NAT网络模式原理及过程展示

NAT的方式及原理 NAT方式是KVM安装后的默认方式。 它支持主机与虚拟机的互访&#xff0c;同时也支持虚拟机访问互联网&#xff0c;但不支持外界访问虚拟机。 default是宿主机安装虚拟机支持模块的时候自动安装的。 其中 virbr0是由宿主机虚拟机支持模块安装时产生的虚拟网络接…

Python编程技巧 – Lambda函数

Python编程技巧 – Lambda函数 Python Programming Skills – Lambda Functions By JacksonML 2023-11-25 在前文介绍过Python函数&#xff0c;一个函数用def关键字声明&#xff0c;不带或带有参数&#xff0c;并以冒号结束&#xff1b;函数块根据结果由解释器确定返回值动态…

【Linux】:信号的产生

信号 一.前台进程和后台进程1.前台进程2。后台进程3.总结 二.自定义信号动作接口三.信号的产生1.键盘组合键2.kill信号进程pid3.系统调用1.kill函数2.raise函数3.abort函数 四.异常五.软件条件六.通过终端按键产生信号 一.前台进程和后台进程 1.前台进程 一个简单的代码演示 …

【Spring集成MyBatis】MyBatis注解开发

文章目录 1. MyBatis的常用注解2. 基于注解的MyBatis增删改查增删改查完整代码加载映射关系测试代码 3. MyBatis的注解实现复杂映射开发一对一操作的实现一对一操作实现的第二种方式一对多操作的实现多对多操作实现 1. MyBatis的常用注解 2. 基于注解的MyBatis增删改查 使用注…

Leetcode—83.删除排序链表中的重复元素【简单】

2023每日刷题&#xff08;四十&#xff09; Leetcode—83.删除排序链表中的重复元素 实现代码 /*** Definition for singly-linked list.* struct ListNode {* int val;* struct ListNode *next;* };*/ struct ListNode* deleteDuplicates(struct ListNode* head) {i…

张弛语言课,战争电影配音声音细致声音来复原战场

为战争片进行声音配音是一项挑战性的工作&#xff0c;它要求精确再现战场的紧张感和复杂情绪。配音人员和声音设计团队必须创造出真实的战争声景&#xff0c;从战斗的轰鸣声到士兵的呐喊&#xff0c;这些声音元素都需细致打造&#xff0c;以传递战争的惨烈、英勇和人性的复杂。…

工作中死循环害死人

背景&#xff1a;研发的一段代码&#xff0c;循环一直没有跳出&#xff0c;导致其他依赖逻辑有问题&#xff0c;生产事故导致9万左右数据不正常。 这里while&#xff08;true&#xff09;真的不要轻易用 &#xff0c;后来研发改动限制mysql的id切分步长&#xff0c;控制不会有数…

2017年2月16日 Go生态洞察:Go 1.8版本的革新

&#x1f337;&#x1f341; 博主猫头虎&#xff08;&#x1f405;&#x1f43e;&#xff09;带您 Go to New World✨&#x1f341; &#x1f984; 博客首页——&#x1f405;&#x1f43e;猫头虎的博客&#x1f390; &#x1f433; 《面试题大全专栏》 &#x1f995; 文章图文…