数据结构 实验14(1-2班):(深入理解索引存储结构)三元组存储的稀疏矩阵建立行列索引并求鞍点

目录

前言:

需求分析:

难点分析:

代码和思路详解:

三元组表的头文件:

建立索引思路:

什么是三元组表的索引:

结合题意实现索引表:

建立索引表的代码实现:

索引表的结构体定义:

索引表的创建思路:

索引表创建的代码实现:

打印索引表:

求鞍点的思路:

基本思路:

注意事项:

求鞍点的代码实现:

程序测试:

main()函数:

行优先三元组表:

列优先三元组表:

三元组表tup的源文件:

总结:


前言:

两个实验14——三元组、广义表,个人写下来,要写好还是这个实验更难一些(也可能是因为我递归写的比较熟悉),这个实验完善后的代码量比广义表复制那个实验多了一倍。

需求分析:

如果矩阵A中存在这样的一个元素A[i][j]满足以下条件:A[i][j]是第i行中值最小的元素,且又是第j列中值最大的元素,则称A[i][j]为矩阵A的一个鞍点。

假设稀疏矩阵A(大小是m×n)已经用三元组表存放。要求建立行索引和列索引实现求鞍点的算法,如果没有鞍点,也给出相应信息。

难点分析:

1.如果是二维数组存储的矩阵实现起来非常简单,但是已经用三元组存储好了,我们并不知道是行优先存储还是列优先存储,需要分情况。

2.行优先或者列优先建立索引和遍历都有区别。

3.三元组表存储的稀疏矩阵,得考虑到某一行若全是大于0的数时,行最小大于0;若某一列全是负数,列最大小于0。

代码和思路详解:

三元组表的头文件:

我在头文件定义了三元组表示稀疏矩阵的结构体、行优先建表、列优先建表和打印三元组;初始化三元组是在建表是内部调用的,方便后续的测试。源文件我放在最后,我们主要讲索引和求鞍点。

/*
**作者:李宗霖    日期:2023/5/10
**三元组表示稀疏矩阵算法库2
*/
#include <stdio.h>
#include <stdlib.h>

typedef struct {
	int r;			//行 
	int c;			//列 
	int v;			//非零元素 
}TraidNode, *Traid;	//三元组 

typedef struct {
	int rows;		//行数 
	int cols;		//列数 
	int nums;		//非零元素数 
	Traid data;		//用指针可以根据nums开空间 
}TSmat, *TSMatrix;	//稀疏矩阵的三元组表 

TSMatrix InitTSM(int *MAT, int rows, int cols);		//初始化三元组表 
TSMatrix RFCreat(int *MAT, int rows, int cols);		//行优先建表 
TSMatrix CFCreat(int *MAT, int rows, int cols);		//列优先建表 
void DispTraid(TSMatrix t);							//打印三元组表 

建立索引思路:

什么是三元组表的索引:

稀疏矩阵在转化成三元组表存储后,大大节省了空间,但是失去了随机存取的性质,需要通过遍历寻找某一元素,而遍历最坏情况的时间复杂度为O(mn),所以我们可以通过建立一个索引(相当于目录)来节省时间,最坏情况时间复杂度为O(m)或O(n),这取决于这个三元组是行优先还是列优先,所以索引也分为行索引和列索引。

 如图1是带行索引的三元组表,没有索引要查找第6行元素需要将前面所有元素都遍历一遍,建了索引之后可以立马找到第六行的第一个元素,从第六行第一个元素开始遍历;如果没有索引要查找是否有第4行元素,需要将前面的所有元素遍历,建立索引,行为4的索引指向行3结束后的第一个元素,行为5,大于4说明没有行4元素。综上索引表可以帮助我们快速定位到目标行/列所在第一个元素,大大加快查找速度。

结合题意实现索引表:

题目需要查找第i行最小元素,和第j列的最大元素,所以我们的索引表除了可以存储查找起始位置外,我们还可以行索引存当前行最小值,列索引存当前列最大值如图2:

图上可以看到,当三元组表行优先时,列无序,列索引存储的位置没法确定,用数组的话,可以一个二元数组存放行索引,一个一元数组存放列索引,这里我定义了一个Index结构体,因为不知道行列大小所以用三个指针动态开空间:*loca存放遍历起始位置,*rvmin存放行最小元素,*cvmax存放列最大元素;r:row行,c:col列,v:value元素。

建立索引表的代码实现:

索引表的结构体定义:

如果是行优先*loca存放行索引,列优先则是存放列索引,为了区分更方便,定义了一个int tag,tag==0时行优先,tag==1时列优先:

