• 数据结构5---矩阵和广义表


    一、矩阵的压缩存储

    特殊矩阵:矩阵中很多值相同的元素并且它们的分布有一定的规律

    稀疏矩阵:矩阵中有很多零元素。压缩存储的基本思想是:

            (1)为多个值相同的元素只分配一个存储空间;

            (2)对零元素不分配存储空间。

    1、特殊矩阵的压缩存储

    (1)对称矩阵

    只存储下三角部分的元素

    存储结构

    对于下三角的元素aij(i>=j),在数组中的下标与i,j关系为 k = i*(i-1)/2 + j-1

    上三角的元素aij(i

     (2)三角矩阵

    只存储上三角(或下三角)部分的元素

    下三角矩阵

    存储:

    1.下三角元素        2.对角线上方的常数

    矩阵中的任一元素aij在数组中的下标k与i,j的对应关系

    当i>=j时        k=i*(i-1)/2+j-1

    当i

    上三角矩阵

    存储:

    1.上三角元素        2.对角线下方的常数

    矩阵中的任一元素aij在数组中的下标k与i,j的对应关系

    当i<=j时        k=(i-1)*(2n-i+2)/2+j-1

    当i>j              k=n*(n+1)/2

     (3)对角矩阵

    对角矩阵:所有非零元素都集中在以主对角线为中心的带状区域中,除了主对角线和它的上下方若干条对角线的元素外,所有其他元素都为零。

     (4)稀疏矩阵

     我们定义了一个三元组

    1. struct Triple
    2. {
    3. int row,col; //行和列
    4. DataType item; //值
    5. };

    三元组表:将稀疏矩阵的非零元素对应的三元组所构成的集合,按行优先的顺序排列成一个线性表。

    采用顺序存储结构存储三元组表

    三元顺序表的存储结构定义

    1. const int MAX = 100;
    2. struct SparseMatrix
    3. {
    4. struct Triple data[MAX]; //存储非零元素
    5. int mu,nu,num; //行数,列数,非零元素个数
    6. };

    这种存储方法的缺点是:进行矩阵加法、减法等操作时,非零元素的个数和位置都会发生变化,顺序存储法就非常不方便了。

    2、十字链表

    采用链接存储结构存储三元组表,每个非零元素对应的三元组存储为一个链表结点,结构为:

    rowcolitem
    downright

    row:存储非零元素的行号

    col:存储非零元素的列号

    item:存储非零元素的值

    right:指针域,指向同一行中的下一个三元组

    down:指针域,指向同一列中的下一个三元组

    定义结构体

    1. #define ElementType int
    2. typedef struct OLNode
    3. {
    4. int row;//1~m
    5. int col;//1~n
    6. ElementType value;
    7. struct OLNode *right, *down;//非零元素所在行,列后继链域
    8. }OLNode, *OLink;
    9. typedef struct CrossList
    10. {
    11. OLink* row_head, *col_head;//行,列链表的头指针向量
    12. int m;//行数
    13. int n;//列数
    14. int len;//非零元素个数
    15. }CrossList;

    代码实现

    1. //稀疏矩阵的链式存储结构:十字链表
    2. #include
    3. #include
    4. #include
    5. #define ElementType int
    6. typedef struct OLNode
    7. {
    8. int row;//1~m
    9. int col;//1~n
    10. ElementType value;
    11. struct OLNode *right, *down;//非零元素所在行,列后继链域
    12. }OLNode, *OLink;
    13. typedef struct CrossList
    14. {
    15. OLink* row_head, *col_head;//行,列链表的头指针向量
    16. int m;//行数
    17. int n;//列数
    18. int len;//非零元素个数
    19. }CrossList;
    20. void CreatCrossList(CrossList* M, int m, int n, int len)
    21. {
    22. M->m = m;
    23. M->n = n;
    24. M->len = len;
    25. if(!(M->row_head = (OLink*)malloc((m + 1) * sizeof(OLink))))
    26. {
    27. perror("row_head");
    28. exit(-1);
    29. }
    30. if(!(M->col_head = (OLink*)malloc((n + 1) * sizeof(OLink))))
    31. {
    32. perror("col_head");
    33. exit(-1);
    34. }
    35. memset(M->row_head, 0, (m + 1) * sizeof(OLink));
    36. memset(M->col_head, 0, (n + 1) * sizeof(OLink));//初始化为空链表
    37. int i = 0, j = 0, e = 0;//结点的行,列,值
    38. printf("请输入结点的行号,列号,值:\n");
    39. while(len)
    40. {
    41. scanf("%d %d %d", &i, &j, &e);
    42. if(i < 1||i > m||j < 1||j > n)
    43. {
    44. printf("行列号不合法,请重新输入:\n");
    45. continue;
    46. }
    47. int flag = 0;//检查位置是否重复
    48. for(OLNode* k = M->row_head[i]; k != NULL; k = k->right)
    49. {
    50. if(k->col == j)
    51. flag = 1;
    52. }
    53. if(flag)
    54. {
    55. printf("结点位置重复,请重新输入:\n");
    56. continue;
    57. }
    58. OLNode* p = (OLNode*)malloc(sizeof(OLNode));//申请结点
    59. if(p == NULL)
    60. {
    61. perror("Node application");
    62. exit(-1);
    63. }
    64. p->row = i; p->col = j; p->value = e;
    65. p->right = p->down = NULL;
    66. //行链表插入
    67. if(M->row_head[i] == NULL)//该行还没有结点
    68. {
    69. M->row_head[i] = p;
    70. }
    71. else if(p->col < M->row_head[i]->col)//新建结点列号<该行首结点列号
    72. {
    73. p->right = M->row_head[i];
    74. M->row_head[i] = p;
    75. }
    76. else//寻找插入位置
    77. {
    78. OLNode* q;
    79. for(q = M->row_head[i]; q->right && q->right->col < j; q = q->right);
    80. p->right = q->right;
    81. q->right = p;
    82. }
    83. //列链表插入
    84. if(M->col_head[j] == NULL)//该列还没有结点
    85. {
    86. M->col_head[j] = p;
    87. }
    88. else if(p->row < M->col_head[j]->row)//新建结点行号<改行首节点行号
    89. {
    90. p->down = M->col_head[j];
    91. M->col_head[j] = p;
    92. }
    93. else//寻找插入位置
    94. {
    95. OLNode* q;
    96. for(q = M->col_head[j]; q->down && q->down->row < i; q = q->down)
    97. p->down = q->down;
    98. p->down = p;
    99. }
    100. len--;
    101. printf("插入成功!\n");
    102. printf("请输入结点的行号,列号,值:\n");
    103. }
    104. }
    105. void PrintCrossList(CrossList* M)//打印十字链表
    106. {
    107. for(int row = 0; row < M->m; row++)
    108. {
    109. OLNode* p = M->row_head[row];
    110. while(p != NULL)
    111. {
    112. printf("(%d, %d)=%d ", p->row, p->col, p->value);
    113. p = p->right;
    114. }
    115. printf("\n");
    116. }
    117. }
    118. int main()
    119. {
    120. CrossList M;
    121. CreatCrossList(&M, 10, 10, 10);
    122. PrintCrossList(&M);
    123. return 0;
    124. }

    3、快速转置

    在顺序结构里转置

    (1)简单转置

    矩阵source的列序进行转置

    算法实现

    1. SparseMatrix source,dest;
    2. int q = 1;
    3. for(col=1;col<=source.nu;col++)
    4. {
    5. for(p=1;p<=source.num;p++){
    6. if(source.data[p].col==col)
    7. {
    8. dest.data[q].row = source.data[p].col;
    9. dest.data[q].col = source.data[p].row;
    10. dest.data[q].e = source.data[p].e;
    11. q++;
    12. }
    13. }
    14. }

    (2)快速转置

    矩阵source的行序进行转置

    方法:增加2个辅助向量

    这里的cPos[col]为:1 3 5 7 8 8 9

    -3一定是第一个,12一定是第三个....

    cPos算法:

    1. cPos[1] = 1;
    2. for(col = 2;col <= source.nu; col++)
    3. {
    4. cPos[col] = cPos[col-1]+cNum[col-1];
    5. }

    cNum算法:

    1. for(col = 1;col <= source.nu; ++col) cNum[col]=0; //初始化全为0
    2. for(sPos = 1;sPos <= source.number;++sPos)
    3. ++cNum[source.data[sPos].col]; //就是按列遍历整个三元组,列号为几,就在对应的cNum上++

    就是找位置,找到对应位置后,其相应的cPs+1

    二、广义表

    1、定义

     广义表又称列表,也是一种线性存储结构,通数组类似,广义表中即可存储不可再分的元素也能存储可在分元素。

            例如:数组中可以存储‘a’、3这样的字符或数字,也能存储数组,比如二维数组、三维数组,数组都是可在分成子元素的。广义表也是如此,但与数组不同的是,在广义表中存储的数据是既可以再分也可以不再分的,形如:{1,{1,2,3}}。

        广义表记作:

    LS = (a1,a2,…,an)

    2、原子和子表

        广义表中存储的单个元素称为 "原子",而存储的广义表称为 "子表"。

    例如创建一个广义表 LS = {1,{1,2,3}},我们可以这样解释此广义表的构成:广义表 LS 存储了一个原子 1 和子表 {1,2,3}。

    以下是广义表存储数据的一些常用形式:

    • A = ():A 表示一个广义表,只不过表是空的。
    • B = (e):广义表 B 中只有一个原子 e。
    • C = (a,(b,c,d)) :广义表 C 中有两个元素,原子 a 和子表 (b,c,d)。
    • D = (A,B,C):广义表 D 中存有 3 个子表,分别是A、B和C。这种表示方式等同于 D = ((),(e),(b,c,d)) 。
    • E = (a,E):广义表 E 中有两个元素,原子 a 和它本身。这是一个递归广义表,等同于:E = (a,(a,(a,…)))。


    注意,A = () 和 A = (()) 是不一样的。前者是空表,而后者是包含一个子表的广义表,只不过这个子表是空表。

    3、表头和表尾

    当广义表不是空表时,称第一个数据(原子或子表)为"表头",剩下的数据构成的新广义表为"表尾"。

    强调一下,除非广义表为空表,否则广义表一定具有表头和表尾,且广义表的表尾一定是一个广义表。

    例如在广义表中 LS={1,{1,2,3},5} 中,表头为原子 1,表尾为子表 {1,2,3} 和原子 5 构成的广义表,即 {{1,2,3},5}。

    再比如,在广义表 LS = {1} 中,表头为原子 1 ,但由于广义表中无表尾元素,因此该表的表尾是一个空表,用 {} 表示。

    4、广义表的存储结构

    使用顺序表实现广义表结构,不仅需要操作 n 维数组(例如 {1,{2,{3,4}}} 就需要使用三维数组存储),还会造成存储空间的浪费。

    1. typedef struct GLNode{
    2. int tag;//标志域
    3. union{
    4. char atom;//原子结点的值域
    5. struct{
    6. struct GLNode * hp,*tp;
    7. }ptr;//子表结点的指针域,hp指向表头;tp指向表尾
    8. }subNode;
    9. }*Glist;

     这里用到了 union 共用体,因为同一时间此节点不是原子节点就是子表节点,当表示原子节点时,就使用 atom 变量;反之则使用 ptr 结构体。

    1. Glist creatGlist(Glist C) {
    2. //广义表C
    3. C = (Glist)malloc(sizeof(Glist));
    4. C->tag = 1;
    5. //表头原子‘a’
    6. C->subNode.ptr.hp = (Glist)malloc(sizeof(Glist));
    7. C->subNode.ptr.hp->tag = 0;
    8. C->subNode.ptr.hp->subNode.atom = 'a';
    9. //表尾子表(b,c,d),是一个整体
    10. C->subNode.ptr.tp = (Glist)malloc(sizeof(Glist));
    11. C->subNode.ptr.tp->tag = 1;
    12. C->subNode.ptr.tp->subNode.ptr.hp = (Glist)malloc(sizeof(Glist));
    13. C->subNode.ptr.tp->subNode.ptr.tp = NULL;
    14. //开始存放下一个数据元素(b,c,d),表头为‘b’,表尾为(c,d)
    15. C->subNode.ptr.tp->subNode.ptr.hp->tag = 1;
    16. C->subNode.ptr.tp->subNode.ptr.hp->subNode.ptr.hp = (Glist)malloc(sizeof(Glist));
    17. C->subNode.ptr.tp->subNode.ptr.hp->subNode.ptr.hp->tag = 0;
    18. C->subNode.ptr.tp->subNode.ptr.hp->subNode.ptr.hp->subNode.atom = 'b';
    19. C->subNode.ptr.tp->subNode.ptr.hp->subNode.ptr.tp = (Glist)malloc(sizeof(Glist));
    20. //存放子表(c,d),表头为c,表尾为d
    21. C->subNode.ptr.tp->subNode.ptr.hp->subNode.ptr.tp->tag = 1;
    22. C->subNode.ptr.tp->subNode.ptr.hp->subNode.ptr.tp->subNode.ptr.hp = (Glist)malloc(sizeof(Glist));
    23. C->subNode.ptr.tp->subNode.ptr.hp->subNode.ptr.tp->subNode.ptr.hp->tag = 0;
    24. C->subNode.ptr.tp->subNode.ptr.hp->subNode.ptr.tp->subNode.ptr.hp->subNode.atom = 'c';
    25. C->subNode.ptr.tp->subNode.ptr.hp->subNode.ptr.tp->subNode.ptr.tp = (Glist)malloc(sizeof(Glist));
    26. //存放表尾d
    27. C->subNode.ptr.tp->subNode.ptr.hp->subNode.ptr.tp->subNode.ptr.tp->tag = 1;
    28. C->subNode.ptr.tp->subNode.ptr.hp->subNode.ptr.tp->subNode.ptr.tp->subNode.ptr.hp = (Glist)malloc(sizeof(Glist));
    29. C->subNode.ptr.tp->subNode.ptr.hp->subNode.ptr.tp->subNode.ptr.tp->subNode.ptr.hp->tag = 0;
    30. C->subNode.ptr.tp->subNode.ptr.hp->subNode.ptr.tp->subNode.ptr.tp->subNode.ptr.hp->subNode.atom = 'd';
    31. C->subNode.ptr.tp->subNode.ptr.hp->subNode.ptr.tp->subNode.ptr.tp->subNode.ptr.tp = NULL;
    32. return C;
    33. }

    4、长度 

     广义表的长度,指的是广义表中所包含的数据元素的个数。
    由于广义表中可以同时存储原子和子表两种类型的数据,因此在计算广义表的长度时规定,广义表中存储的每个原子算作一个数据,同样每个子表也只算作是一个数据。
    例如,在广义表 {a,{b,c,d}} 中,它包含一个原子和一个子表,因此该广义表的长度为 2。
    再比如,广义表 {{a,b,c}} 中只有一个子表 {a,b,c},因此它的长度为 1。
    前面我们用 LS={a1,a2,...,an} 来表示一个广义表,其中每个 ai 都可用来表示一个原子或子表,其实它还可以表示广义表 LS 的长度为 n。

    广义表规定,空表 {} 的长度为 0。

     5、深度

    由于这个不做考试内容,分享一个博客

    数据结构-广义表-CSDN博客icon-default.png?t=N7T8https://blog.csdn.net/qq_51701007/article/details/126352030?ops_request_misc=%257B%2522request%255Fid%2522%253A%2522171894059816800211521274%2522%252C%2522scm%2522%253A%252220140713.130102334..%2522%257D&request_id=171894059816800211521274&biz_id=0&utm_medium=distribute.pc_search_result.none-task-blog-2~all~top_positive~default-1-126352030-null-null.142%5Ev100%5Epc_search_result_base5&utm_term=%E5%B9%BF%E4%B9%89%E8%A1%A8&spm=1018.2226.3001.4187

  • 相关阅读:
    一定要知道的 NOI 大纲(2023年修订版)变化
    Visual Studio 2022 安装
    用于肺结节分类的常规 EHR 的纵向多模态Transformer集成成像和潜在临床特征
    Ubuntu中安装Anaconda 如何将 路径导入为全局变量
    将dumpbin从VS中抠出来,并使用dumpbin查看exe和dll库的依赖关系
    企业服务器中了babyk勒索病毒怎么办,babyk勒索病毒解密数据集恢复
    linux-conda环境安装教程
    营业利润里首次突破两位数,瑞幸能否延续神话?
    [激光器原理与应用-11]: 2022 年中国光纤激光器行业全景图谱
    Lua 协程
  • 原文地址:https://blog.csdn.net/2303_79741845/article/details/139853052