目录
前言:
两个实验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);
}
}
总结:
主要是理解索引表是如何实现的,索引表有什么作用,和你需要索引表存储哪些内容。这种类型代码可能第一次写会比较费劲,写过一次之后就会比较简单了。