数据结构-无向图(C++)

文章目录

  • 对称矩阵
    • 构造与析构
    • 下标访问的实现
    • 输入
    • 输出
    • 删除行列
    • 插入行列
  • 无向图数据结构
    • 构造与析构
    • 图的顶点数
    • 特殊顶点的操作
      • 查找顶点
      • 第i个顶点的第1个邻接顶点
      • 第i个顶点的下一个邻接顶点
    • 插入顶点
    • 删除顶点
    • 输入与输出

采用形式化的定义,图 G G G由两个集合 V V V E E E组成,记为 G = ( V , E ) G=(V,E) G=(V,E),其中 V V V是顶点的有限集合,记为 V ( G ) V(G) V(G) E E E是连接 V V V中两个不同顶点(顶点对)的边的有限集合,记为 E ( G ) E(G) E(G)。如果在图 G G G中,若 < i , j > ∈ E ( G ) <i,j>\in E(G) <i,j>∈E(G)必有 < j , i > ∈ E ( G ) <j,i>\in E(G) <j,i>∈E(G),即 E ( G ) E(G) E(G)是对称的,则用 ( i , j ) (i,j) (i,j)代替这两个顶点对,表示顶点 i i i与顶点 j j j的一条无向边,则称 G G G为无向图。

图的存储结构除了要存储图中各个顶点本身的信息以外,同时还要存储顶点与顶点之间的所有关系(边的信息)。常用的图的存储结构有邻接矩阵和邻接表。由于图的结构是未知的,出于节省存储空间的考虑,采用邻接矩阵作为物理存储结构。

对称矩阵

由于考虑的是特殊的图——无向图,其邻接矩阵是一个对角线全为0的对称矩阵。因此,真正起作用的部分只有对角线上方的元素,若直接使用二维数组处理邻接矩阵将造成巨大的空间浪费。因此,有必要专门写一个“对称矩阵”类。

构造与析构

由于C++语言只提供了数组这种底层结构,并没有“上三角形”结构,因此从第一行开始逐个元素存入数组即可。1此外,为了后续程序的方便,补写了一个对角元素的静态数据成员并赋值为0。2

template <typename T>
class sy_mat
{                  // 为了方便存取数据,补全对角线元素,但声明为静态数据成员static T diag; // 对角线元素的值,默认为0T *a;unsigned n;sy_mat(T *p, unsigned m) : a(p), n(m) {}public:sy_mat() : a(nullptr), n(0) {}~sy_mat(){if (a)delete[] a;}sy_mat(const sy_mat &x){if (x.a){T *p(x.a);*(a = new T[n = x.n]) = *p;while (--n)*++a = *++p;++a -= n = x.n;}else{n = 0;a = nullptr;}}sy_mat(unsigned k) : n(k), a(new T[k * (k - 1) / 2]){if (n == 1)a = nullptr;}sy_mat &operator=(const sy_mat &x){if (a)delete[] a;if (!x.a){a = nullptr;n = x.n;return *this;}T *p(x.a);*(a = new T[n = x.n]) = *p;while (--n)*++a = *++p;++a -= n = x.n;return *this;}
};
template <typename T>
T sy_mat<T>::diag = 0;

下标访问的实现

由于人为地将上三角强制存入了一维数组,因此需要给出计算给定行列返回元素值的函数。设 a i j a_{ij} aij为第 i i i行第 j j j列元素到第一个实际上存储的元素(即 a 12 a_{12} a12)的距离。当 i < j i<j i<j时, a i j = ∑ k = n − i + 1 n − 1 k + j − i − 1 = n ( i − 1 ) − i ( i + 1 ) 2 + j − 1 a_{ij}=\sum\limits_{k=n-i+1}^{n-1}k+j-i-1=n(i-1)-\dfrac{i(i+1)}2+j-1 aij=k=ni+1n1k+ji1=n(i1)2i(i+1)+j1;当 i = j i=j i=j时,返回diag即可;当 i > j i>j i>j时,利用矩阵的对称性, a i j = a j i = n ( j − 1 ) − j ( j + 1 ) 2 + i − 1 a_{ij}=a_{ji}=n(j-1)-\dfrac{j(j+1)}2+i-1 aij=aji=n(j1)2j(j+1)+i1。由于此函数将会很常用,因此重载了函数调用运算符(),方便后续的操作。

inline const T &operator()(unsigned i, unsigned j) const
{ // 以上三角形式存储,节省存储空间if (i == j)return diag;if (i > j)return a[n * (j - 1) - j * (j + 1) / 2 + i - 1];return a[n * (i - 1) - i * (i + 1) / 2 + j - 1];
}
inline T &operator()(unsigned i, unsigned j)
{if (i == j)return diag;if (i > j)return a[n * (j - 1) - j * (j + 1) / 2 + i - 1];return a[n * (i - 1) - i * (i + 1) / 2 + j - 1];
}

