数据结构-数组和广义表

系列文章目录

第一章 线性表
第二章 栈与队列
第三章 串
第四章 数组与广义表



前言

数组和广义表,可以看成是一种扩展的线性数据结构,其特殊性不像栈和队列那样表现在对数据元素的操作受限制,而是反映在数据元素的构成上。在线性表中,每个数据元素都是不可再分的原子类型,而数组和广义表中的数据元素可以推广到一种具有特定结构的数据。本章以抽象数据类型的形式讨论数组和广义表的逻辑定义、存储结构、操作的实现,加以对这两种特殊的线性结构的理解。


一、数组

1.什么是数组?

数组 :每个子结构是由基本元素组成的大小均等的线性表。

2. 数组结构的顺序存储结构*

数组结构是多维的,内存地址是一维的。
存储方式:行主序,列主序
以行主序为例:
一维: LOC(ai) =base+i
二维(MxN): LOC(ai,j) =base+iN +j
三维(MxNxP): LOC(ai,j,k) =base+i
NP +jP +k
四维(MxNxPxQ):LOC(ai,j,k,l)=base+iNPQ+jPQ+kQ+l

Typedef struct
{ ElemType *base;
  int dim;     维数
  int *bounds;    各维元素个数
  int *constants;  各维包含的基本元素个数
}Array;

例:数组A[3,4,5,6]的存储结构
A.dim=4
A.bounds[]={3,4,5,6}
A.constants[]={120,30,6,1}
A[i,j,k,l]的地址: A.base+(i120+j30+k*6+l)


二、矩阵的压缩存储

1.特殊矩阵

(1)对称矩阵

在这里插入图片描述
在这里插入图片描述

NxN矩阵的存储结构:S[N(N+1)/2]。
以行主序的方式存储下三角元素:
若i>=j: k=i*(i+1)/2+j
若i< j: k=j*(j+1)/2+i

在这里插入图片描述

(2)(上/下)三角矩阵

在这里插入图片描述

例:设N阶对称矩阵A存储为一维数组S[N(N+1)/2],以行主序的方式存储A的下三角,则元素A[3][5]的存储位置是?

在这里插入图片描述

(3)对角矩阵

对角矩阵(diagonal matrix)是一个主对角线之外的元素皆为0的矩阵。对角线上的元素可以为0或其他值。也常写为diag(a1,a2,…,an) 值得一提的是:对角线上的元素可以为 0 或其他值。

因此 n 行 n 列的矩阵 = (ai,j) 若符合以下的性质:ai,j=0且i ≠j,则矩阵为对角矩阵。对角线上全部是0的矩阵是特殊的对角矩阵,不过一般称为零矩阵。

在这里插入图片描述

非0元素的分布:以主对角线为中心的带状区域。
半带宽为d,带宽为2d+1。
非0元素个数:(2d+1)n-(1+d)d
存储方案:S[(2d+1)n],扩展了上下二个三角区域。
aij => S[k], k=i(2d+1)+d+(j-i)
改进方案:S[(2d+1)n-2d],去掉数组前后2d个0。
aij => S[k], k=i(2d+1)+(j-i)

在这里插入图片描述
在这里插入图片描述

(4)稀疏矩阵

稀疏矩阵:若矩阵A中非0元素的个数远远小于零元素的个数,则称A为稀疏矩阵
在这里插入图片描述

三元组表示法

将稀疏矩阵存储在二维数组中

在这里插入图片描述

将二维数组对应位置的值,转换到三元组表中,其中第一行不存值
第一行 00位置 代表有几个非零元素
01位置 代表有几行 02 位置代表有几列

剩下的从第1行开始按行主序
0列填入元素值
1列填入元素所在行
2列填入元素所在行列

在这里插入图片描述

三元组结构表示
typedef struct{
	float val;
	int i,j;
}Trimat
Trimat trimat[maxsize+1];
//取值方法
float val = trimat[k].val;
int i = trimat[k].i;
int j = trimat[k].j;
三元组的顺序表结构
#define MAXSIZE 1000
typedef struct //三元组结构
{ 	int i,j;
  	ElemType e; 
}Triple;
typedef struct  //三元组顺序表(按行主序)
{ 	int mu,nu,tu;
	Triple data[MAXSIZE];//静态顺序表
	/* Triple *data; 动态顺序表 */
}TSMatrix;

