【数据结构】图的存储(十字链表)

news2025/6/3 23:50:20

弧节点 

  • tailvex数据域:存储弧尾一端顶点在顺序表中的位置下标;
  • headvex 数据域:存储弧头一端顶点在顺序表中的位置下标;
  • hlink 指针域:指向下一个以当前顶点作为弧头的弧;
  • tlink 指针域:指向下一个以当前顶点作为弧尾的弧;
  • info 指针:存储弧的其它信息,例如有向网中弧的权值。如果不需要存储其它信息,可以省略。

 顶点节点

  • data 数据域:用来存储顶点的信息;
  • firstin 指针域:指向一个链表,链表中记录的都是以当前顶点为弧头的弧的信息;
  • firstout 指针域:指向另一个链表,链表中记录的是以当前顶点为弧尾的弧的信息。

       就类似这种结构图:

大家别看他有这么多变量,其实理解起来很简单,十字链表是有向图的一种存储方式。

顶点节点中的firstout管理的是所有的弧尾,而弧节点的tlink负责链接。

顶点节点中的firstin管理的是所有的弧头 ,而弧节点的hlink负责链接。


在编写代码中,我给弧节点增加了两个指针分别是hprelink与tprelink。

这两个指针的意义是:

hprelink: 指向上一个以当前顶点作为弧头的弧;

tprelink:指向上一个以当前顶点作为弧尾的弧;

其实就是双链表的思想,为什么想用双链表,其实我在实现删除的时候:当我以头结点进行firstout遍历后找到当前顶点为弧尾的弧后,发现还要遍历一遍firstin找到当前顶点为弧头的弧。所以就寻思用双链表吧。其实如果不用双链表也大差不差只不过要多遍历一次,都是要找到这个节点的前一个节点和后一个节点,进行节点维护。

我觉得写代码中有一些心得值得分享的:在删除节点的时候吧,我总是把弧头与弧尾放到一块分析,比如:弧头前一个节点如果为空弧尾前一个节点如果不为空,弧头后一个节点如果为空弧尾后一个节点如果为空.......分析了一大坨。漏洞百出,逻辑没有闭合(痛苦了很长时间)。后来,我就逐个分析把他们分开了,删除这个节点的本质其实就是维护这个节点的前弧头一个节点,和后弧头一个节点,如果前为空....如果后为空....。之后分析弧尾也是这个套路。后来我总结一下:维护一个位置,其实就是分析前一个位置与后一个位置,不用管这个节点位置本身在头还是尾还是中间。

#pragma once
#include <string>
#include <vector>
#include <iostream>
#include <map>
using namespace std;

namespace Cross_linked_list
{
    template<class W>
    struct ArcNode
    {
        int tailvex;    // 弧尾下标
        int headvex;    // 弧头下标

        ArcNode<W>* hlink; // 相同弧头的下一条弧
        ArcNode<W>* tlink;    // 相同弧尾的下一条弧

        ArcNode<W>* hprelink;  // 弧头的上一条弧
        ArcNode<W>* tprelink;    // 弧尾的上一个弧
        W weight;        // 权值
        ArcNode(int tail, int head, W w)
            :tailvex(tail)
            , headvex(head)
            , hlink(nullptr)
            , tlink(nullptr)
            , hprelink(nullptr)
            , tprelink(nullptr)
            , weight(w)
        {}
    };

    // 顶点结构节点
    template<class V, class W>
    struct VexNode
    {
        V data;      // 顶点信息
        ArcNode<W>* firstin; // 指向该顶点的第一条入弧
        ArcNode<W>* firstout;// 指向该顶点的第一条出弧

        VexNode(V val)
            :data(val)
            , firstin(nullptr)
            , firstout(nullptr)
        {}
    };

    template<class V, class W> 
    class Graph
    {
        typedef ArcNode<W> Arc;  
        typedef VexNode<V, W> Vex; 

    public:
        // 4 , "ABCD"
        Graph(int n, const V* ver)
        {
            // 存储顶点并与下标建立映射
            _vertexs.reserve(n);
            _vexstable.resize(n, nullptr); // 初始化为nullptr

            for (int i = 0; i < n; i++)
            {
                _vertexs.push_back(ver[i]);
                _indexMap[ver[i]] = i;

                // 创建VexNode对象
                _vexstable[i] = new Vex(ver[i]); 
            }
        }

