1.数组
首先为了方便这里数组是从下标1开始的
(1)数组的抽象数据类型:
ADT Array
Data:
Operator:
InitAarray(A,n,bound1,....,bound)//若维数n和各维数长度合法。构造相应的数组A,并且返回TRUE。
DestroySArray(A)//销毁数组A
GetValue(A,*e,index1...)//若下标合法则用e返回数组A中由index所指定的值并返回给e
SetValue(A,e,index1...)//若下标合法,则将数组A中由index所指定的元素的值置为e
(2)三维数组的地址计算,设每个下标占用size个存储单元
对于一个三维数组A[r][m][n],这里以行为主序储存的话(把三维数组想成一个魔方),可以这么想,r相当于一个层数,顶层相当于第一层,然后就把吗m,n,看成一个二维数组就行了。
设a111首地址是loc[1,1,1]
那么a[i,j,k]=a[1,1,1]+[(i-1)mn+(j-1)n+(k-1)]size;
(3)将三维数组推广到一般情况:
设三维数组下限为c1,c2,c3上限为d1,d2,d3每个元素占size个存储单元则
用表示loc[j1,j2,j3]=loc[c1,c2,c3]+(d2-c2+1)(d3-c3+1)(j1-c1)size+(d3-c3+1)size(d3-c3+1)(j2-c2)+size*(j3-c3);
设a1=size*(d2-c2+1)(d3-c3+1).
a2=size(d3-c3+1)
a3=size;
loc[j1,j2,j3]=a1*(j1-c1)+a2*(j2-c2)+a3*(j3-c3)
loc[j1,j2,j3]=∑ai*(ji-ci);
那么对于多维数组
loc[j1,j2,j3…jn]=loc[c1,c2…cn]+∑(1~n)ai(ji-ci);
(4)压缩存储(把二维变一维数组)
二维数组通常用来存储矩阵
三角矩阵有三种设A[i][j] (i为行,j为列)
1)当i>=j时,其余为0的称为下三角矩阵
2)当i<=j时,其余为0的称为上三角矩阵
3)若矩阵都满足a[i][j]=a[j][i];称为对称矩阵
1)以下三角矩阵为例:
那么它是如何压缩存储的:把二维矩阵压缩成一维矩阵
地址如何计算:
loc[i,j]=loc[1,1]+i(i-1)/2+(j-1);
其中(i-1)i/2表示前i-1行的元素个数。
2)以上三角矩阵为例:
设第一行有n个元素
第二行有n-1;
第三行n-1;
第i行n-i+1;
那么i-1行有:n-i+2;
前i-1行共有(i-1)(2n-i+2)/2个
那么地址a[i][j]=loc[1,1]+(i-1)(2n-i+2)/2+(j-1)
3)对于对称矩阵对两个相同元素分配一个储存空间,只保留上三角或者下三角,可以将一个nn的元素压缩到n(n+1)/2个空间中
4)带状矩阵(对角矩阵有n行):所有非0元素在主对角线两侧的带状区域内,m对角矩阵是指一行最多有m个元素,下边就是三角矩阵
那么压缩方法(对于上图)是
确定压缩的一维数组储存空间为2+3(n-2)+2=3n-2;
前i-1行共有3(i-1)-1;*
第i行a[i][j]前有(j-i)+1个元素:这里是怎么来的呢吗,由于对角线元素都是j-i=0;那么此时前有1个元素,那么如果j-i=-1那么他就是哪一行的第一个元素,所以总结出来就是(j-i)+1
那么 a[i][j]的地址: loc[i,j]=loc[1,1]+3(i-1)-1+j-i+1
=loc[1,1]+2(i-1)+j-1;
5)稀疏矩阵
稀疏因子(一般是小于33%):非零元素在整个矩阵中的占比,即数组中元素个数小于数组大小的33%
就是元素存储在二维数组中无规律,采用压缩存储的方法叫三元组,即就只存储非零元素的行下标值,列下标值,元素值,一个三元组确定一个非零元素值。
#include<stdio.h>
#include<stdlib.h>
#define max 1000
typedef struct{
int row;//行下标
int col;//列下标
int e;//元素
}Trip;
typedef struct{
Trip data[max+1];//三元数组
int m;//矩阵行数
int n;//矩阵列数
int len;//矩阵非0元素的个数
}Smart;
接下来有一个稀疏矩阵的转置运算,即a[i][j]=b[j][i];
那么这么存就有一个问题,最初的三元数组是按行优先的顺序,
转置后变成列优先了,一般的想法是转置后按行优先排序进行排序,有两种解决办法:
1) 利用列序递增:
#include<stdio.h>
#include<stdlib.h>
#define max 1000
typedef struct{
int row;//行下标
int col;//列下标
int e;//元素
}Trip;
typedef struct{
Trip data[max];
int m;//矩阵行数
int n;//矩阵列数
int len;//矩阵非0元素的个数
}Smart;
void Three(Smart A,Smart *B)
{
B->m=A.n;
B->n=A.m;
B->len=A.len;
if(B->len>0)
{
int j=1;
for(int k=1;k<=A.n;k++)
{
for(int 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++;
}
}
}
}
int main()
{
int a[8]={1,1,3,3,4,5,6,6};//行标
int b[8]={2,3,1,6,3,2,1,4};//列标
int c[8]={12,13,14,15,16,17,18,19};//非零元素
Smart A;
Smart *B;
A.n=8;
A.m=8;
A.len=8;
B=(Smart *)malloc(sizeof(Smart));
for(int i=0;i<8;i++)
{
A.data[i+1].row=a[i];
A.data[i+1].col=b[i];
A.data[i+1].e=c[i];
}
Three(A,B);
for(int i=1;i<=8;i++)
{
printf("%d ",B->data[i].row);
}
printf("\n");
for(int i=1;i<=8;i++)
{
printf("%d ",B->data[i].col);
}
printf("\n");
for(int i=1;i<=8;i++)
{
printf("%d ",B->data[i].e);
}
printf("\n");
}
重点是void three()这个函数
这个函数从列扫描,按照列的顺序进行存储,那么存完就是按照行的顺序进行排的。
2)一次快速定位:
代码:
void firstfast(Smart A,Smart *B)
{
int num[max]={0};
int pos[max]={0};
B->m=A.n;
B->n=A.m;
B->len=A.len;
for(int i=1;i<=A.len;i++)
num[A.data[i].col]++;
pos[1]=1;
for(int i=2;i<=A.n;i++)
pos[i]=num[i-1]+pos[i-1];
for(int i=1;i<=A.len;i++)
{
int h=A.data[i].col;
int q=pos[h];
B->data[q].col=A.data[i].row;
B->data[q].row= A.data[i].col;
B->data[q].e=A.data[i].e;
pos[h]++;
}
}
这里原理是定义两个数组,num[1]里边存的是将要转换的矩阵列为1的元素个数,num[2]里边存的是将要转换的矩阵列数为2的元素个数,依次类推。
pos[1]记录的是列数为1转换后在第一矩阵的第一个位置。依次类推
3)稀疏矩阵的链式存储
十字链表的例子:
#include<stdio.h>
#include<malloc.h>
#include<bits/stdc++.h>
typedef struct OLNode
{
int row;
int col;
int valuue;
struct OLNode *right,*down;
}OLNode,*OLink;
typedef struct
{
OLink *rhead;
OLink *chead;
int m;
int n;
int len;
}CrossList;
void CreateCrossList(CrossList *M)
{
int m;//行
int n;//列
int e;//值
int t;//非0元素个数
int i; //稀疏矩阵元素的行下标
int j;//稀疏矩阵的列下标
OLNode *p,*q;
printf("输入M的行数,列数和非零元素的个数\n");
scanf("%d %d %d",&m,&n,&t);
M->m=m;
M->n=n;
M->len=t;
M->rhead=(OLink *)malloc((m+1)*sizeof(OLink));//由于下标1不用所以多申请一个空间
M->chead=(OLink *)malloc((n+1)*sizeof(OLink));
for(int y=1;y<=m;y++)
M->rhead[y]=NULL;
for(int y=1;y<=n;y++)
M->chead[y]=NULL;
printf("输入:\n");
for(int o=0;o<M->len;o++)
{
scanf("%d %d %d",&i,&j,&e);
p=(OLink )malloc(sizeof(OLNode));
p->row=i;
p->col=j;
p->valuue=e;
if(M->rhead[i]==NULL||M->rhead[i]->row>i)
{
p->right=M->rhead[i];
M->rhead[i]=p;
}
else
{
for (q = M->rhead[i]; (q->right) && q->right->col<j ; q = q->right);
p->right = q->right;
q->right = p;
}
if(M->chead[i]==NULL||M->chead[i]->col>j)
{
p->down=M->chead[i];
M->chead[i]=p;
}
else
{
for (q = M->chead[i]; (q->right) && q->right->row<i ; q = q->down);
p->down = q->down;
q->down = p;
}
}
}
int main()
{
CrossList p;
CreateCrossList(&p);
OLink q;
for(int i=1;i<=p.m;i++)//从第一列到最后一列遍历,这里也可以用行遍历从第一行到最后一行
{
for(q=p.rhead[i];q!=NULL;q=q->down)
printf("%d\n",q->valuue);
}
}
十字链表存储稀疏矩阵:
代码关键在于那两个二级指针,可以理解那两个二级指针是两个指针数组,拿行来说,第一行自然是rhead[1],这个行指针指向矩阵第一行的一个元素,第一个元素的right指向这一个行的第二个元素,依次类推。
2.广义表(简称GL):
1) 是一种非线性结构,是线性表的一种推广,广义表放松对元素的管理,允许结构有自己的结构,一个广义表第一个元素是表头,接下来(第二第三一直到最后的元素加起来称为表尾)
关于定义举下面的例子:
A=();
B=(e);
C=(a,(b,c,d))
D=(A,B,C)=((),(e),(a,(b,c,d)));
E=((a,(a,b),((a,b),c)));
A是一个为空表;
B是含一个元素的表;
C含两个元素,长度为2;
D含有三个表(大写代表)
2)广义表的两种存储方式:
1)头尾链表存储表示:
链表中存在两种结点
一种表结点,一种原子结点(tag是一种标志,标志是表结点还是原子结点):
列子:
那么这个图是如何来的呢(这里可以自定义表结点表头的等级)?
以C为例表中的2个元素都是平行关系,那么就要有两个一级表结点,对于第C里边的尾表(对于C来说就是第二个元素),现在把尾标和C一样的想法,那么有三个二级表结点,一共有5个表结点,那么如此推出表结点的个数,按照等级就会轻易的画出上图
2)扩展性链表的表示;
表结点与原子结点与第一种有点不同:
如何实现上图同样以C为例,注意这里有并列关系,元素a与表尾并列,这里就指向与自己并列的表或者元素,对于C,元素a与第二个元素并列,恰巧第二个元素是一个表,所以就有了元素指向表。这里可以想成一个表里的元素都是同等级,那么就会得到到底是原子结点指向表结点还是表结点指向原子结点等等指向的关系。。。。。。
下面最后一个例子:
L=(f,(b,e),((c,d),a));
两种方式分别是: