多维数组和广义表
文章目录
- 多维数组和广义表
- 5.1 数组的定义
- 5.2 数组的顺序表示和实现
- 5.3 矩阵的压缩存储
- 5.3.1 特殊矩阵
- 5.3.2 稀疏矩阵
- 三元组顺序表 (Triplet Representation) - 有序的双下标法
- 行逻辑链接的顺序表
- 十字链表法
- 5.4 广义表的定义
- 5.5 广义表的存储结构
- 5.6 m元多项式的表示
- 5.7 广义表的递归算法
- 5.7.1 求广义表的深度
- 5.7.2 复制广义表
- 5.7.3 建立广义表的存储结构
5.1 数组的定义
抽象数据类型的定义
数组一旦被定义,它的维数和维界就不再改变。因此,除了结构的初始化和销毁之外,数组只有存取元素和修改元素值的操作。
5.2 数组的顺序表示和实现
数组类型特点:
-
只有引用型操作,没有加工型操作(一般不作插入或删除操作)
-
数组是多维的结构,而存储空间是一个一维的结构,则用一组连续存储单元存放数组的数据元素就有个次序约定间题。
-
有两种顺序映象的方式:
1) 以行序为主序(低下标优先) - row major order;
2) 以列序为主序(高下标优先) - column major order;n维数组的映像函数,ci是常数
L O C ( j 1 , j 2 , . . . , j n ) = L O C ( 0 , 0 , . . . , 0 ) + ∑ i = 1 n c i j i ( c n = L , c i − 1 = b i × c i , 1 < i ≤ n ) LOC(j_1,j_2,...,j_n) = LOC(0,0,...,0) + \sum_{i=1}^nc_ij_i \\(c_n = L,c_{i-1} = b_i \times c_i, 1 < i ≤ n) LOC(j1,j2,...,jn)=LOC(0,0,...,0)+i=1∑nciji(cn=L,ci−1=bi×ci,1<i≤n)
#define _CRT_SECURE_NO_WARNINGS 1
#include <stdarg.h> //包含可变参数列表的头文件。
#include <stdio.h> //包含标准输入输出库。
#include <stdlib.h> //包含标准库函数,如内存分配函数malloc和free。
#define MAX_ARRAY_DIM 8 //假设数组维数的最大值为8
typedef struct {
int* base; // 数组元素基址,由 InitArray 分配 - 指向数组元素的指针
int dim; // 数组维数
int* bounds; // 数组维界基址,由 InitArray 分配 - 指向数组各维度大小的指针
int* constants; // 数组映像函数常量基址,由 InitArray 分配
} Array;
int InitArray(Array* arr, int dim, ...) {
if (dim < 1 || dim > MAX_ARRAY_DIM) return 0; // 维数不合法
// 分配内存并初始化维度信息
arr->dim = dim;
arr->bounds = (int*)malloc(dim * sizeof(int));
if (!arr->bounds) {
fprintf(stderr, "Memory allocation failed\n");
exit(EXIT_FAILURE);
}
arr->constants = (int*)malloc(dim * sizeof(int));
if (!arr->constants) {
free(arr->bounds); // 释放之前分配的内存
fprintf(stderr, "Memory allocation failed\n");
exit(EXIT_FAILURE);
}
// 使用可变参数设置各维度长度,并计算总元素个数
va_list ap; //va_list是C语言中用于访问可变参数列表的类型。
va_start(ap, dim); //初始化可变参数列表, 这行代码告诉编译器,可变参数列表是从dim这个参数之后开始的。
int totalElements = 1; //用于存储数组的总元素数量
for (int i = 0; i < dim; i++) {
arr->bounds[i] = va_arg(ap, int); // 从可变参数列表中获取维度长度: 要获取的下一个参数的类型为int,每次调用va_arg都会更新ap的值,使其指向下一个参数。
totalElements *= arr->bounds[i]; // 计算总元素个数
}
va_end(ap); // 结束可变参数列表
// 分配内存并初始化数组元素
arr->base = (int*)malloc(totalElements * sizeof(int));
if (!arr->base) {
free(arr->bounds); // 释放bounds数组
free(arr->constants); // 释放constants数组
fprintf(stderr, "Memory allocation failed\n");
exit(EXIT_FAILURE);
}
// 计算映像函数常数
arr->constants[dim - 1] = 1; // 最后一个维度的映像函数常数为1
for (int i = dim - 2; i >= 0; i--) {
// c<i> = b<i+1> * c<i+1> ,1<i≤n
arr->constants[i] = arr->bounds[i + 1] * arr->constants[i + 1]; // 计算其他维度的映像函数常数
}
return 1; // 返回成功
}
// 释放数组内存的函数
void FreeArray(Array* arr) {
free(arr->base);
free(arr->bounds);
free(arr->constants);
}
int Locate(Array *A, va_list ap, int* off) //计算出数组中某个元素的相对地址off
{
//若ap指示的各下标值合法,则求出该元素在A中相对地址off
*off = 0;
int index;
for (int i = 0; i < A->dim; ++i)
{
index = va_arg(ap, int);
if (index < 0 || index >= A->bounds[i]) //检查当前索引index是否在合法范围内。
return -1;
*off += A->constants[i] * index; //使用之前计算的映像函数常数A.constants[i]乘以当前维度的索引index,然后将结果加到相对地址off上。这样,off将累积每个维度索引对应的偏移量。
}
return 1;
}
void Assign(Array* A, int e, ...)
{
//A是n维数组,e为元素变量,随后是n个下标值
//若下标不越界,则将e的值赋给所指定的A的元素
va_list ap;
va_start(ap, e);
int off;
int result;
if ((result = Locate(A, ap, &off)) <= 0)
return result;
//*(A->base + off) = e;
if (A && A->base) {
A->base[off] = e;
}
else {
fprintf(stderr, "Error: Invalid array or base pointer.\n");
}
va_end(ap);
}
void Value(const Array* A, int* e, ...)
{
//A是n维数组,e为元素变量,随后是n个下标值
//若各下标不越界,则e赋值为所指定的A的元素值
va_list ap;
va_start(ap, e);
int result;
int off;
if ((result = Locate(A, ap, &off)) <= 0)
return result;
*e = *(A->base + off);
va_end(ap);
}
int main() {
Array myArray;
int testArray[3][4] = { {1, 2, 3, 4},{5, 6, 7, 8}, {9, 10, 11, 12} };
if (InitArray(&myArray, 2, 3, 4)) { // 初始化一个2维数组,维度大小为3x4
// 使用数组...
printf("A.dim = %d\n", myArray.dim);
printf("----------------------\n");
printf("各维的长度是:\n");
int index;
for (index = 0; index < myArray.dim; ++index)
{
printf("%d\t", myArray.bounds[index]);
}
printf("\n----------------------\n");
printf("数组的常量基址是:\n");
for (index = 0; index < myArray.dim; ++index)
{
printf("%d\t", myArray.constants[index]);
}
printf("\n");
for (int i = 0; i < 3; ++i)
{
for (int j = 0; j < 4; ++j)
{
Assign(&myArray, testArray[i][j], i, j); //对数组进行赋值
}
}
int value;
printf("----------------------\n");
printf("创建的数组中的元素为:\n");
int i;
for (i = 0; i < 3; ++i)
{
for (int j = 0; j < 4; ++j)
{
Value(&myArray, &value, i, j);
printf("%d\t", value);
}
printf("\n");
}
printf("----------------------\n");
printf("按照数组的线性序列输出元素,即利用基址地址输出元素:\n");
for (i = 0; i < 3 * 4; i++)
{
printf("第%d个元素=%3d\t", i + 1, myArray.base[i]);
if ((i + 1) % 4 == 0)
printf("\n");
}
FreeArray(&myArray); // 释放内存
}
else {
printf("Array initialization failed.\n");
}
return 0;
}
5.3 矩阵的压缩存储
5.3.1 特殊矩阵
-
对称矩阵
-
若n阶矩阵A中的元满足下述性质: a i j = a j i 1 ≤ i , j ≤ n a_{ij}=a_{ji}\qquad1 ≤ i,j ≤ n aij=aji1≤i,j≤n 则称为 n 阶对称矩阵。
-
对于对称矩阵, 可以为每一对对称元分配一个存储空间,则可将n2个元压缩存储到n(n+1)/2个元的空间中
-
压缩存储:假设以一维数组 sa[n(n+1)/2]作为n阶对称矩阵A的存储结构,则 sa[k]和矩阵元aij,之间存在着一一对应的关系:
k = { i ( i − 1 ) 2 + j − 1 当 i ≥ j j ( j − 1 ) 2 + i − 1 当 i < j k = 0 , 1 , 2 , . . . , n ( n + 1 ) 2 − 1 k =\begin{cases} \dfrac{i(i-1)}{2} + j - 1\qquad当i≥j \\ \dfrac{j(j-1)}{2} + i - 1\qquad当i<j \\ \end{cases}\\ k = 0,1,2,...,\dfrac{n(n+1)}{2} - 1 k=⎩ ⎨ ⎧2i(i−1)+j−1当i≥j2j(j−1)+i−1当i<jk=0,1,2,...,2n(n+1)−1
-
-
三角矩阵
-
上三角矩阵:主对角线下方全为0的方阵
-
下三角矩阵:主对角线上方全为0的方阵
-
-
对角矩阵:除了主对角线上和直接在对角线 上、下方若干条对角线上的元之外,所有其他的元皆为零。
5.3.2 稀疏矩阵
-
定义:假设在 m x n 的矩阵中,有t个元素不为零。令 δ = t m × n δ = \frac{t}{m × n} δ=m×nt,称δ为矩阵的稀疏因子。通常认为 δ≤0.05 时称为稀疏矩阵。
-
以常规方法,即以二维数组表示高阶的稀疏矩阵时产生的问题:
1)零值元素占的空间很大,
2)计算中进行了很多和零值的运算。
-
解决问题的原则:
1)尽可能少存或不存零值元素,2)尽可能减少没有实际意义的运算,
3)运算方便:
能尽可能快地找到与下标值(i,j)对应的元素;
能尽可能快地找到同一行或同一列的非零值元。
-
表示方法:
-
三元组顺序表 (Triplet Representation) - 有序的双下标法
#define MAXSIZE 12500 //假设非零元个数的最大值为12500 typedef struct { int i, j; //该非零元的行下标和列下标 int e;//该非零元的值 }Triple; typedef struct { Triple data[MAXSIZE + 1]; //非零元三元组表,data[0]未用 int mu, nu, tu; // 矩阵的行数,列数和非零元个数 }TSMatrix;
转置运算
🅰️方法一 - 普通转置:时间复杂度O(nu × tu), 即和M的列数及非零元的个数的乘积成正比。当非零元的个数 tu 和 mu × nu 同数量级时,时间复杂度就为 O(nu × (mu × nu)) = O(mu × nu2),虽然节省了存储空间,但时间复杂度提高了, 所以仅适用于 tu « mu × nu 的情况。
#define _CRT_SECURE_NO_WARNINGS 1 #include <stdio.h> #define MAXSIZE 12500 //假设非零元个数的最大值为12500 typedef struct { int i, j; //该非零元的行下标和列下标 int e;//该非零元的值 }Triple; typedef struct { Triple data[MAXSIZE + 1]; //非零元三元组表,data[0]未用 int mu, nu, tu; // 矩阵的行数,列数和非零元个数 }TSMatrix; void CreateSMatrix(TSMatrix* M) { int i, j; printf("请输入矩阵的行数,列数,非零元个数:\n"); scanf("%d%d%d", &M->mu, &M->nu, &M->tu); for (i = 1; i <= M->tu; i++) { printf("请按行序顺序输入第%d个非零元素所在的行,列,元素值:\n",i); scanf("%d%d%d", &M->data[i].i, &M->data[i].j, &M->data[i].e); } } void PrintSMatrix(TSMatrix M) { int i, j, k = 1;//k是非零元计数器,初值为1 Triple* p = M.data + 1;//p指向M的第一个非零元素 for (i = 1; i <= M.mu; i++)// 遍历矩阵的行 { for (j = 1; j <= M.nu; j++)// 遍历矩阵的列 { if (k <= M.tu && p->i == i && p->j == j) { printf("%3d ", p->e);// 如果当前元素是非零元素,则打印其值 p++; k++; } else { printf("%3d ", 0);// 如果当前元素是零元素,则打印0 } } printf("\n"); } } void TransposeSMatix(TSMatrix M, TSMatrix* T) { T->mu = M.nu;// 转置后矩阵的行数等于原矩阵的列数 T->nu = M.mu;// 转置后矩阵的列数等于原矩阵的行数 T->tu = M.tu; // 转置后矩阵的非零元素个数不变 if (T->tu) { int col, p; int q = 1; for (col = 1; col <= M.nu; ++col) { for (p = 1; p <= M.tu; ++p) // 遍历M第col的所有非零元素作为T的第col行非零元素 { if (M.data[p].j == col) { T->data[q].i = M.data[p].j;// 转置元素的行下标变为列下标 T->data[q].j = M.data[p].i;// 转置元素的列下标变为行下标 T->data[q].e = M.data[p].e;// 元素值不变 ++q; } } } } } int main() { TSMatrix M, T; CreateSMatrix(&M); printf("------TSMatrix M-----\n"); PrintSMatrix(M); TransposeSMatix(M, &T); printf("------TSMatrix T-----\n"); PrintSMatrix(T); return 0; }
🅱️方法二 - 快速转置:时间复杂度为 O(nu+tu), 当非零元的个数 tu 和 mu × nu 同数量级时,时间复杂度就为 O(mu × nu)
#define _CRT_SECURE_NO_WARNINGS 1 #include <stdio.h> #include <stdlib.h> #define MAXSIZE 12500 //假设非零元个数的最大值为12500 typedef struct { int i, j; //该非零元的行下标和列下标 int e;//该非零元的值 }Triple; typedef struct { Triple data[MAXSIZE + 1]; //非零元三元组表,data[0]未用 int mu, nu, tu; // 矩阵的行数,列数和非零元个数 }TSMatrix; void CreateSMatrix(TSMatrix* M) { int i, j; printf("请输入矩阵的行数,列数,非零元个数:\n"); scanf("%d%d%d", &M->mu, &M->nu, &M->tu); for (i = 1; i <= M->tu; i++) { printf("请按行序顺序输入第%d个非零元素所在的行,列,元素值:\n",i); scanf("%d%d%d", &M->data[i].i, &M->data[i].j, &M->data[i].e); } } void PrintSMatrix(TSMatrix M) { int i, j, k = 1;//k是非零元计数器,初值为1 Triple* p = M.data + 1;//p指向M的第一个非零元素 for (i = 1; i <= M.mu; i++)// 遍历矩阵的行 { for (j = 1; j <= M.nu; j++)// 遍历矩阵的列 { if (k <= M.tu && p->i == i && p->j == j) { printf("%3d ", p->e);// 如果当前元素是非零元素,则打印其值 p++; k++; } else { printf("%3d ", 0);// 如果当前元素是零元素,则打印0 } } printf("\n"); } } void FastTransposeSMatix(TSMatrix M, TSMatrix* T) { T->mu = M.nu;// 转置后矩阵的行数等于原矩阵的列数 T->nu = M.mu;// 转置后矩阵的列数等于原矩阵的行数 T->tu = M.tu; // 转置后矩阵的非零元素个数不变 if (T->tu) { int col, t, p, q; // 根据M.nu的长度动态分配num数组 int* num = (int*)malloc((M.nu + 1) * sizeof(int)); int* cpot = (int*)malloc((M.nu + 1) * sizeof(int)); if (num && cpot) { for (col = 1; col <= M.nu; ++col) { num[col] = 0; } // 求M中每一列含非零元个数 for (t = 1; t <= M.tu; ++t) { ++num[M.data[t].j]; } //求第col 列中第一个非零元在 b.data中的序号 cpot[1] = 1; for (col = 2; col <= M.nu; ++col) { cpot[col] = cpot[col-1] + num[col-1]; } //T矩阵三元组列表的获取 for (p = 1; p <= M.tu; ++p) { col = M.data[p].j; // M矩阵中三元组列表第p个位置的非零元素的列索引col q = cpot[col]; //查找col列对应T矩阵三元组列表的索引位置 T->data[q].i = M.data[p].j;// 转置元素的行下标变为列下标 T->data[q].j = M.data[p].i;// 转置元素的列下标变为行下标 T->data[q].e = M.data[p].e;// 元素值不变 ++cpot[col];//定位col列【非第一个非零元素】在T矩阵三元组列表的索引位置 } } free(num); free(cpot); } } int main() { TSMatrix M, T; CreateSMatrix(&M); printf("------TSMatrix M-----\n"); PrintSMatrix(M); FastTransposeSMatix(M, &T); printf("------TSMatrix T-----\n"); PrintSMatrix(T); return 0; }
特点:非零元在表中按行序有序存储,因此便于进行依行顺序处理的矩阵运算。然而,若需随机存取某一行中的非零元,则需从头开始进行查找。
-
行逻辑链接的顺序表
#define MAXSIZE 12500 //假设非零元个数的最大值为12500 #define MAXRC 500 // 假设矩阵最大行数为500 typedef struct { int i, j; //该非零元的行下标和列下标 int e;//该非零元的值 }Triple; typedef struct { Triple data[MAXSIZE + 1]; //非零元三元组表,data[0]未用 int rpos [MAXRC + 1]; //各行第一个非零元的位置表 int mu, nu, tu; // 矩阵的行数,列数和非零元个数 }RLSMatrix;
矩阵相乘
#define _CRT_SECURE_NO_WARNINGS 1 #include <stdio.h> #include <stdlib.h> #define MAXSIZE 200 //假设非零元个数的最大值为12500 #define MAXRC 100 // 假设矩阵最大行数为500 typedef struct { int i, j; //该非零元的行下标和列下标 int e;//该非零元的值 }Triple; typedef struct { Triple data[MAXSIZE + 1]; //非零元三元组表,data[0]未用 int rpos[MAXRC + 1]; //各行第一个非零元的位置表 int mu, nu, tu; // 矩阵的行数,列数和非零元个数 }RLSMatrix; void CreateRLSMatrix(RLSMatrix* M) { int i, row, t; printf("请输入矩阵的行数,列数,非零元个数:\n"); scanf("%d%d%d", &M->mu, &M->nu, &M->tu); for (i = 1; i <= M->tu; i++) { printf("请按行序顺序输入第%d个非零元素所在的行,列,元素值:\n", i); scanf("%d%d%d", &M->data[i].i, &M->data[i].j, &M->data[i].e); } //求出rpos的值 int num[MAXSIZE + 1]; if (M->tu) { for (row = 1; row <= M->mu; ++row) num[row] = 0; ///清零 for (t = 1; t <= M->tu; ++t) ++num[M->data[t].i]; ///求M中每一列含非零个数 M->rpos[1] = 1; /// 求第col列中第一个非零元在b.data中的序号 for (row = 2; row <= M->mu; ++row) M->rpos[row] = M->rpos[row - 1] + num[row - 1]; } } void PrintSMatrix(RLSMatrix M) { int i, j, k = 1;//k是非零元计数器,初值为1 Triple* p = M.data + 1;//p指向M的第一个非零元素 for (i = 1; i <= M.mu; i++)// 遍历矩阵的行 { for (j = 1; j <= M.nu; j++)// 遍历矩阵的列 { if (k <= M.tu && p->i == i && p->j == j) { printf("%3d ", p->e);// 如果当前元素是非零元素,则打印其值 p++; k++; } else { printf("%3d ", 0);// 如果当前元素是零元素,则打印0 } } printf("\n"); } printf("rpos: "); for (i = 1; i <= M.mu; i++) { printf("%3d ", M.rpos[i]); } printf("\n"); } int MultSMatrix(RLSMatrix M, RLSMatrix N, RLSMatrix* Q) { if (M.nu != N.mu) return -1; Q->mu = M.mu; Q->nu = N.nu; Q->tu = 0; if (M.tu * N.tu != 0) // 如果矩阵 M 和 N 至少有一个非零元素,那么结果矩阵 Q 将是一个非零矩阵。 { for (int arow = 1; arow <= M.mu; ++arow) { // 处理M的每一行 int ctemp[500] = { 0 }; //当前行各元素累加器清零[ ctemp,用于累加矩阵 Q 当前行的元素值] Q->rpos[arow] = Q->tu + 1; //tp是为了可以遍历 M 在arow行的所有非零元素 int tp = 0; //确定矩阵 M 中当前正在处理的行(arow)的非零元素的结束位置( 也就是下一行第一个非零元素的位置——遍历的时候是开区间取不到) if (arow < M.mu) { tp = M.rpos[arow + 1];} else { tp = M.tu + 1; } // M最后一行的非零元素的结束位置 = M所有非零元素个数 + 1 for (int p = M.rpos[arow]; p < tp; ++p) //循环遍历矩阵 M 当前行的所有非零元素 { int brow = M.data[p].j; //获取当前非零元素的列索引。 //t是为了可以遍历 N 在brow行的所有非零元素 int t = 0; //确定矩阵 N 中当前正在处理的行(brow)的非零元素的结束位置( 也就是下一行第一个非零元素的位置——遍历的时候是开区间取不到) if (brow < N.mu) t = N.rpos[brow + 1]; else { t = N.tu + 1; } for (int q = N.rpos[brow]; q < t; ++q) { //遍历矩阵 N 中对应列的所有非零元素。 int ccol = N.data[q].j; //获取矩阵 N 当前非零元素的目标列索引。 ctemp[ccol] += M.data[p].e * N.data[q].e; //计算两个非零元素的乘积,并累加到 ctemp 数组的相应位置。 } } for (int ccol = 1; ccol <= Q->nu; ++ccol) //遍历ctemp, 范围是[1,Q.nu] 给Q.data赋值 if (ctemp[ccol]) { if (++Q->tu > MAXSIZE) return -1; Q->data[Q->tu].i = arow; //arow是M的当前行,也是Q的当前行 Q->data[Q->tu].j = ccol; //ccol是ctemp的索引,同时是N的当前列,也是Q的当前列 Q->data[Q->tu].e = ctemp[ccol]; //ctemp[ccol]是Q的当前列ccol的数值 } } } return 1; } int main() { RLSMatrix M, N, Q; CreateRLSMatrix(&M); printf("------RLSMatrix M-----\n"); PrintSMatrix(M); CreateRLSMatrix(&N); printf("------RLSMatrix N-----\n"); PrintSMatrix(N); int status = MultSMatrix(M, N, &Q); if (status) { printf("------RLSMatrix Q-----\n"); PrintSMatrix(Q); } return 0; }
👉: 算法的时间复杂度:
累加器 ctemp 初始化的时间复杂度为O(M.mu × N.nu), 求Q的所有非零元的时间复杂度为O(M.tu × N.tu / N.mu),进行压缩存储的时间复杂度为 O(M.mu × N.nu),因此,总的时间复杂度就是 O(M.mu × N.nu + M. tu × N. tu / N.mu)。
解释:
- 对于矩阵
M
中的每一行(非零元素), 都需要遍历矩阵N
。由于M
中有M.tu
个非零元素,可以认为需要遍历M.tu
次。 - 在每次遍历
M
的一行时,可能需要检查N
中的多个行。理想情况下,如果N
的每一行都均匀分布了非零元素,那么平均每次遍历M
的一行时,需要检查N.tu / N.mu
个非零元素。这里N.tu / N.mu
表示平均每行的非零元素数量。 - 因此,总的时间复杂度可以近似为
M.tu × (N.tu / N.mu)
,即M
中非零元素的数量乘以平均每次需要处理的N
中的非零元素数量。
- 对于矩阵
-
十字链表法
用链式存储结构 表示三元组的线性表:
在链表中,每个非零元可用一个含5个域的结点表示,其中 i、j和e这3个域分别表示 该非零元所在的行、列和非零元的值,向右域 right 用以链接同一行中下一个非零元,向 下域 down 用以链接同一列中下一个非零元。同一行的非零元通过 right 域链接成一个 线性链表,同–列的非零元通过 down 域链接成一个线性链表,每个非零元既是某个行链表中的一个结点,又是某个列链表中的一个结点,整个矩阵构成了一个十字交叉的链表。可用两个分别存储行链表的头指针和列链表的头指针的一维数组表示。
typedef struct OLNode { int i, j; int e; struct OLNode* right, *down; }OLNode, *OLink; typedef struct { OLink* rhead, * chead; //行和列链表头指针向量基址由 CreateSMatrix 分配 int mu, nu, tu; }CrossList;
#define _CRT_SECURE_NO_WARNINGS 1 #include <stdio.h> #include <stdlib.h> #define ERROR 0; #define OK 1; typedef struct OLNode { int i, j; int e; struct OLNode* right, *down; }OLNode, *OLink; typedef struct { OLink* rhead, * chead; //行和列链表头指针向量基址由 CreateSMatrix 分配 int mu, nu, tu; }CrossList; int CreateSMatrix_OL(CrossList* M) { int m, n, t, k; int i, j, e; OLNode* p, *q; printf("请输入矩阵的行列数和非零元个数:\n"); 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)))) { printf("分配内存失败\n"); return ERROR; } if (!(M->chead = (OLink*)malloc((n + 1) * sizeof(OLink)))) { printf("分配内存失败\n"); return ERROR; } //初始化行列头指针向量;各行列链表为空链表 for (k = 0; k < m + 1; k++) M->rhead[k] = NULL; for (k = 0; k < n + 1; k++) M->chead[k] = NULL; //按任意次序输入非零元 for (k = 0; k < t; k++) { printf("请输入非零元素所在行列与值\n"); scanf("%d %d %d", &i, &j, &e); if (!(p = (OLNode*)malloc(sizeof(OLNode)))) { printf("分配内存失败\n"); return ERROR; } p->i = i; p->j = j; p->e = e; //生成结点 //进行行插入 if (M->rhead[i] == NULL || M->rhead[i]->j > p->j) { //第i行没有结点 或者 第i行的第一个结点的列数 大于 新插入结点的列数 //将这一结点插入在该列的第一个结点处 p->right = M->rhead[i]; M->rhead[i] = p; } else { //找到p合适的位置并插入,q是为了遍历 M->rhead[i] // 遍历条件:q->right 表示存在下一个结点 ;q->right->j < p->j 表示下一个结点的j列数 小于p结点的列数 for (q = M->rhead[i]; (q->right) && q->right->j < p->j; q = q->right); p->right = q->right; q->right = p; } //进行列插入 if (M->chead[j] == NULL || M->chead[j]->i > p->i) { //第j列没有结点 或者 第j列的第一个结点的行数 大于 新插入结点的行数 //将这一结点插入在该行的第一个结点处 p->down = M->chead[j]; M->chead[j] = p; } else { //找到p合适的位置并插入,q是为了遍历 M->chead[j] // 遍历条件:q->down 表示存在下一个结点 ;q->down->i < p->i 表示下一个结点的i行数 小于p结点的i行数 for (q = M->chead[j]; q->down && q->down->i < p->i; q = q->down); p->down = q->down; q->down = p; } } } void PrintSMartix(CrossList M) { int i, j; OLNode* q; printf("开始遍历稀疏矩阵\n"); if (!M.chead && !M.rhead) { printf("稀疏矩阵为空\n"); return ERROR; } for (i = 1; i <= M.mu; i++) { q = M.rhead[i]; for (j = 1; j <= M.nu; j++) { //如果此结点不为空且此结点的列数是按顺序的,打印此非零元 if (q && q->j == j) { printf("%3d", q->e); q = q->right; } else printf(" 0"); } printf("\n"); } } //销毁 void DestroySMatrix(CrossList* M) { int i; OLNode* p, * q; for (i = 1; i <= M->mu; i++) { p = M->rhead[i]; while (p) { q = p; p = p->right; free(q); } } M->mu = M->nu = M->tu = 0; M->chead = M->rhead = NULL; } int main() { CrossList M; if (CreateSMatrix_OL(&M)) { PrintSMartix(M); DestroySMatrix(&M); } return 0; }
矩阵相加
相加后结果A’只可能有3种情况:aij + bij 或 aij ( bij = 0 ) 或 bij( aij = 0 )
思路:
-
针对矩阵 B 中每一个非零元素,需判断提取到的 B 中的三元组在 A 相应位置上的情况:
- 没有非零元素,此时直接加到矩阵 A 该行链表的对应位置上
- 有非零元素,且相加不为 0 ,此时只需更改 A 中对应位置上的三元组的值
- 有非零元素,但相加为 0 ,此时需删除矩阵 A 中对应结点
-
设指针 pa 和 pb 分别表示矩阵 A 和矩阵 B 中同一行中的结点( pb 和 pa 都是从两矩阵的第一行的第一个非零元素开始遍历)
①当
pa == NULL
时,表明当前行(列)没有非零元素,此时可直接将 pb 结点添加到当前行(列)中②当
pa 的列值j < pb 的列值j
时,表明 pb 结点的位置相对靠后,此时需从 pa 结点处向后查找,找到第一个比 pb 结点列值 j 大的结点,将 pb 插入到该结点前面③当
pa 的列值j > pb 的列值j
时,和上一种情况类似,将 pb 结点插入到 pa 结点的前面④当
pa 的列值j == pb 的列值j
、且两结点的值相加结果不为 0 ,只需更改 pa 指向的结点的值即可⑤当
pa 的列值j == pb 的列值j
、但两结点的值相加结果为 0 ,就需要从矩阵 A 的十字链表中删除 pa 指向的结点
#define _CRT_SECURE_NO_WARNINGS 1 #include <stdio.h> #include <stdlib.h> #define ERROR 0; #define OK 1; typedef struct OLNode { int i, j; int e; struct OLNode* right, *down; }OLNode, *OLink; typedef struct { OLink* rhead, * chead; //行和列链表头指针向量基址由 CreateSMatrix 分配 int mu, nu, tu; }CrossList; int CreateSMatrix_OL(CrossList* M) { int m, n, t, k; int i, j, e; OLNode* p, *q; printf("请输入矩阵的行列数和非零元个数:\n"); 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)))) { printf("分配内存失败\n"); return ERROR; } if (!(M->chead = (OLink*)malloc((n + 1) * sizeof(OLink)))) { printf("分配内存失败\n"); return ERROR; } //初始化行列头指针向量;各行列链表为空链表 for (k = 0; k < m + 1; k++) M->rhead[k] = NULL; for (k = 0; k < n + 1; k++) M->chead[k] = NULL; //按任意次序输入非零元 for (k = 0; k < t; k++) { printf("请输入非零元素所在行列与值\n"); scanf("%d %d %d", &i, &j, &e); if (!(p = (OLNode*)malloc(sizeof(OLNode)))) { printf("分配内存失败\n"); return ERROR; } p->i = i; p->j = j; p->e = e; //生成结点 //进行行插入 if (M->rhead[i] == NULL || M->rhead[i]->j > p->j) { //第i行没有结点 或者 第i行的第一个结点的列数 大于 新插入结点的列数 //将这一结点插入在该列的第一个结点处 p->right = M->rhead[i]; M->rhead[i] = p; } else { //找到p合适的位置并插入,q是为了遍历 M->rhead[i] // 遍历条件:q->right 表示存在下一个结点 ;q->right->j < p->j 表示下一个结点的j列数 小于p结点的列数 for (q = M->rhead[i]; (q->right) && q->right->j < p->j; q = q->right); p->right = q->right; q->right = p; } //进行列插入 if (M->chead[j] == NULL || M->chead[j]->i > p->i) { //第j列没有结点 或者 第j列的第一个结点的行数 大于 新插入结点的行数 //将这一结点插入在该行的第一个结点处 p->down = M->chead[j]; M->chead[j] = p; } else { //找到p合适的位置并插入,q是为了遍历 M->chead[j] // 遍历条件:q->down 表示存在下一个结点 ;q->down->i < p->i 表示下一个结点的i行数 小于p结点的i行数 for (q = M->chead[j]; q->down && q->down->i < p->i; q = q->down); p->down = q->down; q->down = p; } } } void PrintSMartix(CrossList M) { int i, j; OLNode* q; if (!M.chead && !M.rhead) { printf("稀疏矩阵为空\n"); return ERROR; } for (i = 1; i <= M.mu; i++) { q = M.rhead[i]; for (j = 1; j <= M.nu; j++) { //如果此结点不为空且此结点的列数是按顺序的,打印此非零元 if (q && q->j == j) { printf("%3d", q->e); q = q->right; } else printf(" 0"); } printf("\n"); } } //销毁 void DestroySMatrix(CrossList* M) { int i; OLNode* p, * q; for (i = 1; i <= M->mu; i++) { p = M->rhead[i]; while (p) { q = p; p = p->right; free(q); } } M->mu = M->nu = M->tu = 0; M->chead = M->rhead = NULL; } void insertToMatrix(CrossList* M, OLink pn) { //从行的角度将 p 结点链接到 M 矩阵的适当位置 //① M->rhead[p->i]==NULL if (M->rhead[pn->i] == NULL) { M->rhead[pn->i] = pn; M->tu++; } else { OLink pm = M->rhead[pn->i]; OLink pre = NULL; //指示pm所指结点的前驱结点; // ② M->rhead[p->i] != NULL , M->rhead[p->i]->j < p->j 【pm 的列值j < pn 的列值j】 // M->rhead[p->i]中 如果pn【N】的j一直大于pm【M】的j ,pm需要一直向右移动至 pm->j ≥ pn->j的位置 while (pm && (pm->j < pn->j)) // 用两个指针找到该元素块应该插到那个位置 { pre = pm; pm = pm->right; } //插入到最后一个 if (pm == NULL) { pre->right = pn; M->tu++; } // ④+⑤ ,M的列值j == N的列值j if (pm != NULL && pm->j == pn->j) { pm->e += pn->e; //④两结点的值相加结果不为 0, 更改 pm 指向的结点的值 if (pm->e != 0) { //已经更改完 pm 指向的结点的值 } //⑤两结点的值相加结果为 0, 从矩阵M的十字链表中删除 pm 指向的结点 else { if (pre == NULL) //第一个结点的j = pn->j,第一个结点前驱pre是NULL { M->rhead[pn->i] = pm->right; } else { pre->right = pm->right; } M->tu--; } } // ③ M的列值j > N的列值j if (pm != NULL && pm->j > pn->j) { if (pre == NULL) //第一个结点的j > pn->j,第一个结点前驱pre是NULL { M->rhead[pn->i] = pn; pn->right = pm; } else { pre->right = pn; pn->right = pm; } M->tu++; } } //从列的角度将 p 结点链接到 M 矩阵的适当位置 if (M->chead[pn->j] == NULL) { M->chead[pn->j] = pn; } else { OLink pm = M->chead[pn->j]; OLink pre = NULL; // 解决第 ② 种情况 while (pm && (pm->i < pn->i)) { pre = pm; pm = pm->down; } if (pm == NULL) { pre->down = pn; } if (pm != NULL && pm->i == pn->i) { //④两结点的值已经相加了放到pm->e if (pm->e != 0) { //已经更改完 pm 指向的结点的值 } //⑤两结点的值相加结果为 0, 从矩阵M的十字链表中删除 pm 指向的结点 else { if (pre == NULL) //第一个结点的j = pn->j,第一个结点前驱pre是NULL { M->chead[pn->j] = pm->down; } else { pre->down = pm->down; } } } if (pm != NULL && pm->i > pn->i) { if (pre == NULL) //第一个结点的i > pn->i,第一个结点前驱pre是NULL { M->chead[pn->j] = pn; pn->down = pm; } else { pre->down = pn; pn->down = pm; } } } } void AddSMatrix(CrossList* M, CrossList N) { OLink p, q; int k; // 遍历 N 矩阵中的每一行 for (k = 1; k <= N.mu; k++) { //指向k行的第一个非零元素的结点 p = N.rhead[k]; while (p) { q = (OLink)malloc(sizeof(OLNode)); q->i = p->i; q->j = p->j; q->e = p->e; q->down = NULL; q->right = NULL; // 将 q 结点添加到 M 矩阵中 insertToMatrix(M, q); //指向第k行的下一个非零元素结点 p = p->right; } } } int main() { CrossList A, B; CreateSMatrix_OL(&A); printf("-------- A --------\n"); PrintSMartix(A); CreateSMatrix_OL(&B); printf("-------- B --------\n"); PrintSMartix(B); AddSMatrix(&A, B); printf("------ A + B ------\n"); PrintSMartix(A); return 0; }
-
-
5.4 广义表的定义
- 广义表是递归定义的线性结构,一般记作
L S = ( α 1 , α 2 , . . . , α n ) LS = (α_1,α_2,...,α_n) LS=(α1,α2,...,αn)
LS是广义表的名称,n是他的长度
特点:
1)广义表中的数据元素有相对次序;
2)广义表的长度定义为最外层包含元素个数
3)广义表的深度定义为所含括弧的重数; "原子"的深度为“0”; "空表"的深度为1
4)广义表可以共享:
5)广义表可以是一个递归的表:递归表的深度是无穷值,长度是有限值。
-
在线性表的定义中, ai (1≤i≤n) 只限于是单个元素。而在广义表的定义中, αi可以是单个元素,也可以是广义表,分别称为广义表 LS 的原子和子表。用大写字母表示广义表的名称,用小写字母表示原子
-
当广义表 LS 非空时,称第一个元素 α1为 LS 的表头 (Head), 称其余元素组成的表 (α2,α3,…,αn)是 LS 的表尾 (Tail):任何一个非空列表其表头可能是原子,也可能是列表,而其表尾必定为列表。
-
A=() —— A是一个空表,它的长度为零。
B=(e) ——列表B只有一个原子 e,B 的长度为1 GetHead(B) =e, GetTail(B) = ()
C=(a,(b,c,d))—— 列表C的长度为 2, 两个元素分别为原子a和子表 (b,c, d)
D=(A,B,C) ——列表D的长度为 3, 3 个元素都是列表。显然, 将子表的值代入后,则有 D=((),(e),(a,(b,c,d)) GetHead(D) =A, GetTail(D) = CB,C)
E=(a,E)——这是一个递归的表,它的长度为2,E 相当于一个无限的列表 E=(a,(a,(a, …)))。
列表( )和(( ))不同。前者为空表,长度 n=0; 后者长度 n=1, 可分解得到其表头、表尾均为空表()。
-
ADT
5.5 广义表的存储结构
通常采用链式存储结构,每个数据元素可用一个结点表示。
1️⃣第一种结构:一种是表结点,用以表示列表;一种是原子结点,用以表示原子。
一 个表结点可由3个域组成:标志域、指示表头的指针域 和指示表尾的指针域;而原子结点只需两个域:标志域和值域
typedef enum {ATOM, LIST} ElemTag; // ATOM == 0 :原子;LIST == 1 : 子表
typedef int Atomtype;
typedef struct GLNode {
ElemTag tag; // 公共部分,用于区分原子结点和表结点
union // 原子结点和表结点的联合部分
{
Atomtype atom; // atom 是原子结点的值域, AtomType 由用户定义
struct { struct GLNode* hp, * tp; }ptr; // ptr 是表结点的指针域, ptr.hp和ptr.tp 分别指向表头和表尾
};
} *GList; // 广义表类型
2️⃣第二种结构:
typedef enum {ATOM, LIST} ElemTag; // ATOM == 0 :原子;LIST == 1 : 子表
typedef int Atomtype;
typedef struct GLNode {
ElemTag tag; // 公共部分,用于区分原子结点和表结点
union // /原子结点和表结点的联合部分
{
Atomtype atom; // atom 是原子结点的值域, AtomType 由用户定义
struct GLNode* hp; // 表结点的表头指针
};
struct GLNode* tp; //相当于线性链表的 next, 指向下一个元素结点
} *GList;
5.6 m元多项式的表示
任何一个m元多项式都可如此做:先分解出一个主变元,随后再分解出第二个变元,等等。
用广义表来表示m元多项式,广义表的深度即为变元个数。
typedef enum {ATOM, LIST} ElemTag; // ATOM == 0 :原子;LIST == 1 : 子表
typedef int Atomtype;
typedef struct MPNode {
ElemTag tag; // 公共部分,用于区分原子结点和表结点
int exp; //指数区域
union
{
float coef; //系数域
struct MPNode* hp;// 表结点的头指针
};
struct MPNode* tp; //相当于线性链表的 next, 指向下一个元素结点
} *MPList; // m元多项式广义表类型
5.7 广义表的递归算法
递归定义由基本项和归纳项两部分组成。
基本项:描述了一个或几个递归过程的终结状态。所谓终结状态指的是不需要继续递归而可直接求解的状态
归纳项:描述了如何实现从当前状态到终结状态的转化
5.7.1 求广义表的深度
-
广义表的深度定义为广义表中括弧的重数
-
设非空广义表为:
L S = ( α 1 , α 2 , . . . , α n ) 其中 α i ( i = 1 , 2 , . . . , n ) LS = (α_1,α_2,...,α_n)\\ 其中\quad α_i(i = 1,2,...,n) LS=(α1,α2,...,αn)其中αi(i=1,2,...,n)
- 若αi是原子,则由定义其深度为零;若αi是广义表, LS 的深度为各 αi(i = 1,2,…,n)的深度中最大值加1。空表也是广义表,并由 定义可知空表的深度为1——由此可见,求广义表的深度的递归算法有两个终结状态:空表和原子
-
递归定义:
基本项:
DEPTH (LS) = 1 当LS 为空表时
DEPTH(LS) = 0 当LS 为原子时
归纳项:
D E P T H ( L S ) = 1 + M a x { D E P T H ( α i ) } 1 ≤ i ≤ n , n ≥ 1 DEPTH(LS) = 1 + Max\{DEPTH(\alpha_i)\}\qquad1≤i≤n,n≥1 DEPTH(LS)=1+Max{DEPTH(αi)}1≤i≤n,n≥1
5.7.2 复制广义表
复制一个广义表只要分别复制其表头和表尾, 然后合成即可
-
基本项:InitGList(NEWLS) {置空表},当LS为空表时。
-
归纳项:COPY(GetHead(LS) -> GetHead(NEWLS)) {复制表头}
COPY(GetTail(LS) -> GetTail(NEWLS)) {复制表尾}
5.7.3 建立广义表的存储结构
假设把广义表的书写形式看成是一个字符串 S,广义表字符串 可能有两种情况:
(1) S=‘()’( 带括弧的空白串);
(2) S= (α1,α2,…,αn) 其中 αi (i= 1,2, …,n) 是S的子串
αi 可能有3种情况: (1) 带括弧的空白串; (2) 长度为1的单字符串; (3) 长度 >1 的字符串。
-
基本项:置空广义表 当S为空表串时
建原子结点的子表 当S为单字符串时
-
归纳项::假设 sub 为脱去S中最外层括弧的子串,记为 ‘s1,s2,…,sn’ , 其中 si(i= 1,2, …, n) 为非空字符串。对每一个 si, 建立一个表结点,并令其 hp 域的指针为由si,建立的子表的头指针,除最后建立的表结点的尾指针为 NULL 外,其余表结点 的尾指针均指向在它之后建立的表结点。
/* HString.h */
#pragma once //保证头文件只被编译一次
typedef int Status; /* Status是函数的类型,其值是函数结果状态代码,如OK等 */
/* 串的堆分配存储 */
typedef struct
{
char* ch; /* 若是非空串,则按串长分配存储区,否则ch为NULL */
int length; /* 串长度 */
}HString;
void InitString(HString* T);
Status StrAssign(HString* T, char* chars);
Status StrCopy(HString* T, HString S);
Status StrEmpty(HString S);
int StrCompare(HString S, HString T);
int StrLength(HString S);
Status ClearString(HString* S);
Status Concat(HString* T, HString S1, HString S2);
Status SubString(HString* Sub, HString S, int pos, int len);
int Index(HString S, HString T, int pos);
Status StrInsert(HString* S, int pos, HString T);
Status StrDelete(HString* S, int pos, int len);
Status Replace(HString* S, HString T, HString V);
void DestroyString();
void StrPrint(HString T);
/* HString.c */
#define _CRT_SECURE_NO_WARNINGS 1
#include "HString.h"
#include<string.h>
#include<ctype.h>
#include<malloc.h> /* malloc()等 */
#include<limits.h> /* INT_MAX等 */
#include<stdio.h> /* EOF(=^Z或F6),NULL */
#include<stdlib.h> /* atoi() */
#include<io.h> /* eof() */
#include<math.h> /* floor(),ceil(),abs() */
#include<process.h> /* exit() */
/* 函数结果状态代码 */
#define TRUE 1
#define FALSE 0
#define OK 1
#define ERROR 0
#define INFEASIBLE -1
#define OVERFLOW -2
void InitString(HString* T)
{ /* 初始化(产生空串)字符串T。另加 */
(*T).length = 0;
(*T).ch = NULL;
}
Status StrAssign(HString* T, char* chars)
{ /* 生成一个其值等于串常量chars的串T */
int i, j;
if ((*T).ch)
free((*T).ch); /* 释放T原有空间 */
i = strlen(chars); /* 求chars的长度i */
if (!i)
{ /* chars的长度为0 */
(*T).ch = NULL;
(*T).length = 0;
}
else
{ /* chars的长度不为0 */
(*T).ch = (char*)malloc(i * sizeof(char)); /* 分配串空间 */
if (!(*T).ch) /* 分配串空间失败 */
exit(OVERFLOW);
for (j = 0; j < i; j++) /* 拷贝串 */
(*T).ch[j] = chars[j];
(*T).length = i;
}
return OK;
}
Status StrCopy(HString* T, HString S)
{ /* 初始条件:串S存在。操作结果: 由串S复制得串T */
int i;
if ((*T).ch)
free((*T).ch); /* 释放T原有空间 */
(*T).ch = (char*)malloc(S.length * sizeof(char)); /* 分配串空间 */
if (!(*T).ch) /* 分配串空间失败 */
exit(OVERFLOW);
for (i = 0; i < S.length; i++) /* 拷贝串 */
(*T).ch[i] = S.ch[i];
(*T).length = S.length;
return OK;
}
Status StrEmpty(HString S)
{ /* 初始条件: 串S存在。操作结果: 若S为空串,则返回TRUE,否则返回FALSE */
if (S.length == 0 && S.ch == NULL)
return TRUE;
else
return FALSE;
}
int StrCompare(HString S, HString T)
{ /* 若S>T,则返回值>0;若S=T,则返回值=0;若S<T,则返回值<0 */
int i;
for (i = 0; i < S.length && i < T.length; ++i)
if (S.ch[i] != T.ch[i])
return S.ch[i] - T.ch[i];
return S.length - T.length;
}
int StrLength(HString S)
{ /* 返回S的元素个数,称为串的长度 */
return S.length;
}
Status ClearString(HString* S)
{ /* 将S清为空串 */
if ((*S).ch)
{
free((*S).ch);
(*S).ch = NULL;
}
(*S).length = 0;
return OK;
}
Status Concat(HString* T, HString S1, HString S2)
{ /* 用T返回由S1和S2联接而成的新串 */
int i;
if ((*T).ch)
free((*T).ch); /* 释放旧空间 */
(*T).length = S1.length + S2.length;
(*T).ch = (char*)malloc((*T).length * sizeof(char));
if (!(*T).ch)
exit(OVERFLOW);
for (i = 0; i < S1.length; i++)
(*T).ch[i] = S1.ch[i];
for (i = 0; i < S2.length; i++)
(*T).ch[S1.length + i] = S2.ch[i];
return OK;
}
Status SubString(HString* Sub, HString S, int pos, int len)
{ /* 用Sub返回串S的第pos个字符起长度为len的子串。 */
/* 其中,1≤pos≤StrLength(S)且0≤len≤StrLength(S)-pos+1 */
int i;
if (pos<1 || pos>S.length || len<0 || len>S.length - pos + 1)
return ERROR;
if ((*Sub).ch)
free((*Sub).ch); /* 释放旧空间 */
if (!len) /* 空子串 */
{
(*Sub).ch = NULL;
(*Sub).length = 0;
}
else
{ /* 完整子串 */
(*Sub).ch = (char*)malloc(len * sizeof(char));
if (!(*Sub).ch)
exit(OVERFLOW);
for (i = 0; i <= len - 1; i++)
(*Sub).ch[i] = S.ch[pos - 1 + i];
(*Sub).length = len;
}
return OK;
}
int Index(HString S, HString T, int pos) /* 算法4.1 */
{ /* T为非空串。若主串S中第pos个字符之后存在与T相等的子串, */
/* 则返回第一个这样的子串在S中的位置,否则返回0 */
int n, m, i;
HString sub;
InitString(&sub);
if (pos > 0)
{
n = StrLength(S);
m = StrLength(T);
i = pos;
while (i <= n - m + 1)
{
SubString(&sub, S, i, m);
if (StrCompare(sub, T) != 0)
++i;
else
return i;
}
}
return 0;
}
Status StrInsert(HString* S, int pos, HString T) /* 算法4.4 */
{ /* 1≤pos≤StrLength(S)+1。在串S的第pos个字符之前插入串T */
int i;
if (pos<1 || pos>(*S).length + 1) /* pos不合法 */
return ERROR;
if (T.length) /* T非空,则重新分配空间,插入T */
{
(*S).ch = (char*)realloc((*S).ch, ((*S).length + T.length) * sizeof(char));
if (!(*S).ch)
exit(OVERFLOW);
for (i = (*S).length - 1; i >= pos - 1; --i) /* 为插入T而腾出位置 */
(*S).ch[i + T.length] = (*S).ch[i];
for (i = 0; i < T.length; i++)
(*S).ch[pos - 1 + i] = T.ch[i]; /* 插入T */
(*S).length += T.length;
}
return OK;
}
Status StrDelete(HString* S, int pos, int len)
{ /* 从串S中删除第pos个字符起长度为len的子串 */
int i;
if ((*S).length < pos + len - 1)
exit(ERROR);
for (i = pos - 1; i <= (*S).length - len; i++)
(*S).ch[i] = (*S).ch[i + len];
(*S).length -= len;
(*S).ch = (char*)realloc((*S).ch, (*S).length * sizeof(char));
return OK;
}
Status Replace(HString* S, HString T, HString V)
{ /* 初始条件: 串S,T和V存在,T是非空串(此函数与串的存储结构无关) */
/* 操作结果: 用V替换主串S中出现的所有与T相等的不重叠的子串 */
int i = 1; /* 从串S的第一个字符起查找串T */
if (StrEmpty(T)) /* T是空串 */
return ERROR;
do
{
i = Index(*S, T, i); /* 结果i为从上一个i之后找到的子串T的位置 */
if (i) /* 串S中存在串T */
{
StrDelete(S, i, StrLength(T)); /* 删除该串T */
StrInsert(S, i, V); /* 在原串T的位置插入串V */
i += StrLength(V); /* 在插入的串V后面继续查找串T */
}
} while (i);
return OK;
}
void DestroyString()
{ /* 堆分配类型的字符串无法销毁 */
}
void StrPrint(HString T)
{ /* 输出T字符串。另加 */
int i;
for (i = 0; i < T.length; i++)
printf("%c", T.ch[i]);
printf("\n");
}
1️⃣结构一:
/* test.c */
#define _CRT_SECURE_NO_WARNINGS 1
#include "HString.h"
#include "LS1.h"
#include <stdio.h>
#include <string.h>
int main()
{
GList1 LS1;
InitGList1(&LS1);
printf("----------------空广义表LS1----------------\n");
printf("空广义表LS1的长度=%d \n", GListLength1(LS1));
printf("空广义表LS1的深度=%d,是否空(1:是 0:否): %d\n", GListDepth1(LS1), GListEmpty1(LS1));
printf("---------------一般广义表LS1---------------\n");
char p[80];
GList1 NEWLS1;
HString str;
InitGList1(&NEWLS1);
InitString(&str);
printf("请输入广义表LS1(书写形式:空表:(),单原子:a,其它:(a,(b),b)):\n");
/*
gets(p); 是一个函数调用,它的作用是从标准输入(通常是键盘)读取一行文本,直到遇到换行符 \n 或者输入结束(EOF),
并将这些字符存储到由 p 指向的字符数组中。gets 函数不包括换行符在内,并且不会检查数组边界,
这意味着如果输入的字符数超过了数组的大小,就可能发生缓冲区溢出,这是一个严重的安全问题。
由于 gets 函数存在安全风险,它已经在C11标准中被移除,建议使用更安全的函数,如 fgets,
它允许指定接收的最大字符数,包括换行符,并且不会溢出缓冲区。
*/
fgets(p, sizeof(p), stdin); // 读取最多79个字符加上一个结束符'\0'; stdin 表示标准输入
p[strlen(p) - 1] = '\0';
//gets(p);
StrAssign(&str, p);
CreateGList1(&LS1, str);
printf("广义表LS1的长度=%d\n", GListLength1(LS1));
printf("广义表LS1的深度=%d LS1是否空(1:是 0:否)?%d \n", GListDepth1(LS1), GListEmpty1(LS1));
printf("遍历广义表LS1:\n");
Traverse_GL1(LS1, visit1);
printf("\n复制广义表NEWLS1=LS1\n");
CopyGList1(&NEWLS1, LS1);
printf("广义表m的长度=%d\n", GListLength1(NEWLS1));
printf("广义表m的深度=%d\n", GListDepth1(NEWLS1));
printf("遍历广义表NEWLS1:\n");
Traverse_GL1(NEWLS1, visit1);
return 0;
}
/* LS1.h */
#pragma once
#include "HString.h"
typedef char AtomType; /* 定义原子类型为字符型 */
/* 广义表的扩展线性链表存储表示 */
typedef enum { ATOM, LIST } ElemTag; /* ATOM==0:原子,LIST==1:子表 */
typedef struct GLNode
{
ElemTag tag; /* 公共部分,用于区分原子结点和表结点 */
union /* 原子结点和表结点的联合部分 */
{
AtomType atom; /* 原子结点的值域 */
struct { struct GLNode* hp, * tp; }ptr; /* ptr 是表结点的指针域, ptr.hp和ptr.tp 分别指向表头和表尾 */
};
} *GList1, GLNode1;
Status InitGList1(GList1* L);
Status GListEmpty1(GList1 L);
int GListLength1(GList1 L);
int GListDepth1(GList1 L);
Status sever1(HString* str, HString* hstr);
Status CreateGList1(GList1* L, HString S);
void Traverse_GL1(GList1 L, void(*v)(AtomType));
void visit1(AtomType e);
Status CopyGList1(GList1* T, GList1 L);
/* LS1.c */
#define _CRT_SECURE_NO_WARNINGS 1
#include "LS1.h"
#include "HString.h"
#include<string.h>
#include<ctype.h>
#include<malloc.h> /* malloc()等 */
#include<limits.h> /* INT_MAX等 */
#include<stdio.h> /* EOF(=^Z或F6),NULL */
#include<stdlib.h> /* atoi() */
#include<io.h> /* eof() */
#include<math.h> /* floor(),ceil(),abs() */
#include<process.h> /* exit() */
/* 函数结果状态代码 */
#define TRUE 1
#define FALSE 0
#define OK 1
#define ERROR 0
#define INFEASIBLE -1
#define OVERFLOW -2
Status InitGList1(GList1* L)
{ /* 创建空的广义表L */
*L = NULL;
return OK;
}
Status GListEmpty1(GList1 L)
{ /* 初始条件: 广义表L存在。操作结果: 判定广义表L是否为空 */
if (!L || L->tag == LIST && !L->ptr.tp)
return OK;
else
return ERROR;
}
int GListDepth1(GList1 L)
{ /* 初始条件: 广义表L存在。操作结果: 求广义表L的深度 */
int max, dep;
GList1 pp;
if (!L) return 1; /* 空表深度为1 */
if (L->tag == ATOM) return 0; /* 单原子表深度为0 */
/* 求一般表的深度 */
for (max = 0, pp = L; pp; pp = pp->ptr.tp)
{
dep = GListDepth1(pp->ptr.hp); /* 求以pp为头指针的子表深度 */
if (dep > max)
max = dep;
}
return max + 1; /* 非空表的深度是各元素的深度的最大值加1 */
}
Status sever1(HString* str, HString* hstr) {
//将非空串 str 分割成两部分: hsub 为第一个'上之前的子串, str 为之后的子串
int n = StrLength(*str);
int i = 0, k = 0;
HString ch, c1, c2, c3;
InitString(&ch);
InitString(&c1);
InitString(&c2);
InitString(&c3);
StrAssign(&c1, ",");
StrAssign(&c2, "(");
StrAssign(&c3, ")");
do
{
++i;
SubString(&ch, *str, i, 1);
if (!StrCompare(ch, c2))
++k;
else if (!StrCompare(ch, c3))
--k;
} while (i < n && (StrCompare(ch, c1) || k != 0)); //StrCompare(ch, c1) || k != 0 当ch是','并且k=0 才为False 【k!=0说明'左小括号'('还没有找到')'】
if (i < n)
//如果i小于字符串长度n,说明找到了逗号,将str分割为两部分;
{
StrCopy(&ch, *str);
SubString(hstr, ch, 1, i - 1); //逗号之前的赋给hstr
SubString(str, ch, i+1, n - i); //逗号之后的赋给str,更新str
}
else
//否则,将整个str复制到hstr,并将str清空。
{
StrCopy(hstr, *str);
ClearString(str);
}
return OK;
}
Status CreateGList1(GList1* L, HString S) {
HString emp, sub, hsub;
GList1 p, q;
InitString(&emp);
InitString(&sub);
InitString(&hsub);
StrAssign(&emp, "()"); /* 设emp="()" */
if (!StrCompare(S, emp)) /* 创建空表 */
{
*L = NULL;
}
else
{
*L = (GList1)malloc(sizeof(GLNode1)); //建表结点
if (!*L) /* 建表结点不成功 */
exit(OVERFLOW);
if (StrLength(S) == 1)
{
(*L)->tag = ATOM;
(*L)->atom = S.ch[0];
}
else
{
(*L)->tag = LIST;
p = *L;
SubString(&sub, S, 2, StrLength(S) - 2); /* 脱外层括号 3 = '(' + ')' + '\n'*/
do
{
sever1(&sub, &hsub);
CreateGList1(&p->ptr.hp, hsub);
q = p;
if (!StrEmpty(sub))
{
if (!(p = (GList1)malloc(sizeof(GLNode1))))
exit(OVERFLOW);
p->tag = LIST;
q->ptr.tp = p;
}
} while (!StrEmpty(sub));
q->ptr.tp = NULL;
}
}
return OK;
}
int GListLength1(GList1 L)
{ /* 初始条件: 广义表L存在。操作结果: 求广义表L的长度,即元素个数 */
int len = 0;
GList1 p = L;
if (L == NULL) /* 空表 */
return 0; /* 空表返回0 */
else if (L->tag == ATOM) /* 单原子表 */
return 1;
else /* 一般表 */
{
do
{
len++;
p = p->ptr.tp;
} while (p);
return len;
}
}
void Traverse_GL1(GList1 L, void(*v)(AtomType))
{ /* 利用递归算法遍历广义表L */
if (L == NULL) /* 检查L是否为空 */
return;
/* L不空 */
if (L->tag == ATOM) /* L为单原子 */
{
v(L->atom);
}
else /* L为子表 */
{
// 递归遍历子表的头部
Traverse_GL1(L->ptr.hp, v);
// 递归遍历子表的尾部
Traverse_GL1(L->ptr.tp, v);
}
}
void visit1(AtomType e)
{
printf("%c ", e);
}
Status CopyGList1(GList1* T, GList1 L)
{ /* 初始条件: 广义表L存在。操作结果: 由广义表L复制得到广义表T */
if (!L) /* L空 */
{
*T = NULL;
return OK;
}
*T = (GList1)malloc(sizeof(GLNode1));
if (!*T)
exit(OVERFLOW);
(*T)->tag = L->tag; /* 复制枚举变量 */
if (L->tag == ATOM) /* 复制共用体部分 */
(*T)->atom = L->atom; /* 复制单原子 */
else
{
CopyGList1(&((*T)->ptr.hp), L->ptr.hp);
CopyGList1(&((*T)->ptr.tp), L->ptr.tp);
}
return OK;
}
2️⃣结构二:
/* test.c */
#define _CRT_SECURE_NO_WARNINGS 1
#include "HString.h"
#include "LS2.h"
#include <stdio.h>
#include <string.h>
int main()
{
char p[80];
GList l, m;
HString t;
InitString(&t);
InitGList(&l);
InitGList(&m);
printf("空广义表l的深度=%d,是否空? %d(1:是 0:否)\n", GListDepth(l), GListEmpty(l));
printf("------------------------------------------\n");
printf("请输入广义表l(书写形式:空表:(),单原子:a,其它:(a,(b),b)):\n");
gets(p);
StrAssign(&t, p);
CreateGList(&l, t);
printf("广义表l的长度=%d\n", GListLength(l));
printf("广义表l的深度=%d l是否空?%d(1:是 0:否)\n", GListDepth(l), GListEmpty(l));
printf("遍历广义表l:\n");
Traverse_GL(l, visit);
printf("\n复制广义表m=l\n");
CopyGList(&m, l);
printf("广义表m的长度=%d\n", GListLength(m));
printf("广义表m的深度=%d\n", GListDepth(m));
printf("遍历广义表m:\n");
Traverse_GL(m, visit);
DestroyGList(&m);
m = GetHead(l);
printf("\nm是l的表头,遍历广义表m:\n");
Traverse_GL(m, visit);
DestroyGList(&m);
m = GetTail(l);
printf("\nm是l的表尾,遍历广义表m:\n");
Traverse_GL(m, visit);
InsertFirst_GL(&m, l);
printf("\n插入l为m的表头,遍历广义表m:\n");
Traverse_GL(m, visit);
DeleteFirst_GL(&m, &l);
printf("\n删除m的表头,遍历广义表m:\n");
Traverse_GL(m, visit);
printf("\n");
DestroyGList(&m);
return 0;
}
/* LS2.h */
#pragma once
#include "HString.h"
typedef char AtomType; /* 定义原子类型为字符型 */
/* 广义表的扩展线性链表存储表示 */
typedef enum { ATOM, LIST } ElemTag; /* ATOM==0:原子,LIST==1:子表 */
typedef struct GLNode
{
ElemTag tag; /* 公共部分,用于区分原子结点和表结点 */
union /* 原子结点和表结点的联合部分 */
{
AtomType atom; /* 原子结点的值域 */
struct GLNode* hp; /* 表结点的表头指针 */
}a;
struct GLNode* tp; /* 相当于线性链表的next,指向下一个元素结点 */
}*GList, GLNode; /* 广义表类型GList是一种扩展的线性链表 */
Status InitGList(GList* L);
Status sever(HString* str, HString* hstr);
Status CreateGList(GList* L, HString S);
void DestroyGList(GList* L);
Status CopyGList(GList* T, GList L);
int GListLength(GList L);
int GListDepth(GList L);
Status GListEmpty(GList L);
GList GetHead(GList L);
GList GetTail(GList L);
Status InsertFirst_GL(GList* L, GList e);
Status DeleteFirst_GL(GList* L, GList* e);
void Traverse_GL(GList L, void(*v)(AtomType));
void visit(AtomType e);
/* LS2.c */
#define _CRT_SECURE_NO_WARNINGS 1
#include "LS2.h"
#include "HString.h"
#include<string.h>
#include<ctype.h>
#include<malloc.h> /* malloc()等 */
#include<limits.h> /* INT_MAX等 */
#include<stdio.h> /* EOF(=^Z或F6),NULL */
#include<stdlib.h> /* atoi() */
#include<io.h> /* eof() */
#include<math.h> /* floor(),ceil(),abs() */
#include<process.h> /* exit() */
/* 函数结果状态代码 */
#define TRUE 1
#define FALSE 0
#define OK 1
#define ERROR 0
#define INFEASIBLE -1
#define OVERFLOW -2
/* 广义表的书写形式串为HString类型 */
Status InitGList(GList* L)
{ /* 创建空的广义表L */
*L = NULL;
return OK;
}
Status sever(HString* str, HString* hstr) /* 同bo5-52.c */
{ /* 将非空串str分割成两部分:hstr为第一个','之前的子串,str为之后的子串 */
int n, i = 1, k = 0; /* k记尚未配对的左括号个数 */
HString ch, c1, c2, c3;
InitString(&ch); /* 初始化HString类型的变量 */
InitString(&c1);
InitString(&c2);
InitString(&c3);
StrAssign(&c1, ",");
StrAssign(&c2, "(");
StrAssign(&c3, ")");
n = StrLength(*str);
do
{
SubString(&ch, *str, i, 1);
if (!StrCompare(ch, c2))
++k;
else if (!StrCompare(ch, c3))
--k;
++i;
} while (i <= n && StrCompare(ch, c1) || k != 0);
if (i <= n)
{
StrCopy(&ch, *str);
SubString(hstr, ch, 1, i - 2);
SubString(str, ch, i, n - i + 1);
}
else
{
StrCopy(hstr, *str);
ClearString(str);
}
return OK;
}
Status CreateGList(GList* L, HString S)
{ /* 初始条件: S是广义表的书写形式串。操作结果: 由S创建广义表L */
HString emp, sub, hsub;
GList p;
InitString(&emp);
InitString(&sub);
InitString(&hsub);
StrAssign(&emp, "()"); /* 设emp="()" */
*L = (GList)malloc(sizeof(GLNode));
if (!*L) /* 建表结点不成功 */
exit(OVERFLOW);
if (!StrCompare(S, emp)) /* 创建空表 */
{
(*L)->tag = LIST;
(*L)->a.hp = NULL;
(*L)->tp = NULL;
}
else if (StrLength(S) == 1) /* 创建单原子广义表 */
{
(*L)->tag = ATOM;
(*L)->a.atom = S.ch[0];
(*L)->tp = NULL;
}
else /* 创建一般表 */
{
(*L)->tag = LIST;
(*L)->tp = NULL;
SubString(&sub, S, 2, StrLength(S) - 2); /* 脱外层括号 */
sever(&sub, &hsub); /* 从sub中分离出表头串hsub */
CreateGList(&(*L)->a.hp, hsub);
p = (*L)->a.hp;
while (!StrEmpty(sub)) /* 表尾不空,则重复建n个子表 */
{
sever(&sub, &hsub); /* 从sub中分离出表头串hsub */
CreateGList(&p->tp, hsub);
p = p->tp;
};
}
return OK;
}
void DestroyGList(GList* L)
{ /* 初始条件: 广义表L存在。操作结果: 销毁广义表L */
GList ph, pt;
if (*L) /* L不为空表 */
{ /* 由ph和pt接替L的两个指针 */
if ((*L)->tag) /* 是子表 */
ph = (*L)->a.hp;
else /* 是原子 */
ph = NULL;
pt = (*L)->tp;
free(*L); /* 释放L所指结点 */
*L = NULL; /* 令L为空 */
DestroyGList(&ph); /* 递归销毁表ph */
DestroyGList(&pt); /* 递归销毁表pt */
}
}
Status CopyGList(GList* T, GList L)
{ /* 初始条件: 广义表L存在。操作结果: 由广义表L复制得到广义表T */
if (!L) /* L空 */
{
*T = NULL;
return OK;
}
*T = (GList)malloc(sizeof(GLNode));
if (!*T)
exit(OVERFLOW);
(*T)->tag = L->tag; /* 复制枚举变量 */
if (L->tag == ATOM) /* 复制共用体部分 */
(*T)->a.atom = L->a.atom; /* 复制单原子 */
else
CopyGList(&(*T)->a.hp, L->a.hp); /* 复制子表 */
if (L->tp == NULL) /* 到表尾 */
(*T)->tp = L->tp;
else
CopyGList(&(*T)->tp, L->tp); /* 复制子表 */
return OK;
}
int GListLength(GList L)
{ /* 初始条件: 广义表L存在。操作结果: 求广义表L的长度,即元素个数 */
int len = 0;
GList p;
if (L->tag == LIST && !L->a.hp) /* 空表 */
return 0; /* 空表返回0 */
else if (L->tag == ATOM) /* 单原子表 */
return 1;
else /* 一般表 */
{
p = L->a.hp;
do
{
len++;
p = p->tp;
} while (p);
return len;
}
}
int GListDepth(GList L)
{ /* 初始条件: 广义表L存在。操作结果: 求广义表L的深度 */
int max, dep;
GList pp;
if (L == NULL || L->tag == LIST && !L->a.hp)
return 1; /* 空表深度为1 */
else if (L->tag == ATOM)
return 0; /* 单原子表深度为0 */
else /* 求一般表的深度 */
for (max = 0, pp = L->a.hp; pp; pp = pp->tp)
{
dep = GListDepth(pp); /* 求以pp为头指针的子表深度 */
if (dep > max)
max = dep;
}
return max + 1; /* 非空表的深度是各元素的深度的最大值加1 */
}
Status GListEmpty(GList L)
{ /* 初始条件: 广义表L存在。操作结果: 判定广义表L是否为空 */
if (!L || L->tag == LIST && !L->a.hp)
return OK;
else
return ERROR;
}
GList GetHead(GList L)
{ /* 初始条件: 广义表L存在。操作结果: 取广义表L的头 */
GList h;
InitGList(&h);
if (!L || L->tag == LIST && !L->a.hp)
{
printf("\n空表无表头!");
exit(0);
}
h = (GList)malloc(sizeof(GLNode));
if (!h)
exit(OVERFLOW);
h->tag = L->a.hp->tag;
h->tp = NULL;
if (h->tag == ATOM)
h->a.atom = L->a.hp->a.atom;
else
CopyGList(&h->a.hp, L->a.hp->a.hp);
return h;
}
GList GetTail(GList L)
{ /* 初始条件: 广义表L存在。操作结果: 取广义表L的尾 */
GList T;
if (!L)
{
printf("\n空表无表尾!");
exit(0);
}
T = (GList)malloc(sizeof(GLNode));
if (!T)
exit(OVERFLOW);
T->tag = LIST;
T->tp = NULL;
CopyGList(&T->a.hp, L->a.hp->tp);
return T;
}
Status InsertFirst_GL(GList* L, GList e)
{ /* 初始条件: 广义表存在 */
/* 操作结果: 插入元素e作为广义表L的第一元素(表头,也可能是子表) */
GList p = (*L)->a.hp;
(*L)->a.hp = e;
e->tp = p;
return OK;
}
Status DeleteFirst_GL(GList* L, GList* e)
{ /* 初始条件:广义表L存在。操作结果:删除广义表L的第一元素,并用e返回其值 */
if (*L)
{
*e = (*L)->a.hp;
(*L)->a.hp = (*e)->tp;
(*e)->tp = NULL;
}
else
*e = *L;
return OK;
}
void Traverse_GL(GList L, void(*v)(AtomType))
{ /* 利用递归算法遍历广义表L */
GList hp;
if (L) /* L不空 */
{
if (L->tag == ATOM) /* L为单原子 */
{
v(L->a.atom);
hp = NULL;
}
else /* L为子表 */
hp = L->a.hp;
Traverse_GL(hp, v);
Traverse_GL(L->tp, v);
}
}
void visit(AtomType e)
{
printf("%c ", e);
}
参考:
教材:严蔚敏《数据结构》(C语言版).pdf
博客:
有关稀疏矩阵(三元数组)的基本操作(C语言)_用 c 语言实现稀疏矩阵的基本运算-CSDN博客
(数据结构第五章)行逻辑链接的顺序表_51CTO博客_顺序表是逻辑结构
十字链表法稀疏矩阵的基本操作(C语言)_十字链表矩阵复制-CSDN博客
以十字链表为存储结构实现矩阵相加(严5.27)--------西工大noj_51CTO博客_十字链表存储稀疏矩阵
数据结构(严蔚敏)广义表 _严蔚敏求广义表深度代码是不是错的-CSDN博客