数组和广义表

  • 数组和广义表可以看成是线性表在下述含义上的扩展:表中的数据元素本身也是一个数据结构

1.矩阵的压缩存储

  • 假若值相同的元素或者零元素在矩阵中的分布有一定规律,则我们称此类矩阵为特殊矩阵;反之,称为稀疏矩阵。

(1)特殊矩阵的压缩存储

  • 若n节矩阵A中的元满足下述性质:
    aij=aji 1<=i,i<=n
    则称为n阶对称矩阵
  • 对于对称矩阵,我们可以只存储下三角,将n2个元压缩存储到n(n+1)/2个元的空间中。
  • 这种压缩存储的方法同样也适用于三角矩阵。所谓下(上)三角矩阵是指矩阵的上(下)三角(不包括对角线)中的元均为常数c或零的n阶矩阵。则除了和对称矩阵一样, 只存储其下(上)三角中的元之外,再加一个存储常数c的存储空间即可

(2)稀疏矩阵的压缩存储

  • 假设在m×n的矩阵中,有t个元素不为零。令δ = t/(m×n),通常认为δ≤0.05时称为稀疏矩阵

① 三元组顺序表压缩

  • 存储表示
#define MAXSIZE 12500  //假设非零元的最大值
typedef struct
{
	int i,j;   //该非零元的行下标和列下标
	ElemType data;
}Triple;
typedef struct
{
	Triple data[MAXSIZE+1];
	int mu,nu,tu;  //矩阵的行数、列数和非零元个数
}TSMatrix;
  • 压缩的稀疏矩阵对转置矩阵的运算是由很大优势的,只需要进行下面几部:
    • 将矩阵的行列值相互交换
    • 将每个三元组的i和j相互调换
    • 重排三元组之间的次序便可实现矩阵的转置。
  • 主要难点在于第三步,下面给出两种解法
  • 解法1:按照矩阵M的列序来进行转置。为了找到M的每一列中所有的非零元素,需要对其三元组表a.data从第一行起整个扫描一遍,由于a.data是以M的行序为主序来存放每个非零元,由此得到的恰是b.data应有的顺序
bool TransposeSMatrix(TSMatrix M, TSMatrix& T)
{
	T.mu = M.nu;  T.nu = M.mu;  T.tu = M.tu;
	int num = 0;
	if (!T.tu)  return false;
	for (int col = 0; col < M.nu; ++col)
	{
		for (int k = 0; k < M.tu; ++k)
		{
			if (M.data[k].j == col)
			{
				T.data[num].i = M.data[k].j;
				T.data[num].j = M.data[k].i;
				T.data[num].data = M.data[k].data;
				++num;
			}
		}
	}
	return true;
}
  • 一般的矩阵转置算法为O(mu×nu),而上述算法如果tu的数量级和mu×nu相同时,时间复杂度为O(mu×nu2)虽然节省了存储空间,但时间复杂度提高了,因此算法仅适于tu<<mu×nu的情况
  • 解法2:按照a.data中三元组的次序进行转置其,并将转置后的三元组置入b中恰当的位置。如果能预先确定矩阵M中每一列(即T中每一行)的第一个非零元在b.data中应有的位置,那么在对a.data中的三元组依次做转置时,便可直接放到b.data中恰当的位置上去。为了确定这些位置,在转置前 ,应先求得M的每一列中非零元的个数,进而求得每一列的第一个非零元在b.data中应有的位置。
    在此,需要附设num和cpot两个向量。num[col]表示矩阵M中第col列中非零元的个数,cpot[col]指示M中第col列中的第一个非零元在b.data中的恰当位置
