第四章、数组与字符串
一、数组
1.数组定义
- 下标index 和值value;每个偶对形如: (index,value)
示例:
2.数组顺序表达
2.1一维数组:
设第一个数组元素a[0]的地址是loc(a[0]),若已知每个元素占k个存储单元,则a[i]的地址loc(a[i])为
loc(a[i])=loc(a[0])+i*k (i=0,1,2,…,n-1)。
2.2二维数组:
二维数组a[m][n]映射到一维的存储空间时有两种顺序:
行优先和列优先。
2.2.1行优先顺序地址计算:
若已知每个元素占k个存储单元,第一个数组元素a[0][0]的地址是loc(a[0][0]),则数组元素a[i][j]的地址loc(a[i][j])为
loc(a[i][j])=loc(a[0][0])+(i*n+j)*k (0i<m;0j<n)
2.2.2列优先顺序存储:
loc(a[i][j])=loc(a[0][0])+(j*m+i)*k (0i<m;0j<n)
总结:
-
行优先:loc(a[i][j])=loc(a[0][0])+(i×n+j)×k
-
列优先:loc(a[i][j])=loc(a[0][0])+(j×m+i)×k
3.数组的抽象数据类型:
ADT Array{
数据:
下标i = < i1, i2, …, in >和元素v的偶对<i, v>集合。其中n是数组维度,i1, i2, …, in 表示元素在数组各个维度的下标值;数组在各个维度的长度分别是m1, m2, …, mn;数据元素类型为ElemType。
运算:
CreateArray (A, m1, m2, …, mn):创建运算,申请n维数组A所需存储空间并分配给数组A;成功分配则返回OK;否则返回ERROR
DestroyArray (A):清除运算,A存在则撤销数组返回OK,否则返回ERROR;
RetrieveArray (A, i1, i2, …, in, x):数组元素查询运算,在参数x中返回下标为i1, i2, …, in的数组元素;若数组不存在或某下标越界,则返回ERROR
StoreArrayItem (A, i1, i2, …, in, x):数组元素赋值运算,将下标为i1, i2, …, in的数组元素值设置为x;
OutputArray (A):数组输出运算,将数组中所有元素依次输出;
CopyArray (A, B): 数组拷贝运算,将数组A中元素依次拷贝到数组B中;
……
}
3.1数组操作代码演示:
//实现一个三维整型数组类型TDArray及其运算。
typedef struct triplearray
{
int m1;
int m2;
int m3;
int *array;
}TripleArray;
Status CreateArray (TripleArray *triArray, int m1, int m2, int m3)
{
triArray ->m1 = m1;
triArray ->m2 = m2;
triArray ->m3 = m3;
triArray ->array = (int *)malloc(m1*m2*m3*sizeof(int));
if(! triArray ->array) return ERROR;
return OK;
}
Status DestroyArray (TripleArray *triArray)
{
if(!triArray) return ERROR;
if(triArray ->array)
free(triArray ->array);
return OK;
}
Status RetrieveArray(TripleArray triArray, int i1, int i2, int i3, int* x)
{
if (!triArray.array) return NotPresent;
if (i1 < 0 || i2 < 0 || i3 < 0 || i1 >= triArray.m1
|| i2 >= triArray.m2 || i3 >= triArray.m3)
return IllegalIndex;
*x = *(triArray.array + i1 * triArray.m2 * triArray.m3 + i2 * triArray.m3 + i3);
return OK;
}
Status StoreArrayItem(TripleArray* triArray, int i1, int i2, int i3, int x)
{
if (!triArray->array) return NotPresent;
if (i1 < 0 || i2 < 0 || i3 < 0 || i1 >= triArray->m1
|| i2 >= triArray->m2 || i3 >= triArray->m3)
return IllegalIndex;
*(triArray->array + i1 * triArray->m2 * triArray->m3 + i2 * triArray->m3 + i3) = x;
return OK;
}
void OutputArray(TripleArray triArray)
{
int i1, i2, i3;
for (i1=0;i1<triArray.m1 ;i1++)
for (i2=0;i2<triArray.m2;i2++)
for (i3 = 0; i3 < triArray.m3; i3++)
{
int value;
RetrieveArray(triArray, i1, i2, i3, &value);
printf("array [%d][%d][%d]=%d\n", i1, i2, i3, value);
}
}
int main()
{
int i1, i2, i3;
TripleArray TripleArrayA, TripleArrayB;
CreateArray(&TripleArrayA, 2, 2, 2);
CreateArray(&TripleArrayB, 2, 2, 2);
for (i1 = 0; i1 < TripleArrayA.m1; i1++)
for (i2 = 0; i2 < TripleArrayA.m2; i2++)
for (i3 = 0; i3 < TripleArrayA.m3; i3++)
{
StoreArrayItem(&TripleArrayA, i1, i2, i3, 10);
StoreArrayItem(&TripleArrayB, i1, i2, i3, 5);
}
OutputArray(TripleArrayA);
OutputArray(TripleArrayB);
CopyArray(&TripleArrayA,&TripleArrayB);
OutputArray(TripleArrayA);
OutputArray(TripleArrayB);
return 0;
}
4.矩阵
4.1对称矩阵 (行列优先及上下三角问题)
n 2 n^2 n2个元素的对称矩阵,只需要n(n+1)/2个元素的存储空间。
4.1.2对称矩阵的行优先存储
- 若用一维数组B[num]以行优先顺序存储下三角(包括对角线)元素,则矩阵元素aij的下标(i,j)和该元素在数组B中的存储位置k之间有如下关系:
- 行优先顺序存储上三角关系如下:
4.2上(下)三角矩阵
4.3稀疏矩阵
-
定义:大多数元素为零的矩阵称为稀疏矩阵。
矩阵中非零元素数量占元素总数的比例称为矩阵的稠密度
当矩阵的稠密度很小,即包含大量零元素的矩阵称为稀疏矩阵 (sparse matrix)
通常认为稠密度小于5%的矩阵即可视为稀疏矩阵
稀疏矩阵中零元素的位置分布没有规律。
稀疏矩阵常出现于大规模集成电路设计、图像处理等应用领域。
由于稀疏矩阵中只有少量非零元素,为了节省存储空间,对稀疏矩阵可以只存储非零元素。 -
稀疏矩阵ADT
ADT SparseMatrix{
数据: 大多数元素为零的矩阵。
运算:
CreateMatrix(M, m,n):构造运算,构造一个m×n的空稀疏矩阵。
Clear(M):清除运算,清除稀疏矩阵中的所有非零元素。
Transpose(A):转置运算,返回稀疏矩阵A的转置矩阵。
Madd(A, B):加法运算,返回稀疏矩阵A和B的和。
MMulti(A, B):乘法运算,返回稀疏矩阵A和B的积。
StoreSparseMatrixItem (M, i, j, x):稀疏矩阵元素赋值运算,设置稀疏矩阵中下标为i, j的元素值为x。
RetrieveSparseMatrix (M, i, j, x):稀疏矩阵元素查找运算,在参数*x中返回零值。
OutputSparseMatrix (A):稀疏矩阵输出运算,将矩阵所有非零元素依次输出。
}
4.3.1稀疏矩阵顺序存储
(1)三元组表达式:
稀疏矩阵Am×n中的每一个非零元素 aij 的行号i、列号j和值v表示为一个三元组,即:( i, j, v)
(2)行(列)存储:
(3)三元组的存储结构:
typedef struct term{
int row; //非零元在稀疏矩阵中的行下标row
int col; //非零元在稀疏矩阵中的列下标col
ElemType value; //非零元的值
}Term;
typedef struct sparsematrix{
int m; //矩阵行数
int n; //矩阵列数
int t; //非零元个数
Term *table; //存储非零元的三元组表
}SparseMatrix;
4.4矩阵转置方法
- 前件知识:
4.4.1稀疏矩阵的简单转置算法
- 采用二维数组A存储一个普通 m×n 矩阵,假设将矩阵转置结果存储到 n×m 矩阵B中:
for(int i=0; i<m; i++)
for(int j=0; j<n; j++)
B[j][i] = A[i][j];
//时间复杂度为O(m*n)
4.4.2三元组实现矩阵转换
- 如果稀疏矩阵Am×n用行三元组M表示,
转置矩阵保存在一维数组T中,转置之后的T应仍为行三元组。
(1)方法一:将数组 M 中的元素的行、列号交换后保存到 T 数组中;然后按 T 中的行号排序。
(2)方法二:对数组 M(矩阵Am n的三元组) 扫描 n 遍,每遍扫描t次。第 i 遍扫描,找到M中列号为i的元素(即A中第i列中的非0元素),将该元素转置后依次放入T中。
k=0;
for(i=0; i<n; i++)
for(j=0; j<t; j++)
if(M[j].col == i) {
T[k].col = M[j].row;
T[k].row = M[j].col;
T[k++].value = M[j].value;
}
//时间复杂度为O(n*t)
t是T的长度。
(3)方法三:快速转置
#include <stdio.h>
#include <stdlib.h>
typedef struct {
int row, col;
int value;
} Triple;
void transpose(int m, int n, int len, Triple* a, Triple* b) {
int* num, * cpot;
num = (int*)calloc(n + 1, sizeof(int));
cpot = (int*)calloc(n + 1, sizeof(int));
for (int i = 0; i < len; i++) {
num[a[i].col]++;
}
cpot[1] = 0;
for (int i = 2; i <= n; i++) {
cpot[i] = cpot[i - 1] + num[i - 1];
}
for (int i = 0; i < len; i++) {
int col = a[i].col;
int pos = cpot[col];
b[pos].row = a[i].col;
b[pos].col = a[i].row;
b[pos].value = a[i].value;
cpot[col]++;
}
free(num);
free(cpot);
}
int main() {
Triple a[] = { {1, 2, 3},
{1, 3, 4},
{2, 1, 5},
{3, 2, 6} };
int m = 3, n = 3, len = sizeof(a) / sizeof(Triple);
Triple* b = (Triple*)malloc(len * sizeof(Triple));
transpose(m, n, len, a, b);
printf("原始稀疏矩阵:\n");
for (int i = 0; i < len; i++) {
printf("(%d,%d,%d)\n", a[i].row, a[i].col, a[i].value);
}
printf("转置稀疏矩阵:\n");
for (int i = 0; i < len; i++) {
printf("(%d,%d,%d)\n", b[i].row, b[i].col, b[i].value);
}
free(b);
b[i].row, b[i].col, b[i].value);
}
free(b);
}
逻辑解释:col[i] 记录列数;num[i] 记录非零元素个数;K[i] 表示首个非零元素在三元组中的位置。