        ~Graph()
        {
            // 释放所有顶点和弧节点
            for (auto vex : _vexstable)
            {
                // 释放出弧节点
                Arc* p = vex->firstout;
                while (p)
                {
                    Arc* temp = p;
                    p = p->tlink;
                    delete temp;
                }
                // 释放顶点
                delete vex;
            }
        }

        void addArc(V src, V dst, W weight)
        {
            int srci = locateVex(src);
            int dsti = locateVex(dst);

            if (srci == -1 || dsti == -1)
            {
                cout << "顶点不存在!" << endl;
                return;
            }

            Arc* arc = new Arc(srci, dsti, weight);

            // 获取源顶点和目标顶点的指针
            Vex* srcVex = _vexstable[srci]; 
            Vex* dstVex = _vexstable[dsti]; 

            // 维护出弧链表(双向)
            if (srcVex->firstout) {
                srcVex->firstout->tprelink = arc;
            }
            arc->tlink = srcVex->firstout;
            srcVex->firstout = arc;
            arc->tprelink = nullptr;

            // 维护入弧链表(双向)
            if (dstVex->firstin) {
                dstVex->firstin->hprelink = arc;
            }
            arc->hlink = dstVex->firstin;
            dstVex->firstin = arc;
            arc->hprelink = nullptr;
        }

        void delEdge(V src, V dst)
        {
            int srci = locateVex(src);
            int dsti = locateVex(dst);

            if (srci == -1 || dsti == -1)
            {
                cout << "顶点不存在!" << endl;
                return;
            }

            // 查找从srci到dsti的节点
            Arc* arc = _vexstable[srci]->firstout;
            Arc* prev = nullptr;
            while (arc && arc->headvex != dsti)
            {
                prev = arc;
                arc = arc->tlink;
            }

            if (!arc)
            {
                cout << "边不存在!" << endl;
                return;
            }

            // 获取源顶点和目标顶点的指针
            Vex* srcVex = _vexstable[srci];
            Vex* dstVex = _vexstable[dsti];

            // 从出弧链表中删除
            // 如果前节点存在,不存在就更新头节点
            if (prev)
                prev->tlink = arc->tlink;
            else
                srcVex->firstout = arc->tlink;
            // 如果后节点存在
            if (arc->tlink)
                arc->tlink->tprelink = prev;

            // 从入弧链表中删除
            if (arc->hprelink)
                arc->hprelink->hlink = arc->hlink;
            else
                dstVex->firstin = arc->hlink;
            // 如果后节点存在
            if (arc->hlink)
                arc->hlink->hprelink = arc->hprelink;

            delete arc;
        }

        void print()
        {
            cout << "映射关系" << endl;
            for (size_t i = 0; i < _vexstable.size(); ++i)
            {
                cout << _vexstable[i]->data << ":" << i << endl;
            }
            cout << endl;

            cout << "出弧链表:" << endl;
            for (size_t i = 0; i < _vexstable.size(); ++i)
            {
                Arc* p = _vexstable[i]->firstout;
                cout << "顶点 " << _vexstable[i]->data << "(" << i << ") 的出弧:";
                while (p)
                {
                    cout << "[" << _vertexs[p->headvex] << ", 权值:" << p->weight << "] ";
                    p = p->tlink;
                }
                cout << endl;
            }

            cout << endl;
            cout << "入弧链表:" << endl;
            for (size_t i = 0; i < _vexstable.size(); ++i)
            {
                Arc* p = _vexstable[i]->firstin;
                cout << "顶点 " << _vexstable[i]->data << "(" << i << ") 的入弧:";
                while (p)
                {
                    cout << "[" << _vertexs[p->tailvex] << "->" << _vexstable[i]->data << ", 权值:" << p->weight << "] ";
                    p = p->hlink;
                }
                cout << endl;
            }
        }

    private:
        // 查找顶点在顶点表中的下标
        int locateVex(V v)
        {
            auto it = _indexMap.find(v);
            if (it != _indexMap.end())
            {
                return it->second;
            }
            else
            {
                return -1;
            }
        }

    private:
        vector<Vex*> _vexstable;  // 顶点表
        map<V, int> _indexMap;    // 映射关系
        vector<V> _vertexs;       // 顶点集合
    };

