数组以及稀疏矩阵的快速转置算法详细分析

一.数组:

1.数组的地址计算:

数组的地址计算比较简单,读者可以自行了解,在这里不再赘述;

2.特殊矩阵的压缩存储:

 在这里我们主要说明稀疏矩阵的主要内容:

(1)稀疏矩阵的三元组表示法:

在这里,存放非零元素的行标,列标以及元素值:

三元组表存放在一维数组中,一行一行存储,并且在每一行中,按照列号递增的方式来 !

(2)稀疏矩阵的三元组表的定义:

#define MAXSIZE 1000

#define ElementType int
typedef struct
{
	int row, col;//定义行标,列标
	ElementType e;
}Triple;

typedef struct//定义矩阵
{
	Triple data[MAXSIZE];
	int m, n, len;//矩阵的行,列数,以及非零元素的个数;
}TSmatrix;

 (3)矩阵的转置算法

a.经典的矩阵转置:
//经典的矩阵转置算法:就是给出矩阵及其转置矩阵,对应位置直接赋值

void TransMatrix(ElementType source[m][n], ElementType dest[n][m])(二维的)
{
	int i, j;
	for (i = 0; i < m; i++)
	{
		for (j = 0; j < n; j++)
		{
			dest[j][i] = source[i][j];
		}
	}
}
b.用三元组实现稀疏矩阵的转置:

关键点:保证转置之后的三元组也是按照行序优先来进行存储的

那么如果说先直接交换行列的值,那么就会造成转换之后的顺序紊乱,如果要保证转置之后仍然按照行优先来进行存储,那么我们需要对其按照行优先进行排序,就会移动大量的元素;

为了解决上述问题:

(1)列序递增转置算法

假设A为要转置的三元组表,B为最终结果的存放;

列序递增即按照A的列号进行递增排列存储的算法;

依次扫描A的列,每一个分别找出列号为1-K的,找到后就存入B中;

在这里:可能会有读者有这样的疑问:我们已经按照A的列号进行递增存贮,我们是否还需要对A的行号进行比较判断呢?以保证转置之后的的元素按照“列序”递增的顺序进行排列

答案是否定的,在转置之前,A就是按照行号递增来进行排序的,那么我们一定是先找到行号比较小的符合条件的元素,所以不需要再对行号进行排序;

//稀疏矩阵的列序递增转置算法

void TransposeTSMatrix(TSmatrix A, TSmatrix* B)//B传址调用
{
	int i, j, k;
	//先对B的行列数,以及元素个数进行初始化
	B->m = A.n;
	B->n = A.m;
	B->len = A.len;
	if (B->len > 0)
	{
		j = 0;
		for (k = 1; k <= A.n; k++)//扫描A,找到列数等于K的元素
		{
			for (i = 1; i < A.len; i++)//对三元组表中所有的元素进行遍历
			{
				if (A.data[i].col == k)
				{
					B->data[j].row = A.data[i].col;
					B->data[j].col = A.data[i].row;
					B->data[j].e = A.data[i].e;
					j++;
				}
			}
		}
	}
}

算法的时间复杂度分析:算法的时间耗费主要是在双重循环中,时间复杂度为:O(A.n*A.len)

A.len最大为A.m*A.n,此时算法时间复杂度为:O(A.m*A.n^2),而经典的算法实现转置的时间复杂度为:O(A.m*A.n)

由此可见:采用列序递增的算法,储存空间减少,但是在非零元素的个数较多时,时间复杂度并未显著降低;

(2)一次定位快速转置算法

一次定位快速转置:为了降低上述过程的时间复杂度;

那么我们需要以牺牲空间为代价:引入num[],和position[]这两个数组

其中num[]用于存放每一列的非零元素的总个数,position数组用于存放每一列的第一个非零元素对应在B中应该存放的位置,即在B三元组中的下标;

num[col]:扫描一遍三元组表A,A的元素的列标出现一次,num[col]就增加1,最终得到相应的列标对应的总的非零元素的个数;

position[col]:根据上述position其实就是表示对应元素在B中应该存放的下标的值;那么position[col]=position[col-1]+num[col-1],即上一列的下标,加上上一列的总的元素所占的位置,就是这一列的首非零元的在B中的下标;

在算法实现中:我们首先将num初始化为0,并且遍历一次A,求出相应的num数组;

其次我们应该利用:position[col]=position[col-1]+num[col-1],这个语句来计算列对应的position的值,并且由于存在col-1,而col是从1开始的,而第一个存储的元素的在B中的下标一定为1,那么我们可以直接将position[1]=1即可;然后就可以从A的列标为2,开始遍历,计算position[col];在这里是给position赋值,所以不用初始化为0;

关于B中的对应的下标的移动:在不同的列中,是通过position来进行增加移动的;

那在相同的列中呢?我们可以在相同的列中,每存储到B中一个元素,就把对应的列position[col]++,然后就可以使得position[col]一直都指向即将要存储的正确位置了;

最后,再次遍历A,将A的行列以及元素值都赋给B,每赋一个,就把对应的position[col]++,更新位置即可!(只要能够理解num和position的实现,就能够理解快速转置算法啦!

以下是具体的代码实现:

void FastTransposeTSMatrix(TSmatrix A, TSmatrix *B)
{
	int col, t, p, q;//列标
	int num[MAXSIZE], position[MAXSIZE];
	//仍然是先初始化B的行列数以及非零元素值
	B->len = A.len;
	B->n = A.m;
	B->m = A.n;
	if (B->len > 0)
	{
		for (col = 1;col <= A.n; col++)
		{
			num[col] = 0;//先将num数组初始化为0;//且数组是从1开始的!
		}
		//接着遍历A中的所有元素
		for (t = 1; t < A.len; t++)
		{
			num[A.data[t].col]++;//A中的元素的列标作为num的下标;
		}
		position[1] = 1;//第一个肯定是存储在第一个位置
		//那么从第二列开始赋值;
		for (col = 2; col <= A.n; col++)
		{
			position[col] = position[col - 1] + num[col - 1];
		}
		for (p = 1; p <= A.len; p++)
		{
			col = A.data[p].col;//列数取决于A中的元素的列数
			q = position[col];//q一直是在B中的下标;
			B->data[q].row = A.data[p].col;//B的行等于A的列;
			B->data[q].col = A.data[p].row;
			B->data[q].e = A.data[p].e;
			position[col]++;//下一个元素依然可能是这个列,因此改变下标位置;而不是这个列,对应的也有新的position值,所以....
		}
	}
}

一次定位快速转置的时间复杂度:在这里时间主要耗费在4个并列的单循环上;分别为:O(A.len)+O(A.n)+O(A.len)+O(A.n),那么该算法的时间复杂度就为:O(A.n+A.len)

那么我们总结一下:按照列序递增的方式与一次定位的区别:

按照列序递增,遍历K次A,按照列号递增,每次找到对应的列号的运算,再存入到B中,也就是必须是依次存放,列号要么相等,要么相邻;

而对于一次定位快速转置来说:遍历一次,不用非要是挨着的或者是一样的列标一次存入,按照列标存入到对应的应该存放的位置,不一定非要连续存入,其实更像是插入;一次定位的关键也就在这,就是要找到列标对应的正确的存放位置,也就是上述所说的position[col]的计算;


以上就是关于稀疏矩阵快速转置的算法理解,欢迎大家在评论区进行交流!

  • 11
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值