typedef struct {
	int tag;		//标志:tag==0,行优先索引;tag==1,列优先索引 
	int *loca;		//行优先则存储对应行非0元素在三元组中出现位置,列优先亦然 
	int *rvmin;		//存储对应行的最小元素值(r:行,v:元素)
	int *cvmax;		//存储对应列的最大元素值(c:列,v:元素)
}Index;

索引表的创建思路:

1.判断行优先还是列优先:

        三元组表t是已经存储好的,我们并不知道到底是行优先还是列优先,所以我们需要在遍历的过程中去判断三原组表的优先级。

        我这里的思路是定义一个lastrow存储遍历过程中访问的上一个行标,如果lastrow大于现在正在访问的行标row,说明行标是无序的,那就是列优先,将lastrow置为-1。如果从头到尾row一直>=lastrow,那说明row是有序的,就是行优先,行和列都有序默认为行优先。

2.对rvmin数组和cvmax数组的初始化:

        在初始化方面如果第一次写思路很顺的话下意识就给全部初始化为0了,我最开始写就写成这样了,后面测试发现有问题修改的,这个是很容易被忽略的问题。

        之前说了行的最小值可能不为0,即整个行元素都大于0,如果初始化为0,0永远小于正整数,而0不被记录在三元组内,则rvmin的值永远小于等于0,很显然有问题。很多人也很容易想到,赋值0不行,那我遍历一遍获取整个矩阵的最大值再赋值给rvmin不就行了?那当然,没有那么简单;如果全部赋值为最大值,0不被记录,解决了某行全部元素大于0的问题,但是其他不是所有元素大于0的行不九出问题了,明明最小值为0,0没被记录,所以0不是最小值。下面我给出我能想到的最简单的方法:

        思路就是全部赋值为矩阵最大值,不过最后还需要加上判断语句,如果行非0元素的数量小于矩阵的列数,并且,当前rvmin[i]>0的时候rvmin[i]=0。计算非0元素数量我们后续操作也有用。

        解决rvmin,还有cvmax,当某一列元素全为负数的时候0恒大于负数,存储的最大值为0,实际最大值是小于0的数,解决思路和上面一样,如果非0元素数量小于矩阵行数,并且,当前cvmax[i]<0的时候cvmax[i]=0。

3.对rvmin,cvmax,loca的赋值

        rvmin和cvmax的赋值非常简单,初始化完成之后,在遍历三元组表的过程中进行比较,符合条件的赋值。

        loca的赋值和三元组的快速转置思路相同:我们先确定标号为1的访问位置为0,我们需要计算出每行/每列非0元素的个数,上一行/列的首元素位置+上一行/列元素数量=当前行/列首元素的位置。

索引表创建的代码实现:

为了更符合我们的阅读习惯(从第1行开始而不是第0行)rvmin[0],cvmax[0],loca[0],我们可以不存放数据,但是为了我们后续输出索引表方便遍历,我们可以分别存放,矩阵的行数,矩阵的列数,和非0元素个数。

代码分为几个部分:Index的初始化(开空间)->计算矩阵最大最小值并初始化rvmin,cvmax,rvnum,cvnum ->遍历三元组表求出各行最小元素值,各列最大元素值,各行、列非0元素数量,并判断是行优先还是列优先 ->根据优先级*loca开空间并赋值(上一行/列位置+上一行/列的数量)。