输入

从存储结构很容易发现同一行的元素在物理存储结构上是相邻的,因此在用户输入矩阵的时候只需要将上三角的数据用指针循环存储到数组中即可。此外,还特例化了unsigned char类型的输入。3程序如下:

template <typename T>
std::istream &operator>>(std::istream &c, sy_mat<T> &x)
{std::cout << "请输入矩阵阶数:\n";c >> x.n;if (x.a)delete[] x.a;if (x.n <= 1){x.a = nullptr;return c;}std::cout << "请输入矩阵:" << std::endl;T *p(x.a = new T[x.n * (x.n - 1)]), tem;for (unsigned i(1); i < x.n; ++i)for (unsigned j(1); j <= x.n; ++j)if (i < j)c >> *p++;elsec >> tem;for (unsigned i(0); i < x.n; ++i)c >> tem;return c;
}
std::istream &operator>>(std::istream &c, sy_mat<unsigned char> &x)
{std::cout << "请输入矩阵阶数:\n";c >> x.n;if (x.a)delete[] x.a;if (x.n <= 1){x.a = nullptr;return c;}std::cout << "请输入矩阵:" << std::endl;unsigned char *p(x.a = new unsigned char[x.n * (x.n - 1)]);unsigned short tem;for (unsigned i(1); i < x.n; ++i)for (unsigned j(1); j <= x.n; ++j){c >> tem;if (i < j)*p++ = tem;}for (unsigned i(0); i < x.n; ++i)c >> tem;return c;
}

输出

由于物理存储结构与矩阵的逻辑结构上有较大差距,因此利用下标元素访问的方式套用两层循环来实现输出。程序如下:

template <typename T>
std::ostream &operator<<(std::ostream &c, const sy_mat<T> &x)
{if (!x.n){c << "\t[空]" << std::endl;return c;}if (x.n == 1){c << '0' << std::endl;return c;}for (unsigned i(1); i <= x.n; ++i){for (unsigned j(1); j <= x.n; ++j)c << x(i, j) << '\t';c << std::endl;}return c;
}
std::ostream &operator<<(std::ostream &c, const sy_mat<unsigned char> &x)
{if (!x.n){c << "\t[空]" << std::endl;return c;}if (x.n == 1){c << '0' << std::endl;return c;}for (unsigned i(1); i <= x.n; ++i){for (unsigned j(1); j <= x.n; ++j)c << short(x(i, j)) << '\t';c << std::endl;}return c;
}

删除行列

对于一个数学结构来说,矩阵删除行列的操作是必要的。4但是为了保持矩阵的对称性,删除一行的同时也需要删除一列。因此删除后的数组大小为 ( n − 1 ) ( n − 2 ) 2 \dfrac{(n-1)(n-2)}2 2(n1)(n2),依次赋值即可。程序如下:

sy_mat &del(unsigned k) // 删除第k行及第k列
{if (!k || k > n || !n)return *this;if (n == 1){--n;return *this;}if (n == 2){delete[] a;--n;a = nullptr;return *this;}T *p(new T[(n - 2) * (n - 1) / 2]), *q(p);for (unsigned i(1); i <= n; ++i)for (unsigned j(1); j <= n; ++j)if (i < j && i != k && j != k)*q++ = this->operator()(i, j);a = p;--n;return *this;
}

插入行列

对应地,有删除行列就有插入行列。5插入后的数组大小为 n ( n + 1 ) 2 \dfrac{n(n+1)}2 2n(n+1),依次赋值即可。程序如下:

sy_mat &add(unsigned k, T *b = nullptr)
{ // 将b数组插入到第k+1行列if (++k > ++n)k = n;if (n == 1)return *this;T *p(a), *q(a = new T[n * (n - 1) / 2]), *t(p);if (b){for (unsigned i(1); i <= n; ++i)for (unsigned j(1); j <= n; ++j)if (i < j)if (i == k || j == k)*q++ = *b++;else*q++ = *p++;}elsefor (unsigned i(1); i <= n; ++i)for (unsigned j(1); j <= n; ++j)if (i < j)if (i == k || j == k)*q++ = 0;else*q++ = *p++;if (n != 2)delete[] t;return *this;
}

无向图数据结构

构造与析构