    void test()
    {
        Graph<char, int> g(4, "ABCD"); 

        g.addArc('A', 'B', 1);
        g.addArc('A', 'C', 1);
        g.addArc('C', 'D', 1);
        g.addArc('C', 'A', 1);
        g.addArc('D', 'A', 1);
        g.addArc('D', 'C', 1);

        cout << "\n删除边之前:\n";
        g.print();

        g.delEdge('C', 'A');
        g.delEdge('C', 'D');
        cout << "\n删除边之后:\n";
        g.print();
    }
}

效果展示: 

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

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

相关文章

Redis最佳实践——秒杀系统设计详解

基于Redis的高并发秒杀系统设计&#xff08;十万级QPS&#xff09; 一、秒杀系统核心挑战 瞬时流量洪峰&#xff1a;100万 QPS请求冲击库存超卖风险&#xff1a;精准扣减防止超卖系统高可用性&#xff1a;99.99%服务可用性要求数据强一致性&#xff1a;库存/订单/支付状态同步…

STM32软件spi和硬件spi

核心观点 本文主要介绍了SPI通信的两种实现方式&#xff1a;软件SPI和硬件SPI。详细阐述了SPI通信协议的基本概念、硬件电路连接方式、移位示意图、时序基本单元以及四种工作模式。同时&#xff0c;对W25Q64模块进行了详细介绍&#xff0c;包括其硬件电路、框图以及操作注意事…

深度刨析树结构(从入门到入土讲解AVL树及红黑树的奥秘)

目录 树的表示 二叉树的概念及结构&#xff08;重点学习&#xff09; 概念 &#xff1a; 特点&#xff1a; 树与非树 特殊的二叉树 二叉树的性质(重点) 二叉树的存储结构 堆的概念及结构 建堆方式&#xff1a; 向下调整算法 向上调整算法 建堆第一步初始化 建…

【Linux】shell的条件判断

目录 一.使用逻辑运算符判定命令执行结果 二.条件判断方法 三.判断表达式 3.1文件判断表达式 3.2字符串测试表达式 3.3整数测试表达式 3.4逻辑操作符 一.使用逻辑运算符判定命令执行结果 && 在命令执行后如果没有任何报错时会执行符号后面的动作|| 在命令执行后…

第九天:java注解

注解 1 什么是注解&#xff08;Annotation&#xff09; public class Test01 extends Object{//Override重写的注解Overridepublic String toString() {return "Test01{}";} }2 内置注解 2.1 Override Override重写的注解 Override public String toString() {ret…

十一、【核心功能篇】测试用例管理:设计用例新增编辑界面

【核心功能篇】测试用例管理&#xff1a;设计用例新增&编辑界面 前言准备工作第一步&#xff1a;创建测试用例相关的 API 服务 (src/api/testcase.ts)第二步&#xff1a;创建测试用例编辑页面组件 (src/views/testcase/TestCaseEditView.vue)第三步&#xff1a;配置测试用例…

Spring是如何实现属性占位符解析

Spring属性占位符解析 核心实现思路1️⃣ 定义占位符处理器类2️⃣ 处理 BeanDefinition 中的属性3️⃣ 替换具体的占位符4️⃣ 加载配置文件5️⃣ Getter / Setter 方法 源码见&#xff1a;mini-spring 在使用 Spring 框架开发过程中&#xff0c;为了实现配置的灵活性&#xf…

DDR4读写压力测试

1.1测试环境 1.1.1整体环境介绍 板卡&#xff1a; pcie-403板卡 主控芯片&#xff1a; Xilinx xcvu13p-fhgb2104-2 调试软件&#xff1a; Vivado 2018.3 代码环境&#xff1a; Vscode utf-8 测试工程&#xff1a; pcie403_user_top 1.1.2硬件介绍 UD PCIe-403…

编写测试用例

测试用例&#xff08;Test Case&#xff09;是用于测试系统的要素集合 目录 编写测试用例作用 编写测试用例要包含七大元素 测试用例的设计方法 1、等价类法 2、边界值法 3、正交表法 4、判定表法 5、错误推测法 6、场景法 编写测试用例作用 1、确保功能全面覆盖…

每日Prompt:隐形人

提示词 黑色棒球帽&#xff0c;白色抹胸、粉色低腰短裙、白色襪子&#xff0c;黑色鞋子&#xff0c;粉紅色背包&#xff0c;衣服悬浮在空中呈现动态姿势&#xff0c;虚幻引擎渲染风格&#xff0c;高清晰游戏CG质感&#xff0c;户外山林背景&#xff0c;画面聚焦在漂浮的衣服上…

