实验内容及要求:
从字符文件读入3元组(i, j, e)来建立两个矩阵A和B的十字链表存储结构(矩阵的行、列数m, n以及非零元素的数目需首先读入)。计算C=A+B,其中矩阵C也采用十字链表存储结构。计算结束后,以三元组形式输出矩阵C至另一个字符文件。矩阵C的十字链表存储空间可以独立于A和B,也可以就用A矩阵的十字链表来存储(则A矩阵的非零元素结点数目可能增加、减少或不变)。
要求实验报告必须写出:输入文件数据格式;输出文件数据格式;加法的实现算法;各子函数的详细说明等。每个同学至少准备一个测试用例(A, B以及期望的C)。每个同学除自己的测试用例外,必须随机选择另外两名同学,用他们的测试用例测试自己的程序。在实验报告中需注明3个测试用例分别来自哪位同学(给出姓名和学号,自己准备的测试用例也需写出姓名和学号)。
实验目的:掌握稀疏矩阵的十字链表存储结构及其基本操作。
数据结构设计简要描述:
采用十字链表存储稀疏矩阵
typedef int elem; // 将int作为elem
// 定义十字链表节点
typedef struct node_ {
int i; // 行
int j; // 列
elem e; // 非0元素个数、元素值
struct node_* right; // 向列延伸
struct node_* down; // 向行延伸
}node, * crossLink;
算法设计简要描述:
先对字符文件AB.txt逐字符读入,创建A和B,在计算相加的时候,结合有序顺序表的合并的思想,进一步优化时间复杂度,使得只需遍历一遍A和B就可以完成相加操作。在相加时选择在A中插入新节点而不是将B中已有的节点给A,主要考虑最后释放的时候会涉及到空间重复释放的原因。
输入/输出设计简要描述:
输入:AB.txt文件,A在上,B在下,每一部分的第一行都是矩阵的行列数和非0元素的数目,接着才是各非0元素。A和B之间用一个空格隔开。
输出:C.txt文件,第一行是矩阵的行列数和非0元素数目,剩下是具体非0元素
编程语言说明:
使用Visual Studio Code编程。 主要代码采用C语言实现 ;动态存储分配采用C++的new和delete操作符实现;输入与输出采用C++的文件流对象和cout流;程序注释采用C/C++规范。
主要函数说明:
// 初始化十字链表
void Init(crossLink& L, int m, int n, int e);
// 十字链表中插入节点
void Insert(crossLink& L, int i, int j, elem e);
// 十字链表中删除节点
void Delete(crossLink& L, int i, int j);
// 创建有数据的十字链表
void Create(crossLink& A, crossLink& B);
// 计算相加
void Add(crossLink A, crossLink B);
// 将结果输出到文件
void toFile(crossLink A);
// 销毁十字链表
void Relese(crossLink& L);
程序测试简要报告:
- 测试实例1
程序输入
程序输出
结论
程序输出结果与期望输出结果相符。
测试实例2
程序输入
程序输出
结论
程序输出结果与期望输出结果相符。
测试实例3
程序输入
程序输出
结论
程序输出结果与期望输出结果相符。
源代码:
#include <iostream>
#include <fstream>
using namespace std;
typedef int elem; // 将int作为elem
// 定义十字链表节点
typedef struct node_ {
int i; // 行
int j; // 列
elem e; // 非0元素个数、元素值
struct node_* right; // 向列延伸
struct node_* down; // 向行延伸
}node, * crossLink;
// 初始化十字链表
void Init(crossLink& L, int m, int n, int e) {
node* temp = new node;
L = temp;
L->i = m; // 稀疏矩阵的行数
L->j = n; // 稀疏矩阵的列数
L->e = e; // 稀疏矩阵的非0元素
// 以下建行头节点数组
node* row = new node[m];
if (!row) {
cout << "内存申请失败!!!" << endl;
exit(1);
}
for (int i = 0; i < m; ++i) {
row[i].i = i + 1;
row[i].right = &row[i];
}
L->down = row;
// 以下建列头节点数组
node* col = new node[n];
if (!col) {
cout << "内存申请失败!!!" << endl;
exit(1);
}
for (int j = 0; j < n; ++j) {
col[j].j = j + 1;
col[j].down = &col[j];
}
L->right = col;
}
// 十字链表中插入节点
void Insert(crossLink& L, int i, int j, elem e) {
if (i < 1 || i > L->i || j < 1 || j > L->j) {
cout << "行号或列号超出范围!!!" << endl;
return;
}
// 建新元素节点
node* n = new node;
n->i = i;
n->j = j;
n->e = e;
// 以下插入新节点到第i行循环链表
// 插入后使第i行循环链表各节点按列下标升序连接
node* pr = &L->down[i - 1];
node* p = pr->right;
while (p != &L->down[i - 1] && j > p->j)
{
pr = p;
p = p->right;
}
pr->right = n;
n->right = p;
// 以下插入新节点到第j列循环链表
// 插入后使第j列循环链表各接点按行下标升序连接
pr = &L->right[j - 1];
p = pr->down;
while (p != &L->right[j - 1] && i > p->i)
{
pr = p;
p = p->down;
}
pr->down = n;
n->down = p;
}
// 十字链表中删除节点
void Delete(crossLink& L, int i, int j) {
if (i < 1 || i > L->i || j < 1 || j > L->j) {
cout << "行号或列号超出范围!!!" << endl;
return;
}
// 以下在第i行链表中查找(i, j)
node* pr1 = &L->down[i - 1];
node* pr2 = pr1->right;
while (pr2 != &L->down[i - 1] && i != pr2->i)
{
pr1 = pr2;
pr2 = pr2->right;
}
// 查找失败
if (pr2 == &L->down[i - 1]) {
cout << "不存在此节点!!!" << endl;
return;
}
// 以下在第j列链表中查找(i, j)
node* pc1 = &L->right[j - 1];
node* pc2 = pc1->down;
while (pc2 != &L->right[j - 1] && j != pc2->j)
{
pc1 = pc2;
pc2 = pc2->down;
}
// 查找失败
if (pc2 == &L->right[j - 1]) {
cout << "不存在此节点!!!" << endl;
return;
}
// 行、列查找结果不一致
if (pc2 != pr2) {
cout << "发生错误!!!" << endl;
return;
}
// 以下删除元素节点(i, j)
pr1->right = pr2->right;
pc1->down = pc2->down;
delete pr2;
}
// 创建有数据的十字链表
void Create(crossLink& A, crossLink& B) {
// 实例化文件对象
ifstream ifs;
ifs.open("AB.txt", ios::in);
char c;
// num:每行第几个字符 flag:标识变量 i j e:行 列 值
int num = 1, flag = 1, i, j, e;
// 以下读取A稀疏矩阵
c = ifs.get();
while (c != '\n' || num != 1)
{
if (num == 1) {
if (c == '-') {
cout << "稀疏矩阵文件错误!!!行或列不能为负!!!" << endl;
return;
}
i = c - '0';
++num;
}
else if (num == 3) {
if (c == '-') {
cout << "稀疏矩阵文件错误!!!行或列不能为负!!!" << endl;
return;
}
j = c - '0';
++num;
}
else if (num == 5) {
// 当数值为负时
if (c == '-') {
c = ifs.get();
e = -(c - '0');
}
else {
e = c - '0';
}
++num;
}
else if (num == 6) {
if (flag) {
Init(A, i, j, e);
flag = 0;
}
else {
Insert(A, i, j, e);
}
num = 1;
}
else {
++num;
}
c = ifs.get();
}
// 以下读取B稀疏矩阵
flag = 1;
c = ifs.get();
while (c != '\n' || num != 1)
{
// 当读到文件末尾
if (c == EOF) {
break;
}
if (num == 1) {
if (c == '-') {
cout << "稀疏矩阵文件错误!!!行或列不能为负!!!" << endl;
return;
}
i = c - '0';
++num;
}
else if (num == 3) {
if (c == '-') {
cout << "稀疏矩阵文件错误!!!行或列不能为负!!!" << endl;
return;
}
j = c - '0';
++num;
}
else if (num == 5) {
if (c == '-') {
c = ifs.get();
e = -(c - '0');
}
else {
e = c - '0';
}
++num;
}
else if (num == 6) {
if (flag) {
Init(B, i, j, e);
flag = 0;
}
else {
Insert(B, i, j, e);
}
num = 1;
}
else {
++num;
}
c = ifs.get();
}
ifs.close();
}
// 计算相加
void Add(crossLink A, crossLink B) {
// 如果A和B的行或列不一样
if (A->i != B->i || A->j != B->j) {
cout << "A和B不满足相加条件!!!" << endl;
return;
}
// 最后非0元素个数的变化
int differ = 0;
// 对A的行头节点数组遍历 将最后的结果存储在A
// 因行的列下标是有序的 故借用有序顺序表合并的思想
for (int i = 1; i <= A->i; ++i) {
node* A_row = &A->down[i - 1];
node* B_row = &B->down[i - 1];
node* pA = A_row->right;
node* pB = B_row->right;
while (pA != A_row && pB != B_row)
{
// A和B都有(i, j)元素
if (pA->j == pB->j) {
pA->e += pB->e;
// 如果相加为0 删除此节点
if (pA->e == 0) {
differ -= 1;
node* temp = pA->right;
Delete(A, pA->i, pA->j);
pA = temp;
}
else {
pA = pA->right;
}
pB = pB->right;
}
else if (pA->j < pB->j) { // A元素的列小于B元素的列
pA = pA->right;
}
else { // A元素的列大于B元素的列
Insert(A, pB->i, pB->j, pB->e); // 这里选择插入新节点 而不是将B中的节点放入A
differ += 1;
pB = pB->right;
}
}
while (pB != B_row)
{
Insert(A, pB->i, pB->j, pB->e);
differ += 1;
pB = pB->right;
}
}
A->e += differ;
}
// 将结果输出到文件
void toFile(crossLink A) {
// 实例化文件对象
ofstream ofs;
ofs.open("C.txt", ios::out);
// 将结果矩阵的行 列 非0元素个数先输出到文件中
ofs << A->i << " " << A->j << " " << A->e << endl;
// 遍历行 将每一个节点输出到文件
for (int i = 1; i <= A->i; ++i) {
node* p = &A->down[i - 1];
p = p->right;
while (p != &A->down[i - 1])
{
ofs << p->i << " " << p->j << " " << p->e << endl;
p = p->right;
}
}
ofs.close();
}
// 销毁十字链表
void Relese(crossLink& L) {
// 先释放每个节点
for (int i = 1; i <= L->i; ++i) {
node* pr1 = &L->down[i - 1];
pr1 = pr1->right;
if (pr1 == &L->down[i - 1]) {
continue;
}
else {
node* pr2 = pr1->right;
while (pr1 != &L->down[i - 1])
{
delete pr1;
pr1 = pr2;
pr2 = pr2->right;
}
}
}
// 释放头结点数组和列节点数组
delete[] L->down;
delete[] L->right;
delete L;
}
int main(void) {
// 定义A和B
crossLink A, B;
Create(A, B);
Add(A, B);
toFile(A);
Relese(A);
Relese(B);
cout << "计算成功!!!" << endl;
return 0;
}