由图的定义可知,图 G = ( V , E ) G=(V,E) G=(V,E)。其中 V V V是顶点的有限集合,记为 V ( G ) V(G) V(G),用一个长度为n的数组存储; E E E是连接 V V V中两个不同顶点(顶点对)的边的有限集合,记为 E ( G ) E(G) E(G),用邻接矩阵存储。由于领接矩阵类包含了n,为了避免空间浪费,不再单独设置长度变量来存储顶点数。程序如下:

#include "sy_mat.h"
template <typename T, typename U = unsigned char>
class graph
{T *node;     // 顶点sy_mat<U> W; // 邻接矩阵
public:graph() : node(nullptr) {}~graph(){if (node)delete[] node;}graph(const graph &x) : W(x.W){if (W.n){T *p(x.node);*(node = new T[W.n]) = *p;while (--W.n)*++node = *++p;++node -= W.n = x.W.n;}elsenode = nullptr;}graph &operator=(const graph &x){W = x.W;if (node)delete[] node;if (!x.node){node = nullptr;return *this;}T *p(x.node);*(node = new T[W.n]) = *p;while (--W.n)*++node = *++p;++node -= W.n = x.W.n;return *this;}inline T &operator[](unsigned i) { return node[--i]; }inline const T &operator[](unsigned i) const { return node[--i]; }inline U &operator()(unsigned i, unsigned j){if (i == j)return sy_mat<U>::diag;if (i > j)return W.a[W.n * (j - 1) - j * (j + 1) / 2 + i - 1];return W.a[W.n * (i - 1) - i * (i + 1) / 2 + j - 1];}inline const U &operator()(unsigned i, unsigned j) const{if (i == j)return sy_mat<U>::diag;if (i > j)return W.a[W.n * (j - 1) - j * (j + 1) / 2 + i - 1];return W.a[W.n * (i - 1) - i * (i + 1) / 2 + j - 1];}
};

图的顶点数

直接返回领接矩阵的阶数即可。程序如下:

inline unsigned size() const { return W.n; }

特殊顶点的操作

查找顶点

给定一个元素值查找顶点所在位置下标,同线性表。程序如下:

unsigned locateVex(T x) const
{T *p(node);unsigned k(1);if (*p == x)return k;while (++k <= W.n)if (*++p == x)return k;return 0;
}

第i个顶点的第1个邻接顶点

unsigned firstAdjVex(unsigned i) const
{for (unsigned j(1); j <= W.n; ++j)if (W(i, j))return j;return 0;
}

第i个顶点的下一个邻接顶点

unsigned nextAdjVex(unsigned i) const
{for (unsigned j(i + 1); j <= W.n; ++j)if (W(i, j))return j;return 0;
}

插入顶点

graph &insertnode(unsigned k, T value, U *w = nullptr)
{unsigned n((W.add(k, w)).n);T *p(node), *q(node = new T[n]), *t(p);if (++k > n)k = n;n -= k;while (--k)*q++ = *p++;*q = value;while (n--)*++q = *p++;delete[] t;return *this;
}

删除顶点

graph &deletenode(unsigned k)
{unsigned n(W.n);if (!k || k > n || !n)return *this;W.del(k);if (!n){delete[] node;return *this;}T *p(node), *q(node = new T[n]), *t(p);++n -= k;while (--k)*q++ = *p++;while (--n)*q++ = *++p;delete[] t;return *this;
}

输入与输出

直接让用户输入顶点集和领接矩阵。但是由于领接矩阵元素类型有可能为unsigned char,因此利用C++语言中的conceptstype_traits来判断类型。6