Index *CreatIndex(TSMatrix t) {
	//rvmin,cvmax开空间 
	Index *I = (Index *)malloc(sizeof(Index));
	I->rvmin = (int *)malloc((t->rows+1)*sizeof(int));
	I->cvmax = (int *)malloc((t->cols+1)*sizeof(int));
	//rvnum存各行的非0元素数量,cvnum存各列的非0元素数量 
	int i, max = 0, min = 0;
	int rvnum[t->rows+1], cvnum[t->cols+1];
	//max获取矩阵最大值,min获取矩阵最小值 
	for(i = 0; i < t->nums; i++) {
		if(max < t->data[i].v) max = t->data[i].v;
		else if(min > t->data[i].v) min = t->data[i].v;
	}
	//rvmin初始化为矩阵最大值,cvmax初始为举证最小值,num初始化为0 
	for(i = -1; i < t->rows && i < t->cols; i++) {
		if(i < t->rows) {
			I->rvmin[i+1] = max;
			rvnum[i+1] = 0;
		}
		if(i < t->cols) {
			I->cvmax[i+1] = min;
			cvnum[i+1] = 0;
		}
	}
	I->rvmin[0] = t->rows;
	I->cvmax[0] = t->cols;
	//遍历三元组表,lastrow记录上一个三元组的行标,若行标无序判断为列优先 
	int lastrow = t->data[0].r;
	for(i = 0; i < t->nums; i++) {
		//遍历求出各行最小元素值,各列最大元素值,各行、列非0元素数量 
		int row = t->data[i].r;
		rvnum[row]++;
		int col = t->data[i].c;
		cvnum[col]++;
		if(t->data[i].v < I->rvmin[row]) I->rvmin[row] = t->data[i].v;
		if(t->data[i].v > I->cvmax[col]) I->cvmax[col] = t->data[i].v;
		if(lastrow != -1) {
			if(lastrow <= row) lastrow = row;
			else lastrow = -1;
		}
	}
	//某行不全是非0元素且最小值不小于0,置为0 
	for(i = 1; i < t->rows + 1 && i < t->cols + 1; i++) {
		if(i < t->rows + 1 && rvnum[i] != t->cols && I->rvmin[i] > 0)
			I->rvmin[i] = 0;
		if(i < t->cols + 1 && cvnum[i] != t->rows && I->cvmax[i] < 0)
			I->cvmax[i] = 0;
	}
	//行优先三元组表,位置索引为行索引,记录每一行第一个元素在三元组表中出现的位置 
	if(lastrow != -1) {
		I->tag = 0;
		I->loca = (int *)malloc((t->rows+1)*sizeof(int));
		I->loca[0] = t->nums;
		I->loca[1] = 0;
		for(i = 1; i < t->rows; i++) {
			I->loca[i+1] = I->loca[i] + rvnum[i];
		}
	}
	//列优先三元组表 
	else {
		I->tag = 1;
		I->loca = (int *)malloc((t->cols+1)*sizeof(int));
		I->loca[0] = t->nums;
		I->loca[1] = 0;
		for(i = 1; i < t->cols; i++) {
			I->loca[i+1] = I->loca[i] + cvnum[i];
		}
	}
	return I;
}

打印索引表:

void DispIndex(Index *I) {
	int i;
	if(I->tag == 0) {
		printf("\n行优先三元组表索引:");
		printf("\n行号:\t\t");
		for(i = 1; i < I->rvmin[0] + 1; i++)
			printf("%d\t", i);
		printf("\n遍历起始位:\t");
		for(i = 1; i < I->rvmin[0] + 1; i++)
			printf("%d\t", I->loca[i]);
		printf("\n行最小元素:\t");
		for(i = 1; i < I->rvmin[0] + 1; i++)
			printf("%d\t", I->rvmin[i]);
		printf("\n\n列号:\t\t");
		for(i = 1; i < I->cvmax[0] + 1; i++)
			printf("%d\t", i);
		printf("\n列最大元素:\t");
		for(i = 1; i < I->cvmax[0] + 1; i++)
			printf("%d\t", I->cvmax[i]); 
	}
	else {
		printf("\n列优先三元组表索引:");
		printf("\n行号:\t\t");
		for(i = 1; i < I->rvmin[0] + 1; i++)
			printf("%d\t", i);
		printf("\n行最小元素:\t");
		for(i = 1; i < I->rvmin[0] + 1; i++)
			printf("%d\t", I->rvmin[i]);
		printf("\n\n列号:\t\t");
		for(i = 1; i < I->cvmax[0] + 1; i++)
			printf("%d\t", i);
		printf("\n遍历起始位:\t");
		for(i = 1; i < I->cvmax[0] + 1; i++)
			printf("%d\t", I->loca[i]);
		printf("\n列最大元素:\t");
		for(i = 1; i < I->cvmax[0] + 1; i++)
			printf("%d\t", I->cvmax[i]); 
	}
	printf("\n");
}

求鞍点的思路:

基本思路:

我们建立的索引表中包含了每一行的最小值*rvmin,和每一列的最大值*cvmax,只需要i遍历行,j遍历列,判断rvmin[i]==cvmax[j],那么点A[i][j]就是第i行最小值,第j列最大值,即鞍点。

注意事项:

1.我们在遍历过程中需要判断是否有鞍点,我的思路是建立一个int have=0,如果判断有鞍点have=1,遍历完成have=0,则没有鞍点。

2.如果判断有鞍点后,我们需要获取鞍点的值,如果遍历整个三元组表那就是置索引于枉然。我们在这里需要使用三元组表的索引查找来节省时间,快速判断三元组表是否存在点i,j,存在赋值,不存在则鞍点值为0,注意遍历也需要区分是行优先还是列优先。

求鞍点的代码实现:

上面建索引比较难,建成索引,求鞍点就洒洒水啦。