bool FastTransposeSMatrix(TSMatrix M, TSMatrix& T) 
{
	int num[MAXSIZE+1];
	int cpot[MAXSIZE + 1];
	memset(num, 0, M.tu);
	memset(cpot, 0, M.tu);
	//得到M中每列元素的个数
	for (int k = 0; k < M.tu; ++k)
		++num[M.data[k].j];
	//得到每一列第一个元素放在T中的位置
	for (int k = 1; k < M.nu; ++k)
		cpot[k] = cpot[k- 1] + num[k - 1];
	for (int k = 0; k < M.nu; ++k)
	{
		T.data[cpot[M.data[k].j]].i = M.data[k].j;
		T.data[cpot[M.data[k].j]].j = M.data[k].i;
		T.data[cpot[M.data[k].j]].data = M.data[k].data;
		++cpot[M.data[k].j];
	}
	return true;
}
  • 上述算法时间复杂度仅为O(tu+nu)

② 行逻辑链接的顺序表

  • 为了便于随机存储任意一行的非零元,则需知道每一行的第一个非零元在三元组表中的位置。谓词,可将指示“行”信息的辅助数组固定在稀疏矩阵的存储结构中如下:
#define MAXSIZE 12500  //假设非零元的最大值
typedef struct
{
	int i,j;   //该非零元的行下标和列下标
	ElemType data;
}Triple;
typedef struct
{
	Triple data[MAXSIZE+1];
	int rpos[MAXRC+1]; 
	int mu,nu,tu;  //矩阵的行数、列数和非零元个数
}TSMatrix;
  • 普通矩阵乘法算法:
    时间复杂度为O(m1×n1×n2)
for(int i =1;i<m1:++i){
	for(int j=1;j<=n2;++j){
		Q[i][j] = 0;
		for(k=1;k<=n1;++k)
			Q[i][j]+=M[i][k]*N[k][j];
	}
}
  • 利用压缩后的矩阵计算乘法:
    在这里插入图片描述
    在这里插入图片描述
//矩阵相乘
bool MultSMatrix(TSMatrix M, TSMatrix N, TSMatrix& Q)
{
	//不符合格式直接退出
	if (M.nu != N.mu)		 return false;
	//对Q初始化
	Q.mu = M.mu;
	Q.nu = N.nu;
	Q.tu = 0;
	int num = 0;
	memset(Q.rpos, 0, Q.mu * sizeof(ElemType));
	//计算
	for (int row = 0; row < M.mu; ++row)
	{
		int tmp[MAXRC + 1] = { 0 };
		for (int k = M.rpos[row]; k < M.rpos[row + 1]; ++k)
		{
			int col = M.data[k].j;
			for (int q = N.rpos[col]; q < N.rpos[col + 1]; ++q)
			{
				tmp[N.data[q].j] += M.data[k].data * N.data[q].data;
			}	
		}
		for (int k = 0; k < N.nu; ++k)
		{
			if (tmp[k] != 0)
			{
				Q.data[num].i = row;
				Q.data[num].j = k;
				Q.data[num].data = tmp[k];
				num++;
			}
		}
		Q.rpos[row+1] = num;
	}
}

③ 十字链表

  • 当矩阵的非零元个数和位置在操作过程中变化较大时,就不宜采用顺序存储结构来表示三元组的线性表。例如,在作“将矩阵B加到矩阵A上”的操作时,由于非零元的插入或删除将会引起A.data中元素的移动。为此,对这种类型的矩阵,采用链式存储结构表示三元组的线性更为恰当
  • 在链表中,每个非零元可用一个含5个域的结点表示,其中i,j和e这3个域分别表示该非零元所在的行、列和非零元的值,向右域right用以链接同一行中下一个非零元,向下down用以链接同一列中的下一个非零元
    在这里插入图片描述
  • 稀疏矩阵的十字链表存储表示
typedef struct OLNode
{
	int i,j;  
	ElemType e;
	struct QLNode *right,*down;
}OLNode,*QLink;
typedef struct
{
	OLink *rhead,*chread;
	int mu,nu,tu;
}CrossList;
  • 十字链表的初始化