#include <concepts>
#include <type_traits>
template <typename T, typename U>
std::istream &operator>>(std::istream &c, graph<T, U> &x)
{std::cout << "请输入顶点数:\n";c >> x.W.n;std::cout << "请输入顶点:\n";if (x.node)delete[] x.node;T *p(x.node = new T[x.W.n]);for (unsigned i(0); i < x.W.n; ++i)c >> *p++;std::cout << "请输入邻接矩阵:\n";if (x.W.a)delete[] x.W.a;if (x.W.n <= 1){x.W.a = nullptr;return c;}U *q(x.W.a = new U[x.W.n * (x.W.n - 1)]);if (std::is_same_v<U, unsigned char>){unsigned short tem;for (unsigned i(1); i < x.W.n; ++i)for (unsigned j(1); j <= x.W.n; ++j){c >> tem;if (i < j)*q++ = tem;}for (unsigned i(0); i < x.W.n; ++i)c >> tem;}else{U tem;for (unsigned i(1); i < x.W.n; ++i)for (unsigned j(1); j <= x.W.n; ++j)if (i < j)c >> *q++;elsec >> tem;for (unsigned i(0); i < x.W.n; ++i)c >> tem;}return c;
}
template <typename T, typename U>
std::ostream &operator<<(std::ostream &c, const graph<T, U> &x)
{if (!x.W.n){c << "\t[空]" << std::endl;return c;}if (x.W.n == 1){c << "顶点:\n"<< *x.node << std::endl;return c;}c << "顶点:\n";unsigned m(x.W.n);T *p(x.node);doc << *p++ << '\t';while (--m);c << "\n邻接矩阵:\n"<< x.W;return c;
}

  1. 此时数组的大小为 ∑ i = 1 n − 1 i = ( n − 1 ) n 2 \sum\limits_{i=1}^{n-1}i=\dfrac{(n-1)n}2 i=1n1i=2(n1)n。 ↩︎

  2. 也可以是当前数据类型中可能的最大值,代表无穷。 ↩︎

  3. 防止其直接进行字符输入。 ↩︎

  4. 对应到图中,就是删除其中一个顶点。 ↩︎

  5. 对应到图中,就是插入一个顶点。 ↩︎

  6. 这是由于C++语言不支持函数模板偏特化,因此不能直接重写函数如下:template<typename T>std::istream& operator>>(std::istream& c, graph<T, unsigned char>& x)。 ↩︎

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

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

相关文章

华为机试真题实战应用【赛题代码篇】-优雅数组(附Java、python和C++代码)

目录 题目描述 解题思路 思路1 思路2 代码实现 Java python

使用代理IP池实现多线程爬虫的方法

目录 前言 代理IP的概念和作用 为什么使用代理IP池 代理IP池的实现步骤 代码实现 注意事项 总结 前言 随着互联网的发展&#xff0c;爬虫技术在各个领域中被广泛应用。然而&#xff0c;目标网站对爬虫的限制也日益严格&#xff0c;例如限制单个IP的请求频率。为了解决这…

从0到1:如何建立一个大规模多语言代码生成预训练模型

国产AI辅助编程工具 CodeGeeX 是一个使用AI大模型为基座的辅助编程工具&#xff0c;帮助开发人员更快的编写代码。可以自动完成整个函数的编写&#xff0c;只需要根据注释或Tab按键即可。它已经在Java、JavaScript和Python等二十多种语言上进行了训练&#xff0c;并基于大量公开…

三轴加速度计LIS2DW12开发(3)----检测活动和静止状态

e2studio开发三轴加速度计LIS2DW12.3--检测活动和静止状态 概述视频教学样品申请源码下载新建工程工程模板保存工程路径芯片配置工程模板选择时钟设置UART配置UART属性配置设置e2studio堆栈e2studio的重定向printf设置R_SCI_UART_Open()函数原型回调函数user_uart_callback ()…

mongoose6.0版以上操作mongodb数据库的基本使用

1、介绍 Mongoose 是一个对象文档模型库&#xff0c;官网 http://www.mongoosejs.net/ 2、作用 方便使用代码操作 mongodb 数据库 3、使用流程 3.1、链接数据库 //1. 安装 mongoose---> npm install mongoose --save//2. 导入 mongoose const mongoose require(&quo…

航模遥控开关电路图大全

航模遥控开关电路图&#xff08;一&#xff09;&#xff1a;单通道航模遥控器的构造 遥控装置一般应用于车模、航模等领域&#xff0c;用以实现对靶机、航模、玩具等的自动控制。下面介绍一种无线比例电机遥控器的制作方法。它选用易购元件&#xff0c;具有原理简单、性能可靠…

JavaScript 14种方法可以实现文件下载

JavaScript 14种方法可以实现文件下载 使用 a 标签的 download 属性&#xff1a; function downloadFile(url, fileName) {const link document.createElement(a);link.href url;link.download fileName;link.target "_blank"; // 可选&#xff0c;如果希望在新…

我的大数据之路 - 关于大数据平台上任务管理的思考

本文于2019年7月16日完成&#xff0c;发布在个人博客网站上。 作业&#xff0c;比如提交一个hive脚本到计算平台上运行&#xff0c;这个脚本宏观上称为一个作业。 任务&#xff0c;比如mapper&#xff0c;reducer等。 资源&#xff0c;比如CPU时间&#xff0c;内存&#xff0c;…

28 星际旋转

效果演示 实现了一个太阳系动画&#xff0c;其中包括了地球、火星、金星、土星、水星、天王星、海王星以及火卫二号等行星的动画效果。太阳系的行星都被放在一个固定的容器中&#xff0c;并使用CSS动画来实现旋转和移动的效果。当太阳系的行星绕着太阳运行时&#xff0c;它们会…

PHP运算符汇总