void GetSaddle(Index *I, TSMatrix t) {
	int i, j, val;
	int have = 0;			//have作为标志判断是否存在鞍点,存在have=1 
	for(i = 1; i < t->rows + 1; i++) {
		for(j = 1; j < t->cols + 1; j++) {
			if(I->rvmin[i] == I->cvmax[j]) {		//判断点(i,j)是否是鞍点 
				val = 0;			//鞍点的值默认为0,与三元组表成功匹配则赋值 
				have = 1;			//鞍点存在 
				int k;				//k作为访问三元组表的指针 
				if(I->tag == 0) {	//行优先 
					for(k = I->loca[i]; t->data[k].r == i; k++) {
						if(t->data[k].c == j) {
							val = t->data[k].v;
							break;	//匹配成功直接退出节约时间 
						}
					}
				}
				else {				//列优先 
					for(k = I->loca[j]; t->data[k].c == j; k++) {
						if(t->data[k].r == i) {
							val = t->data[k].v;
							break;
						}
					}
				}
				printf("\n鞍点:(%d,%d) = %d", i, j, val);
			}
		}
	}
	printf("\n");
	//鞍点不存在输出提示信息 
	if(have == 0) printf("在这个稀疏矩阵中没找到鞍点啦!!!\n");
}

程序测试:

main()函数:

int MAT[M][N] = {
		{1, 0, 0, 5, 0},
		{0, 0, 0, 0, 0},
		{0, 0, 0, 0, 0},
		{7, 6, 8, 5, 9},
		{0, 0, 0, 0, 0},
		{0, 0, 3, 0, 0}
	};
	int i, j;
	for(i = 0; i < M; i++) {
		for(j = 0; j < N; j++)
			printf("%d\t", MAT[i][j]);
		printf("\n");
	}
	TSMatrix rft = RFCreat(*MAT, M, N);
	Index *rfI = CreatIndex(rft);
	TSMatrix cft = CFCreat(*MAT, M, N);
	Index *cfI = CreatIndex(cft);
	DispTraid(rft);
	DispIndex(rfI);
	GetSaddle(rfI, rft);
	DispTraid(cft);
    DispIndex(cfI);
	GetSaddle(cfI, cft);
	return 0;
}

行优先三元组表:

列优先三元组表:

 大家可以多测试几组一些矩阵试试,我测试下来没有任何问题

三元组表tup的源文件:

/*
**作者:李宗霖    日期:2023/5/10
**三元组表示稀疏矩阵算法库2
*/
#include "tup.h"

//初始化
TSMatrix InitTSM(int *MAT, int rows, int cols) {
	//计算非0元素个数nums 
	int i, j, nums = 0;
	for(i = 0; i < rows; i++) {
		for(j = 0; j < cols; j++) {
			if(MAT[i*cols+j] != 0) nums++;
		}
	}
	TSMatrix t = (TSMatrix)malloc(sizeof(TSmat));
	t->rows = rows;
	t->cols = cols;
	t->nums = nums;
	t->data = (Traid)malloc(nums * sizeof(TraidNode));
	return t;
}

//行优先建表 
TSMatrix RFCreat(int *MAT, int rows, int cols) {
	TSMatrix t = InitTSM(MAT, rows, cols);
	int i, j, k = 0;
	for(i = 0; i < rows; i++) {
		for(j = 0; j < cols; j++) {
			if(MAT[i*cols+j] != 0) {
				t->data[k].r = i + 1;
				t->data[k].c = j + 1;
				t->data[k++].v = MAT[i*cols+j];
			}
		}
	}
	return t;
}

//列优先建表 
TSMatrix CFCreat(int *MAT, int rows, int cols) {
	TSMatrix t = InitTSM(MAT, rows, cols);
	int i, j, k = 0;
	for(j = 0; j < cols; j++) {
		for(i = 0; i < rows; i++) {
			if(MAT[i*cols+j] != 0) {
				t->data[k].r = i + 1;
				t->data[k].c = j + 1;
				t->data[k++].v = MAT[i*cols+j];
			}
		}
	}
	return t;
}

//打印三元组表
void DispTraid(TSMatrix t) {
	printf("\n %d\t%d\t%d\n", t->rows, t->cols, t->nums);
	printf(" ----------------\n");
	int i;
	for(i = 0; i < t->nums; i++) {
		printf(" %d\t%d\t%d\n", t->data[i].r, t->data[i].c, t->data[i].v);
	}
}

总结:

主要是理解索引表是如何实现的,索引表有什么作用,和你需要索引表存储哪些内容。这种类型代码可能第一次写会比较费劲,写过一次之后就会比较简单了。

  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Leisure_水中鱼

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值