bool CreateSMatrix_OL(CrossList& M)
{
	//输入M的行数、列数和非零元个数
	cin >> M.mu >> M.nu >> M.tu;
	M.rhead = (OLink*)malloc((M.mu + 1) * sizeof(OLink));
	M.chead = (OLink*)malloc((M.nu + 1) * sizeof(OLink));
	if (M.rhead || M.rhead)  return false;

	//初始化行列头指针向量
	for (int i = 1; i < M.mu + 1; ++i)
		M.rhead[i] = nullptr;
	for (int i = 1; i < M.nu + 1; ++i)
		M.chead[i] = nullptr;

	//插入数据
	for (int k = 0 ;k < M.tu; ++k)
	{
		int i, j;
		ElemType e;
		cin >> i >> j >> e;
		//生成结点
		ONode* newNode = (ONode*)malloc(sizeof(ONode));
		newNode->i = i;		newNode->j = j;		newNode->e = e;
		//行插入
		if (M.rhead[i] == NULL || M.rhead[i]->j > j)
		{
			newNode->right = M.rhead[i];
			M.rhead[i] = newNode;
		}
		else
		{
			for (ONode* q = M.rhead[i]; (q->right) && (q->right->j < j); q = q->right)
			{
				//插到q的后面
				newNode->right = q->right;
				q->right = newNode;
			}
		}
		//列插入
		if (M.chead[j] == NULL || M.chead[j]->i > i)
		{
			newNode->down = M.chead[j];
			M.chead[j] = newNode;
		}
		else
		{
			for (ONode* q = M.chead[j]; (q->down) && (q->down->i < i); q = q->down)
			{
				//插到q的后面
				newNode->down = q->down;
				q->down = newNode;
			}
		}
	}
	return true;
}

2.广义表

(1)广义表的定义

  • 广义表是线性表的推广,也有人称其为列表。在线性表的定义中,ai可以是单个元素,也可以是广义表,分别称为广义表LS的原子和子表

  • 习惯上,用大写字母表示广义表的名称,用小写字母表示原子。

  • 当广义表LS非空时,称第一个元素为LS的表头,称其余元素组成的表是LS的表尾,如果说表中只有一个元素,则表尾为空表

  • 列表的三个重要结论

    • 列表的元素可以是字表,而子表的元素还可以是子表
    • 列表可为其他列表所共享
    • 列表可以是一个递归的表,即列表也可以是其本身的一个子表

(2)广义表的存储结构

  • 由于广义表中的数据元素可以具有不同的结构,因此难以用顺序存储结构表示,通常采用链式存储结构。
  • 广义表的头尾链表存储表示
typedef enum { ATOM, LIST }ElemTag;
typedef int AtomType;
typedef struct GLNode{
	ElemTag tag;  //公共部分标志位
	union
	{
		AtomType atom;
		struct {
			struct GLNode* hp, * tp;
		}ptr;
	};
}GLNode,*GList;
  • 示例:
    在这里插入图片描述
    在这里插入图片描述

(3)广义表的应用——m元多项式

  • 如果单纯用普通链表表示m元多项式,每个结点需要分配m+1个空间存储1个系数和m个值数,然而不是每个项都有m个值数,会造成存储空间的浪费。通过下面例子我们看出一个m元多项式可以改写成广义表的形式:
    在这里插入图片描述
  • 任何一个m元多项式都可以这么做:先分解出一个主变元,随后再分解出第二个变元等等。
  • 可以用如下结构存储:
    在这里插入图片描述
typedef struct MPNode
{
	ElemTag tag;  
	int     exp;
	union
	{
		float coef; 
		struct MPNode *hp;
	};
	struct MPNode *tp;
}*MPList;

在这里插入图片描述

(4)广义表的递归算法

① 求广义表的深度

  • 广义表的深度定义为广义表中括弧的重数,是广义表的一种量度
  • 广义表深度的递归定义为:
    基本项:
    DEPTH(LS)= 1 当LS为空表
    DEPTH(LS)= 0 当LS为原子
    归纳项:
    DEPTH(LS)= 1 + MAX{DEPTH(ai)} n>=1
int GListDepth(GList L)
{
	if(!L) return 1;
	if(L->tag == ATOM) return 0;  
	for(max = 0;pp=L;pp;pp=pp->ptr.tp)
	{
		dep = GListDepth(pp->ptr).hp);
		if(dep>max) max = dep;
	}
	return max+1;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值