前言
Hi, 我是Rike,欢迎来到我的频道~
本篇为大家带来的是,C语言版数据结构笔记,第五章-数组、矩阵、广义表。数组是最基本的线性结构,矩阵是数组二维化的表示(即,二维数组),而广义表是一种更加灵活的结构表示,可以用来构建树或具有递归性质的结构。三者都属于承上启下的存在。
本篇中,只对基本算法形式讲述,涉及数学层次的知识就不叙述啦🤣~~~
希望各位多多支持~
压缩存储:指为多个值相同的元素只分配一个存储空间,而对于零元素不分配空间,其目的就是节省存储空间。
一、数组
数组是用来存储具有 "一对一" 逻辑关系数据的线性存储结构。
数组和其他线性存储结构不同。顺序表、链表、栈和队列存储的都是不可再分的数据元素,而数组既可以用来存储不可再分的数据元素,也可以用来存储像顺序表、链表这样的数据结构。
无论数组的维数是多少,数组中的数据类型都必须一致。
(一)逻辑表示
1、一维数组
dataType a[n];
//其中dataType为数据类型,如int型
2、二维数组
dataType a[m][n];
//其中dataType为数据类型,如int型
(二)二维数组存储方式及地址计算公式
1、存储方式
数组 A [ n ] [ m ],行优先时,n 为行m 为列(前行后列);列优先时,m 为行n 为列(前列后行)。
(1)行优先存储
先存储在第一行中,本行存满转至下行继续存储。
即,以行序为主(先行后序):按照列号从小到大的顺序,依次存储每一行的元素。
(2)列优先存储
先存储在第一列中,本行存满转至下列继续存储。
即,以列序为主(先列后行):按照行号从小到大的顺序,依次存储每一列的元素。
2、计算公式
设有数组 A [ n ] [ m ](n∈[1 , n],m∈[1, m. ]),数组的每个元素长度为 L ,求 A [ i , j ]的存储地址。
(1)行优先
- 首下标 1:LOC( i , j ) = LOC( 1 , 1 ) + ( ( i - 1 ) * n + ( j - 1 ) ) * L
- 首下标 0:LOC( i , j ) = LOC( 0 , 0 ) + ( i * n + j ) * L
(2)列优先
- 首下标 1:LOC( i , j ) = LOC( 1 , 1 ) + ( ( j - 1 ) * m + ( i - 1 ) ) * L
- 首下标 0:LOC( i , j ) = LOC( 0 , 0 ) + ( j * m + i ) * L
注:
- LOC( i , j ):a( i , j ) 的存储位置。
- LOC( 0 , 0 ):a( 0 , 0 ) 的存储位置。
- m(n):数组的总行(列)数。
- L:单个数据元素占据的存储单元,即元素长度。
- 偏移量:相对于某个元素,距离其有几个存储单元。
二、矩阵
(一)矩阵
Amn 为一个矩阵的逻辑表示,可用二维数组来存储。
#define m 4
#define n 4
dataType A[m][n];
//其中dataType为数据类型,常用int、float型
//m、n可预先宏定义
(二)基本操作
1、矩阵转置
void trsmat(int A[][maxsize],int B[][maxsize],int m,int n)
{
for(int i=0;i<m;++i)
for(int j=0;j<n;++j)
B[j][i]=A[i][j];
}
2、矩阵加法
void addmat(int C[][maxsize],int A[][maxsize],int B[][maxsize],int m,int n)
{
for(int i=0;i<m;++i)
for(int j=0;j<n;++j)
C[i][j]=A[i][j]+B[i][j];
}
3、矩阵相乘
void mutmat(int C[][maxsize],int A[][maxsize],int B[][maxsize],int m,int n,int k)
{
for(int i=0;i<m;++i)
for(int j=0;j<k;++j)
{
C[i][j]=0;
for(int h=0;h<n;++h)
C[i][j]=A[i][h]*B[h][j];
}
}.
(三)特殊矩阵
这里所说的特殊矩阵,主要分为以下两类:
- 含有大量相同数据元素的矩阵,比如对称矩阵。
- 含有大量 0 元素的矩阵,比如稀疏矩阵、上(下)三角矩阵。
1、对称矩阵
矩阵中的元素满⾜ ai,j = a i,j (数据元素沿着主对角线对应相等) 的矩阵称之为对称矩阵(矩阵必须是方阵)。
由于对称矩阵中的元素关于主对角线对称,因此在存储时可只存储对称矩阵中上三角或下三角中的元素,使得对称的元素共享一个存储空间。
这样,就可以将 n2个元素压缩存储到 n(n+1)/2 个元素的空间中。以行序为主序存储其下三角 + 对角线的元素。
ai,j 元素下标之间的对应关系如下:
- i > = j 时,存储下三角元素。
- i < j 时,存储上三角元素。
对称矩阵的实现过程是:将各元素所在的行标 i 和列标 j 代入公式,得到数组中的地址 k。
2、三角矩阵
(1)上三角矩阵
上三角矩阵是指下三角区所有元素均为同一常量,设有下三角矩阵 ai,j[ j ]如下图所示:
元素下标之间的对应关系为:(1 <= i,j <= n)
-
按行优先存储,并存储上三角:
-
按列优先存储,并存储上三角:
上三角矩阵在内存中的压缩存储形式如下:
(2)下三角矩阵
下三角矩阵是指上三角区所有元素均为同一常量,设有下三角矩阵 ai,j 如下图所示:
元素下标之间对应关系为:(1 <= i,j <= n)
-
按行优先存储,并存储下三角:
-
按列优先存储,并存储下三角:
上三角矩阵在内存中的压缩存储形式如下:
3、对角矩阵
对角矩阵指所有非零元素都集中在以主对角线为中心的 3 条对角线的区域,其他区域均为零的矩阵;
设有对角矩阵 ai,j如下图所示:
对角矩阵采用压缩存储时,是将 3 条对角线上的元素 ai,j(1=<i,j= <n,| i - j | =<1)
在一维数组 B 中存放下标为 k = 2i + j - 3。
对角矩阵在内存中的压缩存储形式如下:
(四)稀疏矩阵
假设在 m×n 得矩阵中,有 t 个元素不为零。令 δ = t / (m×n),称 δ 为矩阵的稀疏因子。通常认为 δ <= 0.05 时称为稀疏矩阵。
即,矩阵中绝大多数都为相同元素 C(一般假设为 0)的矩阵为稀疏矩阵。
1、顺序存储 - 三元组表示法
三元组数据结构为一个长度为 n,表内每个元素都有 3 个分量的线性表,其 3 个分量分别为:值、行下标、列下标。
稀疏矩阵:
对其压缩后:
或这种表示形式:
(1)结构体定义
/*三元组结构体*/
typedef struct
{
int val;//int可替换,元素的值
int i,j;//行、列
}Trimat;
/*矩阵的结构表示*/
typedef struct {
Trimat data[number];//存储该矩阵中所有非0元素的三元组
int n,m,num;//n和m分别记录矩阵的行数和列数,num记录矩阵中所有的非0元素的个数
}TSMatrix;
(2)定义一个含有 maxterms 个非零元素的稀疏矩阵
Trimat triamt[maxterms+1];//maxterms是已经定义的常量
trimat[k].val;//表示取第k个非零元素的值
trimat[k].i;trimat[k].j;//表示取第k个非零元素在矩阵中的行下标和列下标
(3)数组形式表示
int trimat[maxterms+1][3];
/*第k个非零元素操作*/
trimat[k][0];//表示原矩阵元素按行优先顺序的第k个非零元素的值
trimat[k][1];//表示第k个非零元素在矩阵中的位置
/*第0行元素*/
trimat[0][0];//表示存储非零元素 个数
trimat[0][1];// 行数
trimat[0][2];// 列数
/*float型数组取元素位置,需强制转换类型*/
(int)trimat[k][1];
(int)trimat[k][2];
2、链式存储
(1)邻接表表示法
将矩阵中的每一行的非零元素串连成一个链表,链表结点有两个分量,分别为该结点对应的元素值及其列号。
与“图”中的邻接表是一个东西
(2)十字链表表示法
该存储方式采用的是 "链表 + 数组" 结构。
使用十字链表压缩存储稀疏矩阵时,矩阵中的各行各列都各用一个链表存储,与此同时,所有行链表的表头存储到一个数组(rhead),所有列链表的表头存储到另一个数组(chead)中。
各个链表中结点的结构:
行标(row)、列标(col)、数据域分量(val)、指向下方结点的指针 A(rhead)、指向右方结点的指针 B(chead)。
其中,最左边和最上边是头结点数组,不存储数据信息,左上角的结点为整个十字链表的头结点,有 5 个分量,分别存储矩阵的行数、列数、非零元素的个数、以及指向两个头结点数组的指针。
∧:表示已无下个结点
三、广义表
(一)概念
-
广义表:表元素可以是原子或者广义表的一种线性表的扩展结构,表中存储的单个元素称为 "原子",而存储的广义表称为 "子表"。
-
表长度:表中最上层元素的个数。
广义表规定,空表{} 的长度为 0。由于广义表中可以同时存储原子和子表两种类型的数据,因此在计算广义表的长度时规定,广义表中存储的每个原子、子表只算作是一个数据元素。
-
表深度:表中括号的最大层数,即有几个括号。
-
表头和表尾:当广义表不是空表时,称第一个数据(原子或子表)为"表头",剩下的数据构成的新广义表为"表尾"。
GetHead(表名) = 头数据元素;//得到广义表中第一个原子
GetTail(表名) = 尾数据元素;//得到除首原子外剩下的元素构成的表
强调一下,除非广义表为空表,否则广义表一定具有表头和表尾,且广义表的表尾一定是一个广义表。tail 得到的元素需在外层加一个()。
(二)存储结构
1、常用形式
- A = ( ):A 是一个空表,⻓度为 0,深度为 1。
- B = (d, e):B 的元素全是原子,d 和 e,⻓度为 2,深度为 1。
- C = (b, (c, d)):C 有两个元素,分别是原子 b 和子表(c, d),⻓度为 2,深度为 2。
- D = (B, C):D 中存两个元素 B 和 C,⻓度为 2,深度为 3,D = ((d, e),(b, (c, d))) 。
- E = (a, E):E 有两个元素,原子 a 和它本身,⻓度为 2,这是一个递归广义表,等同于:E = (a,(a,(a,…)))。
A=()和 A=(( ))不同。前者是空表,而后者是包含一个子表为空表的广义表。
- GetHead(D)=B;GetTail(D)=C;GetHead((B,C))=B;GetTail((B,C))=C;
2、头尾链表存储结构
- 原子结点:标记域(tag)、数据域(atom)。
- 广义表结点:标记域(tag)、头指针域(hp)、尾指针域(tp)。
- 标记域:用于区分当前结点是原子(用 0 来表示)、广义表(用 1 来表示)。
- 头指针域:指向原子或广义表结点。
- 尾指针域:为空或指向本层中的下一个广义表结点。
3、扩展线性表存储结构
- 原子结点:标记域(tag)、数据域(atom)、尾指针域(tp)。
- 广义表结点:标记域(tag)、头指针域(hp)、尾指针域(tp)。
参考资料
个人学习记录,若有侵权,请留言联系
- 2022 天勤计算机考研高分笔记-数据结构
- 2022 王道计算机考研复习指导-数据结构
- 解学武数据结构与算法教程(C 语言版):data.biancheng.net/