稀疏矩阵的顺序表示
其中 mu代表行 nu 代表列 tu 代表非零元素的个数

在这里插入图片描述

三元组的转置运算M=>T (以T为中心)

在这里插入图片描述

根据转置后的结果画转置后的顺序表

ijeije
01300110
10101030
134012-5
21-5=>2320
24503140
32203330
33304250

代码实现转置

Status TSMatrix_Transpose(TSMatrix M,TSMatrix &T) 
{   int k,col,i;
	T.mu=M.nu; T.nu=M.mu; T.tu=M.tu;
  	if(T.tu==0) return(OK);
	for(k=0,col=0; col<M.nu; col++)    //找M中所有列
	    for(i=0; i<M.tu; i++) 
	      if(M.data[i].j==col)
	        { T.data[k].i=M.data[i].j;
	          T.data[k].j=M.data[i].i;
	          T.data[k].e=M.data[i].e;  k++;
	        }
  	return(OK);
}

思考:时间复杂度:

若M有n列,有t个非0数:为O(nt)。
若t≈m
n: 为O(nmn)。

三元组的快速转置运算M=>T (以T为中心)

辅助数据结构:
num[]:M中每列(T中每行)的三元组个数。
=> pos[]:T中每行第一个三元组的下标。

pos[0]=0;
pos[j]=pos[j-1]+num[j-1]; j∈[1,M.nu-1]

在这里插入图片描述

快速转置讲解

在这里插入图片描述

小练习
在这里插入图片描述
答案:
在这里插入图片描述

快速转置代码如下:(不是强制要求)*

Status TSMatrix_FastTranspose(TSMatrix M,TSMatrix &T)
{ 	int i,j,k,num[MAX],pos[MAX];
	T.mu=M.nu; T.nu=M.mu; T.tu=M.tu;
 	 if(T.tu==0) return(OK);
 	 //统计M中每列的三元组个数
  	for(i=0;i<M.nu;i++)   num[i]=0;
	for(i=0;i<M.tu;i++)   num[M.data[i].j]++;
	//计算pos数组
	for(pos[0]=0,i=1;i<M.nu;i++)
 	pos[i]=pos[i-1]+num[i-1];
	//快速转置
	for(i=0;i<M.tu;i++)   
	{   col=M.data[i].j;  loc=pos[col];
		T.data[loc].i=M.data[i].j;
		T.data[loc].j=M.data[i].i;
		T.data[loc].e=M.data[i].e;   pos[col]++;
   }
	return(OK); 
}

思考:时间复杂度:

若M有n列,有t个非0数:循环次数:n+t+n+t
若t≈mn: 为O(mn)。
空间复杂度:2个数组(n+n个单元),O(n)。


三、广义表

1.什么是广义表?

广义表又称列表,也是一种线性存储结构,通数组类似,广义表中即可存储不可再分的元素也能存储可在分元素。线性表的扩展,每个元素的结构不等。
GList={a1,a2,…,an | ai∈AtomSet或ai∈GList}
ai∈AtomSet:ai为原子
ai∈Glist :ai为子表
数据关系:顺序关系、层次关系。
a1为表头(head)元素, 其余为表尾(tail)。
应用实例:Lisp语言、前缀表达式
(setq x (+ 4 (- a b)) y (+ 3 4) )
表头:函数名; 表尾:参数表

例如:数组中可以存储‘a’、3这样的字符或数字,也能存储数组,比如二维数组、三维数组,数组都是可在分成子元素的。广义表也是如此,但与数组不同的是,在广义表中存储的数据是既可以再分也可以不再分的,形如:{1,{1,2,3}}。

如果创建一个二维数组来存储{1,{1,2,3}}。在存储上确实可以实现,但无疑会造成存储空间的浪费。

广义表记作:

LS = (a1,a2,…,an)

2.原子和子表

广义表中存储的单个元素称为 “原子”,而存储的广义表称为 “子表”。

例如创建一个广义表 LS = {1,{1,2,3}},我们可以这样解释此广义表的构成:广义表 LS 存储了一个原子 1 和子表 {1,2,3}。
以下是广义表存储数据的一些常用形式:

  • A = ():A 表示一个广义表,只不过表是空的。
  • B = (e):广义表 B 中只有一个原子 e。
  • C = (a,(b,c,d)) :广义表 C 中有两个元素,原子 a 和子表 (b,c,d)。
  • D = (A,B,C):广义表 D 中存有 3 个子表,分别是A、B和C。这种表示方式等同于 D = ((),(e),(b,c,d)) 。
  • E = (a,E):广义表 E 中有两个元素,原子 a 和它本身。这是一个递归广义表,等同于:E = (a,(a,(a,…)))。

    注意,A = () 和 A = (()) 是不一样的。前者是空表,而后者是包含一个子表的广义表,只不过这个子表是空表。

3.表头和表尾

当广义表不是空表时,称第一个数据(原子或子表)为"表头",剩下的数据构成的新广义表为"表尾"。
强调一下,除非广义表为空表,否则广义表一定具有表头和表尾,且广义表的表尾一定是一个广义表。
例如在广义表中 LS={1,{1,2,3},5} 中,表头为原子 1,表尾为子表 {1,2,3} 和原子 5 构成的广义表,即 {{1,2,3},5}。
再比如,在广义表 LS = {1} 中,表头为原子 1 ,但由于广义表中无表尾元素,因此该表的表尾是一个空表,用 {} 表示。

4.广义表的存储结构

typedef enum {ATOM,LIST} ElemTag;
typedef struct GLNode
{ 
	ElemTag tag;          //区别子表结点、原子结点
  	union
	   { AtomType atom;
	     struct GLNode *child; //指向子表的指针、
	   }UNION;
  	struct GLNode *next;  //后继指针
}GLNODE, *Glist;

5.绘制存储结构(考点)

在这里插入图片描述

练习: C= ( 1, (2, 3, (4,5)) , 6 , (7) )
在这里插入图片描述

6.广义表的创建(不作要求)

GLNODE* Glist_Create(char str[], int* pi) {
	while (str[*pi]==' ')
	{
		(* pi)++;
	}

	GLNODE* p;  char e;
	switch (str[*pi])
	{
	  case '(':
			p = (GLNODE *)malloc(sizeof(GLNODE));
			p->tag = LIST;
			(* pi)++;
			p->UNION.child = Glist_Create(str, pi);
			p->next = Glist_Create(str, pi);
		break;
	  case ')':
		  (*pi)++;
		  p = NULL;
		break;
	  case '\0':
		  p = NULL;
		break;
	  default:
		  p = (GLNODE*)malloc(sizeof(GLNODE));
		  p->tag = ATOM;
		  p->UNION.atom = str[*pi];
		  (*pi)++;
		  p->next = Glist_Create(str, pi);
		break;
	}
	return p;
}

7.广义表的遍历

void Glnode_traverse(GLNODE* p) {
	GLNODE* p1 = p;
	while (p1)
	{
		if (p1->tag == ATOM) {
			printf("%c", p1->UNION.atom);
		}
		else {
			printf("(");
			Glnode_traverse(p1->UNION.child);
			printf(")");
		}
		p1 = p1->next;
	}	
}

8.广义表的深度

在这里插入图片描述

int GetDepth(GLNODE* p) {
	GLNODE* p1 = p;
	if (p1->tag == 0) {
		return 0;
	}
	int dep = 0;
	int max_depth = 0;
	p1 = p1->UNION.child;
	while (p1)
	{
		dep = GetDepth(p1);
		if (dep > max_depth) {
			max_depth = dep;
		}
		p1 = p1->next;
	}
	return max_depth + 1;
}

9.广义表的长度(宽度)

在这里插入图片描述

int  GetLength(GLNODE* p) {
	GLNODE* p1 = p->UNION.child;
	int i = 0;
	while (p1->next)
	{
		i++;
		p1 = p1->next;
	}
	return i;
}

总结

只是为了考试,只是些基础

  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

是阿鹏啊~

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

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

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

打赏作者

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

抵扣说明:

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

余额充值