⭕️前些天发现了一个巨牛的人工智能学习网站&#xff0c;通俗易懂&#xff0c;风趣幽默&#xff0c;忍不住分享一下给大家(点击跳转到网站)⭕️ 一、算数运算符 基础理解 加减乘除、求余数、整数除法&#xff08;向下取整&#xff09;、字符串拼接等 <?php $x10; $y…

电子学会C/C++编程等级考试2021年09月(四级)真题解析

C/C++编程(1~8级)全部真题・点这里 第1题:最佳路径 如下所示的由正整数数字构成的三角形: 7 3 8 8 1 0 2 7 4 4 4 5 2 6 5 从三角形的顶部到底部有很多条不同的路径。对于每条路径,把路径上面的数加起来可以得到一个和,和最大的路径称为最佳路径。你的任务就是求出最佳路径…

Rust-trait

Rust语言中的trait是非常重要的概念。 在Rust中&#xff0c;trait这一个概念承担了多种职责。在中文里&#xff0c;trait可以翻译为“特征”“特点”“特性”等。 成员方法 trait中可以定义函数。用例子来说明&#xff0c;我们定义如下的trait: 上面这个trait包含了一个方法…

【C++入门到精通】智能指针 [ C++入门 ]

阅读导航 引言一、什么是智能指针二、为什么需要智能指针三、内存泄漏1. 什么是内存泄漏&#xff0c;内存泄漏的危害2. 内存泄漏的示例&#xff0c;以及解决方法3. 内存泄漏分类&#xff08;1&#xff09;堆内存泄漏(Heap leak)&#xff08;2&#xff09;系统资源泄漏 4. 如何检…

FFmpeg 的使用与Docker安装流媒体服务器

本文阐述的均为命令行的使用方式&#xff0c;并不牵扯FFmpeg 的 C音视频开发内容&#xff0c;补充一句&#xff0c;C的资料真的少&#xff0c;能把C学好的人&#xff0c;我真的是觉得巨佬。 我主要是使用FFmpeg 推流方面的知识&#xff0c;案例大都是靠近这方面。 一、FFmpeg…

常用Java代码-Java中的并发集合(ConcurrentHashMap、CopyOnWriteArrayList等)

在Java中&#xff0c;并发集合是一组为多线程环境设计的集合类&#xff0c;它们提供了线程安全的操作。这些集合类包括ConcurrentHashMap&#xff0c;CopyOnWriteArrayList等。以下是对这两个类的一个简单的代码解释。 1.ConcurrentHashMap ConcurrentHashMap是Java并发包jav…

如何在“Microsoft Visual Studio”中使用OpenCV构建应用程序

我在这里描述的所有内容都将应用于 OpenCV 的界面。我首先假设您已经阅读并成功完成了 Windows 中的安装教程。因此&#xff0c;在进一步操作之前&#xff0c;请确保您有一个包含 OpenCV 头文件和二进制文件的 OpenCV 目录&#xff0c;并且您已按照此处所述设置环境变量 设置 O…

迅腾文化用网络集成化生态系统助力品牌之路的坚实后盾

商业竞争激烈&#xff0c;品牌不仅是企业的标志和形象&#xff0c;更是其核心价值和竞争力的体现。然而&#xff0c;企业在品牌推广过程中面临着诸多如缺乏有效的渠道管理、品牌形象模糊以及竞争激烈的市场环境等。这些阻碍着企业的品牌发展和市场占有率的提升。本文将通过企业…

C语言辨析——深入理解格式字符的用法

1. 问题 下面程序为什么的输出结果为什么不是25而是0&#xff1f;问题出在哪&#xff1f; #include <stdio.h> #include <math.h> int main() {int a3,b4; printf("%d\n",pow(a,2)pow(b,2)); return 0; } 2. 分析 函数pow的返回类型是double&…

双周赛121(模拟、位运算、BFS、数位DP上下界)

文章目录 双周赛121[2996. 大于等于顺序前缀和的最小缺失整数](https://leetcode.cn/problems/smallest-missing-integer-greater-than-sequential-prefix-sum/)模拟 [2997. 使数组异或和等于 K 的最少操作次数](https://leetcode.cn/problems/minimum-number-of-operations-to…

线上剧本杀小程序搭建,未来线上剧本杀有哪些发展优势?

剧本杀游戏是当下比较流行的一种新型游戏模式&#xff0c;它能够让玩家在游戏中进行角色扮演&#xff0c;体验不同的角色人生&#xff0c;沉浸式玩游戏&#xff0c;因此受到了众多年轻人的喜欢。随着互联网科技的发展&#xff0c;剧本杀的发展也转型到了互联网上&#xff0c;为…