目录
一、数组的定义
1.1 相关概念和特点
(1)相关定义
数组是由类型相同的数据元素构成的有序集合,每个元素称为数组元素,每个元素受n(n>=1)个线性关系的约束,可以通过下标访问该数据元素。
(2)相关特点
数组可以看成是线性表的推广,其特点是结构中的元素本身可以是具有某种结构的数据, 但属于同一数据类型。
例如,一维数组可以看成是一个线性表,二维数组可以看成数据元素是线性表的线性表。
数组结构固定,数组行列数不可变、数据元素同构
1、数据结构中最基本的一个结构就是线性结构,而线性结构又分为连续存储结构和离散存储结构。所谓的连续存储结构其实就是数组。
2、在内存中,数组中的数据是以一组连续的数据集合的形式存在于内存中。当我们访问存在于内存中的数组时,我们应该找到其在内存中的地址,当我们找到数据的地址后我们就可以找到对应的数据。
3、如何才能找到数据在内存中的地址?
这个问题其实很简单,因为数组在内存中是一组连续的数据集合,所以我们只要知道数组首地址,然后通过对应字节长度的加减就可以找到对应字节数的数据。
1.2 数组运算
图1.2-1 数组的基本操作
1、由于数组一般不作插入或删除操作,也就是说,一旦建立了数组,则结构中的数据元素个数和元素之间的关系就不再发生变动。
2、由于存储单元是一维的结构,而数组是个多维的结构,则用一组连续存储单元存放数组的数据元素就有个次序约定问题。
3、对于数组,一旦规定了它的维数和各维的长度,便可为它分配存储空间,反之,只要给出一组下标便可求得相应数组元素的存储位置。
4、由于计算各个元素存储位置的时间相等,所以存取数组中任一元素的时间也相等。称这一特点的存储结构为随机存储结构。
二、数组的表示和实现
以下部分操作都较为基本就直接放代码了
2.1 顺序数组的表示和实现
(1)结构体定义与相关宏
2.1-1 数组顺序存储基本方式
代码如下:
typedef struct Arr
{
int *pAddress; //数组首地址
int len; //申请的长度
int occupy; //占用长度
}ARR;
(2)初始化数组
//函数功能:初始化
void init_arr(struct Arr *pArr)
{
printf("请输入初始化数组的长度: ");
scanf("%d", &pArr->len);
pArr->pAddress = (int *)malloc(sizeof (int)*pArr->len);
if(NULL == pArr->pAddress)
{
printf("分配内存失败!\n");
exit(0);
}
else
{
pArr->occupy = 0;
system("cls"); //清屏
Menu(); //菜单
printf("初始化成功\n");
}
return;
}
(3) 查找操作
//函数功能:查找
void find(struct Arr *pArr)
{
int i;
int find;
printf("请输入你要查找的值: ");
scanf("%d", &find);
for(i=0; i<pArr->occupy; i++)
{
printf("查找成功!在第%d个数值为:%d\n",i+1, pArr->pAddress[i]);
if(find==pArr->pAddress[i])
{
system("cls"); //清屏
printf("查找成功!在第%d个位置,数值为:%d\n",i+1, pArr->pAddress[i]);
break;
}
}
if(i==pArr->occupy)
{
system("cls"); //清屏
printf("查无此值\n");
}
}
因为过于基础,其余的部分就留给大家自己实现吧。
三、经典实例
3.1 矩阵的压缩存储(仅以行优先存储为例)
在编写程序时往往都是二维数组表示矩阵,然而在数值分析中经常出现一些阶数很高的的矩阵同时在距震中有很多值相同的元素,或者是零元素,为了节省空间,可以对这类矩阵进行压缩存储,所谓的压缩存储就是,多个值相同的元之分配一个存储空间,对零元不分配空间。
矩阵分类:
1、假如值相同的元素或者零元素在矩阵中的分配有一定的规律,则我们称此类矩阵为特殊矩阵。
2、n阶对称矩阵:满足Aij = Aji 1<=i,j<=n;
3、稀疏矩阵:非零元较零元少,且分布没有规律。 4、上下三角阵:即有上半部(或下半部)元素均为c或0,其余半部随机分布 5、对角阵和三对角阵: 对角矩阵所有非零元素集中在以主对角线为中心的带状区域。三对角矩阵以主对角线为中心的三条带状区域有元素,其余位置为0。
(1)对称矩阵
满足Aij = Aji 1<=i,j<=n;的方阵,称为对称矩阵,我们只需要保存下三角区和主对角线元素:
1 2 4 7 1
2 3 5 8 => 2 3
4 5 6 9 4 5 6
7 8 9 10 7 8 9 10
对于对称矩阵,可以为每一对对称元分配一个存储空间,则可将n^2个元压缩存储到n(n+1)/2 个元的空间中,不失一般性,可以行序为主序存储其下三角(包括对角线)中的元。
假设以一维数组sa[n(n + 1)/2]作为n阶对称矩阵A的存储结构,则sa[k]和矩阵元aij的之间存在着一一对应的关系:
图3.1-1 k与i、j的关系式
对于任意给定的一组下标(i,j),均可在sa中找到矩阵元aij;反之,对所有的k=0,1,2,… ,都能确定sa内中的元在矩阵中的位置(i,j)。由此,称sa[n(n+1)/2]为n阶对称矩阵A的压缩存储
图3.1-2 数组的下标及存储内容
源码实现如下:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define N 4
//为N阶对称矩阵初始化存储空间
void Init_arr(int **p)
{
if(p == NULL)
{
return;
}
//当确定了矩阵的压缩存储方式后,就可以分配存储空间了
*p = (int *)malloc(sizeof(int) *(N*(N+1)/2));
}
//将A[i][j]的值e存储到b中
void arr_Assign(int *b , int e , int i , int j)
{
if(i >= j)
{ //存储下三角的数据元素到数组B中
b[i*(i+1)/2 + j] = e;
}
else
{
//存储上三角的数据元素到数组B中
b[j*(j+1)/2 +i] = e;
}
}
//返回存储在b[M]中的A[i][j]值
int arr_Value(int *b , int i, int j)
{
//取出数组b中的下三角数据元素
if(i >= j)
{
return b[ i * (i + 1) / 2 + j];
}
else
{
return b[j * (j + 1) / 2 + i];
}
}
//输出压缩存储在b中的对称矩阵
void Disp(int *b)
{
int i;
int j;
for(i = 0; i < N; i++)
{
for(j = 0; j < N; j++)
{
printf("%4d" , arr_Value(b , i,j));
}
printf("\n");
}
}
//销毁存储空间
void Destroy(int *b)
{
free(b);
b = NULL;
}
int main(void)
{
int *arrB = NULL;
int i = 0;
int j = 0;
int value = 0;
Init_arr(&arrB);
printf("请输入对称矩阵的下三角部分:\n");
for(i = 0; i < N; i++)
{
printf("输入第%d行的%d个数据元素: ", i+1, i+1);
for(j = 0; j <= i; j++)
{
scanf("%d" , &value);
arr_Assign(arrB,value , i,j);
}
}
//打印输出
printf("输出对称矩阵;\n");
Disp(arrB);
Destroy(arrB);
return 0;
}
(2)上下三角阵
以主对角线划分,三角矩阵有上三角矩阵和下三角矩阵两种。上三角矩阵是指矩阵下三角(不包括对角线)中的元均为常数c或零的n阶矩阵,下三角矩阵与之相反。对三角矩阵进行压缩存储时,除了和对称矩阵一样,只存储其上(下)三角中的元素之外,再加一个存储常数c的存储空间即可。
图3.1-3 上三角阵sa[k]与矩阵元素aij之间的对应关系
图3.1-4 下三角阵sa[k]与矩阵元素aij之间的对应关系
代码逻辑与对称阵类似,有兴趣可自行实现。
(3)对角阵和三对角阵
对角矩阵所有的非零元都集中在以主对角线为中心的带状区域中,即除了主对角线上和直接在对角线上、下方若干条对角线上的元之外,所有其他的元皆为零。对这种矩阵,也可按某个原则(或以行为主,或以对角线的顺序)将其压缩存储到一维数组。
k=2+3*(i-2)+j-i+1=2i+j-3
若已知k,则ai,j为
i=(k+1)/3+1,(k+1:向右偏移一个单位;3:每行3个元素;+1:i从1开始)
j=k-2i+3(由k的计算公式可得)
图3.1-5 对角阵与三对角阵
图3.1-6 三对角阵压缩存储示意图
(4)稀疏矩阵(两种方法,重点!)
矩阵中非零元素的个数t,相对矩阵元素的个数s来说非常少,即s>>t的矩阵称为稀疏矩阵。
第一种方法:三元组
这个时候我们需要建立一个三元组,三元组的作用为:
1.存储非零元素
2.同时存储该非零元素所对应的行下标和列下标
3.稀疏矩阵中的每一个非零元素需由一个三元组(i, j, aij)唯一确定,稀疏矩阵中的所有非零元素构成三元组线性表,三元组中的i就是行下标,j是列下标,aij是对应的元素值。
图3.1-7 三元组法
结构体描述入
struct node
{
int i,j; //定义三元组的行、列号
int v; //三元组的值
};
struct sparmatrix
{
int rows,cols; //稀疏矩阵的行、列数
int terms; //稀疏矩阵的非零元个数
struct node data[maxsize]; //存放稀疏矩阵的三元组表
};
矩阵压缩:
#include<stdio.h>
//判断该矩阵是否为稀疏矩阵
#define m 10
#define n 10
int a[m][n]={
{1,0,0,0,0,0,0,0,0,0}, //自定义数组
{0,0,0,0,0,0,0,0,0,0},
{0,0,0,0,0,0,0,0,0,0},
{1,0,0,0,0,0,0,0,0,0},
{0,0,0,0,0,0,0,0,0,0},
{0,0,0,0,0,0,0,0,0,0},
{0,0,0,0,0,0,0,0,0,0},
{0,0,0,0,0,0,0,0,7,0},
{0,0,0,0,0,0,8,0,0,0},
{0,0,0,0,0,0,0,0,0,0},
};
struct three
{
int i,j; //定义矩阵下标
int value; //下标对应值
};
struct three stu[100];
void compress()
{
int t=0;
for(int r=0;r<m;r++)
{
for(int c=0;c<n;c++)
{
if(a[r][c]!=0) //只存储不为零的元素
{
stu[t].i=r; //行下标
stu[t].j=c; //列下标
stu[t].value=a[r][c];//压缩后矩阵的值用二维数组的下标表示
t++;
}
}
}
}
第二种方法:十字链表法
对于压缩存储稀疏矩阵,归根结底是使用数组存储稀疏矩阵。介于数组 "不利于插入和删除数据" 的特点,以上两种压缩存储方式都不适合解决类似 "向矩阵中添加或删除非 0 元素" 的问题。
例如,A 和 B 分别为两个矩阵,在实现 "将矩阵 B 加到矩阵 A 上" 的操作时,矩阵 A 中的元素会发生很大的变化,之前的非 0 元素可能变为 0,而 0 元素也可能变为非 0 元素。对于此操作的实现,之前所学的压缩存储方法就显得力不从心。
于是我们将使用十字链表法,该存储方式采用的是 "链表+数组" 结构。
图3.1-8 十字链表法
可以看到,使用十字链表压缩存储稀疏矩阵时,矩阵中的各行各列都各用一各链表存储,与此同时,所有行链表的表头存储到一个数组,所有列链表的表头存储到另一个数组中。
图3.1-9 十字链表法节点结构
两个指针域分别用于链接所在行的下一个元素以及所在列的下一个元素。
代码实现如下:
#include<stdio.h>
#include<stdlib.h>
typedef struct OLNode
{
int i, j, e; //矩阵三元组i代表行 j代表列 e代表当前位置的数据
struct OLNode *right, *down; //指针域 右指针 下指针
}OLNode, *OLink;
typedef struct
{
OLink *rhead, *chead; //行和列链表头指针
int mu, nu, tu; //矩阵的行数,列数和非零元的个数
}CrossList;
CrossList CreateMatrix_OL(CrossList M);
void display(CrossList M);
int main()
{
CrossList M;
M.rhead = NULL;
M.chead = NULL;
M = CreateMatrix_OL(M);
printf("输出矩阵M:\n");
display(M);
return 0;
}
CrossList CreateMatrix_OL(CrossList M)
{
int m, n, t;
int i, j, e;
OLNode *p, *q;
printf("输入矩阵的行数、列数和非0元素个数:");
scanf("%d%d%d", &m, &n, &t);
M.mu = m;
M.nu = n;
M.tu = t;
if (!(M.rhead = (OLink*)malloc((m + 1) * sizeof(OLink))) || !(M.chead = (OLink*)malloc((n + 1) * sizeof(OLink))))
{
printf("初始化矩阵失败");
exit(0);
}
for (i = 1; i <= m; i++)
{
M.rhead[i] = NULL;
}
for (j = 1; j <= n; j++)
{
M.chead[j] = NULL;
}
for (scanf("%d%d%d", &i, &j, &e); 0 != i; scanf("%d%d%d", &i, &j, &e)) {
if (!(p = (OLNode*)malloc(sizeof(OLNode))))
{
printf("初始化三元组失败");
exit(0);
}
p->i = i;
p->j = j;
p->e = e;
//链接到行的指定位置
if (NULL == M.rhead[i] || M.rhead[i]->j > j)
{
p->right = M.rhead[i];
M.rhead[i] = p;
}
else
{
for (q = M.rhead[i]; (q->right) && q->right->j < j; q = q->right);
p->right = q->right;
q->right = p;
}
//链接到列的指定位置
if (NULL == M.chead[j] || M.chead[j]->i > i)
{
p->down = M.chead[j];
M.chead[j] = p;
}
else
{
for (q = M.chead[j]; (q->down) && q->down->i < i; q = q->down);
p->down = q->down;
q->down = p;
}
}
return M;
}
void display(CrossList M)
{
for (int i = 1; i <= M.nu; i++)
{
if (NULL != M.chead[i])
{
OLink p = M.chead[i];
while (NULL != p)
{
printf("%d\t%d\t%d\n", p->i, p->j, p->e);
p = p->down;
}
}
}
}