TensorFlow深度学习实战(19)——受限玻尔兹曼机

TensorFlow深度学习实战&#xff08;19&#xff09;——受限玻尔兹曼机 0. 前言1. 受限玻尔兹曼机1.1 受限玻尔兹曼机架构1.2 受限玻尔兹曼机的数学原理 2. 使用受限玻尔兹曼机重建图像3. 深度信念网络小结系列链接 0. 前言 受限玻尔兹曼机 (Restricted Boltzmann Machine, RB…

告别手动绘图!基于AI的Smart Mermaid自动可视化图表工具搭建与使用指南

以下是对Smart Mermaid的简单介绍&#xff1a; 一款基于 AI 技术的 Web 应用程序&#xff0c;可将文本内容智能转换为 Mermaid 格式的代码&#xff0c;并将其渲染成可视化图表可以智能制作流程图、序列图、甘特图、状态图等等&#xff0c;并且支持在线调整、图片导出可以Docke…

【Oracle】安装单实例

个人主页&#xff1a;Guiat 归属专栏&#xff1a;Oracle 文章目录 1. 安装前的准备工作1.1 硬件和系统要求1.2 检查系统环境1.3 下载Oracle软件 2. 系统配置2.1 创建Oracle用户和组2.2 配置内核参数2.3 配置用户资源限制2.4 安装必要的软件包 3. 目录结构和环境变量3.1 创建Ora…

QT中更新或添加组件时出现“”qt操作至少需要一个处于启用状态的有效资料档案库“解决方法”

在MaintenanceTool.exe中点击下一步 第一个&#xff1a; 第二个&#xff1a; 第三个&#xff1a; 以上任意一个放入资料库中

论文速读《UAV-Flow Colosseo: 自然语言控制无人机系统》

论文链接&#xff1a;https://arxiv.org/abs/2505.15725项目主页&#xff1a;https://prince687028.github.io/UAV-Flow/ 0. 简介 近年来&#xff0c;无人机技术蓬勃发展&#xff0c;但如何让无人机像智能助手一样理解并执行人类语言指令&#xff0c;仍是一个前沿挑战。现有研…

ES6+中Promise 中错误捕捉详解——链式调用catch()或者async/await+try/catch

通过 unhandledrejection 捕捉未处理的 Promise 异常&#xff0c;手动将其抛出&#xff0c;最终让 window.onerror 捕捉&#xff0c;从而统一所有异常的处理逻辑 规范代码&#xff1a;catch&#xff08;onRejected&#xff09;、async...awaittry...catch 在 JavaScript 的 Pro…

解常微分方程组

Euler法 function euler_method % 参数设置 v_missile 450; % 导弹速度 km/h v_enemy 90; % 敌艇速度 km/h % 初始条件 x0 0; % 导弹初始位置 x y0 0; % 导弹初始位置 y xe0 120; % 敌艇初始位置 y t0 0; % 初始时间 % 时间步长和总时间 dt 0.01; % 时间步长 t_final …

C++实现汉诺塔游戏自动完成

目录 一、汉诺塔的规则二、数学递归推导式三、步骤实现(一)汉诺塔模型(二)递归实现(三)显示1.命令行显示2.SDL图形显示 四、处理用户输入及SDL环境配置五、总结六、源码下载 一、汉诺塔的规则 游戏由3根柱子和若干大小不一的圆盘组成&#xff0c;初始状态下&#xff0c;所有的…

pikachu靶场通关笔记07 XSS关卡03-存储型XSS

目录 一、XSS 二、存储型XSS 三、源码分析 四、渗透实战 1、输入mooyuan试一试 2、注入Payload 3、查看数据库 4、再次进入留言板页面 本系列为通过《pikachu靶场通关笔记》的XSS关卡(共10关&#xff09;渗透集合&#xff0c;通过对XSS关卡源码的代码审计找到XSS风险的…

OpenCV CUDA模块直方图计算------用于在 GPU 上执行对比度受限的自适应直方图均衡类cv::cuda::CLAHE

操作系统&#xff1a;ubuntu22.04 OpenCV版本&#xff1a;OpenCV4.9 IDE:Visual Studio Code 编程语言&#xff1a;C11 算法描述 cv::cuda::CLAHE 是 OpenCV 的 CUDA 模块中提供的一个类&#xff0c;用于在 GPU 上执行对比度受限的自适应直方图均衡&#xff08;Contrast Limi…