• 稀疏矩阵的基础


    目录

    一、采用矩阵的正常存储方式,实现矩阵转置的经典算法

    二、利用三元组表实现稀疏矩阵的转置

    方法一:为了保证转置后的矩阵的三元组表B也是以“行序为主序”进行存放,则需要对行,列互换后的三元组表B按B的行下标(即A的列下标)大小重新排序

    方法二:依次按三元组A的次序进行转置,转置后直接访到三元组B的正确位置上。这种转置算法称为快速转置算法。

    三、稀疏矩阵的链式存储结构:十字链表

    (一)在进行行链表插入的时候3中情况

    情况1

    情况2

    情况3 

    (二)在进行列链表插入的时候3种情况

    情况1

    情况2 

    情况3 


    一、采用矩阵的正常存储方式,实现矩阵转置的经典算法

    1. #pragma once
    2. #include
    3. #include
    4. #include
    5. #include
    6. using namespace std;
    7. typedef int ElemType;
    8. int main()
    9. {
    10. //int v1[4][3]={{1,2,3},{4,5,6},{7,8,9},{10,11,12}};
    11. //int v2[3][4];
    12. vectorint>> v1{ {1,2,3},{4,5,6},{7,8,9},{10,11,12} };
    13. vectorint>> v2{{0,0,0,0},{0,0,0,0},{0,0,0,0}};
    14. int i = 0, j = 0;
    15. for (i = 0; i < 4; i++)
    16. {
    17. for (j = 0; j < 3; j++)
    18. {
    19. v2[j][i] = v1[i][j];
    20. }
    21. }
    22. for (int i = 0; i < 3; i++)
    23. {
    24. for (int j = 0; j < 4; j++)
    25. {
    26. cout << v2[i][j] << " ";
    27. }
    28. cout << endl;
    29. }
    30. return 0;
    31. }
    • 时间复杂度为:O(A.m×A.n) 

    二、利用三元组表实现稀疏矩阵的转置

    • 三元组线性表中一个元素是用来描述一个非零元素的信息.
    • 稀疏矩阵的顺序存储就是用一段连续的存储单元依次存储其对应的三元组线性表,并称这种存储结构的三元组线性表为三元组顺序表。 

    方法一:为了保证转置后的矩阵的三元组表B也是以“行序为主序”进行存放,则需要对行,列互换后的三元组表B按B的行下标(即A的列下标大小重新排序

      

    1. #pragma once
    2. #include
    3. #include
    4. #include
    5. #include
    6. using namespace std;
    7. typedef int ElemType;
    8. #define MAXSIZE 100//非零元素的个数最多为1000
    9. typedef struct
    10. {
    11. int row, col;//非零元素的行下标和列下标
    12. ElemType e;//该非零元素的值
    13. }Triple;
    14. typedef struct
    15. {
    16. Triple data[MAXSIZE+1];//非零元素的三元组表,data[0]未用
    17. int m, n, len;//矩阵的行数,列数和非零元素的个数
    18. }TSMatrix;
    19. void Init(TSMatrix &S);//初始化
    20. bool Push(TSMatrix &S);//录入数据
    21. void Transpose(TSMatrix A, TSMatrix &B);//转置矩阵
    22. void print(TSMatrix S);//打印三元组
    1. #include"Array.h"
    2. void Init(TSMatrix& S)
    3. {
    4. S.m = 0;
    5. S.n = 0;
    6. S.len = 0;
    7. }
    8. bool Push(TSMatrix &S)
    9. {
    10. vector < vector<int>> v{ {0,0,0,0,0,1},{0,0,2,3,0,0},{0,0,0,0,6,0} };
    11. int k = 1;
    12. S.m = v.size();
    13. S.n = v[0].size();
    14. for (int i = 0; i < v.size(); i++)
    15. {
    16. for (int j = 0; j < v[0].size(); j++)
    17. {
    18. if (v[i][j] != 0)
    19. {
    20. S.data[k].row = i + 1;
    21. S.data[k].col = j + 1;
    22. S.data[k].e = v[i][j];
    23. k++;
    24. S.len++;
    25. }
    26. }
    27. }
    28. return true;
    29. }
    30. void Transpose(TSMatrix A, TSMatrix &B)
    31. {
    32. int i = 0, j = 0;
    33. B.m = A.n;
    34. B.n = A.m;
    35. B.len = A.len;
    36. if (B.len > 0)
    37. {
    38. int k = 1;//位置计数器用于指向当前转置后元素应放入三元组表B中的位置
    39. for (i = 1; i <= A.n; i++)//A数组中非零元素所在列最高可能为A.n,当寻找位于第1列的非零元素时
    40. //需要遍历A.len(表示非零元素个数)次,再寻找位于第2列的非零元素时,也要遍历A.len次
    41. {
    42. for (j = 1; j<= A.len; j++)//遍历A数组列
    43. {
    44. if (A.data[j].col == i)
    45. {
    46. B.data[k].row = A.data[j].col;
    47. B.data[k].col = A.data[j].row;
    48. B.data[k].e = A.data[j].e;
    49. k++;
    50. }
    51. }
    52. }
    53. }
    54. }
    55. void print(TSMatrix S)
    56. {
    57. int i = 1, j = 1;
    58. for (i = 1; i <= S.len; i++)
    59. {
    60. cout << S.data[i].row << " " << S.data[i].col << " " << S.data[i].e << endl;
    61. }
    62. cout << endl;
    63. }
    1. #include"Array.h"
    2. int main()
    3. {
    4. TSMatrix S;
    5. Init(S);
    6. Push(S);
    7. print(S);
    8. cout << S.m << " " << S.n<
    9. TSMatrix B;
    10. Transpose(S,B);
    11. print(B);
    12. return 0;
    13. }

    • 算法的时间耗费主要是在双重循环中,其时间复杂度为O(A.n×A.len)(其中A.n表示原始矩阵的列数,A.len表示原始矩阵中非零元素的个数),最坏的情况是矩阵A全是非零元素即A.len=A.m×A.n,其时间复杂度为O(A.m×A.n^2)。

    方法二:依次按三元组A的次序进行转置,转置后直接访到三元组B的正确位置上。这种转置算法称为快速转置算法。

    • 为了能将待转置三元组表A中元素一次定位到三元组表B的正确位置上,需要预先计算一下数据:
    1. 待转置矩阵source每一列中非零元素的个数(即转置后矩阵dest每一行中非零元素的个数)。
    2. 待转置矩阵source每一列中第一个非零元素在三元组表B中的正确位置(即转置后矩阵dest每一行中第一个非零元素在三元组B中国的正确位置) 
    • 实现:设两个数组
    1. num[col]:表示矩阵M中第col列中非零元素个数
    2. pos[col]:指示M中第col列第一个非零元素在三元表B中的位置:
    1. pos[1]=1;
    2. pos[col]=pos[col-1]+num[col-1];(2<=col<=原始矩阵的列数)

     

    1. #pragma once
    2. #include
    3. #include
    4. #include
    5. #include
    6. using namespace std;
    7. #define MAXSIZE 100//非零元素的个数最多为100
    8. typedef int ElemType;
    9. typedef struct
    10. {
    11. int row, col;//非零元素的行下标和列下标
    12. ElemType e;//该非零元素的值
    13. }Triple;
    14. typedef struct
    15. {
    16. Triple data[MAXSIZE+1];//非零元素的三元组表,data[0]未用
    17. int m, n, len;//矩阵的行数,列数和非零元素的个数
    18. }TSMatrix;
    19. void Init(TSMatrix& S);//初始化
    20. bool Push(TSMatrix& S);//给TSMatrix对象的三元组中录入数据
    21. bool TastTransposeTSMatrix(TSMatrix A,TSMatrix &B);//转置矩阵
    22. void print(TSMatrix S);//打印三元组
    23. void print2(TSMatrix S);//打印稀疏矩阵
    1. #include"Array.h"
    2. void Init(TSMatrix& S)
    3. {
    4. S.m = 0;
    5. S.n = 0;
    6. S.len = 0;
    7. }
    8. bool Push(TSMatrix& S)//给TSMatrix对象的三元组中录入数据
    9. {
    10. vector < vector<int>> v{ {0,0,0,0,0,1},{0,0,2,3,0,0},{0,0,0,0,6,0} };
    11. int k = 1;
    12. S.m = v.size();
    13. S.n = v[0].size();
    14. for (int i = 0; i < v.size(); i++)
    15. {
    16. for (int j = 0; j < v[0].size(); j++)
    17. {
    18. if (v[i][j] != 0)
    19. {
    20. S.data[k].row = i + 1;
    21. S.data[k].col = j + 1;
    22. S.data[k].e = v[i][j];
    23. k++;
    24. S.len++;
    25. }
    26. }
    27. }
    28. return true;
    29. }
    30. bool TastTransposeTSMatrix(TSMatrix A, TSMatrix& B)//转置矩阵
    31. {
    32. int num[MAXSIZE];//表示原始矩阵A中第col列中非零元素个数
    33. int pos[MAXSIZE];//指示原始矩阵A中第col列第一个非零元素在转置矩阵B中的三元组中的位置
    34. B.m = A.n;
    35. B.n = A.m;
    36. B.len = A.len;
    37. int col = 0;
    38. if (A.len!=0)
    39. {
    40. for (int col = 1; col <= A.n; col++)//先将原始矩阵A中每一列的非零元素个数都初始化为0
    41. {
    42. num[col] = 0;
    43. }
    44. for (int j = 1; j <= A.len; j++)//对矩阵A的三元组的列从头开始向下扫描
    45. {
    46. num[A.data[j].col]++;//比如A.data[1].col对应矩阵A的三元组表col中的第一个数据2,
    47. //2表示第2列,即A.data[1].col=2,说明原始矩阵A中第2列出现了一个非零元素,则num[A.data[j].col]++
    48. }
    49. pos[1] = 1;
    50. for (int col = 2; col <= A.n; col++)//求原始矩阵A中的第col列中第一个非零元素在转置矩阵B的三元组中的位置
    51. {
    52. pos[col] = pos[col - 1] + num[col - 1];
    53. }
    54. for (int p = 1; p <= A.len; p++)
    55. {
    56. col = A.data[p].col;
    57. int k = pos[col];
    58. B.data[k].row = A.data[p].col;
    59. B.data[k].col = A.data[p].row;
    60. B.data[k].e = A.data[p].e;
    61. pos[col]++;//当原始矩阵A的三元组col中的A.data[p].col=A.data[p+1].col的时候,pos[col]后移动
    62. //一位
    63. }
    64. }
    65. return true;
    66. }
    67. void print(TSMatrix S)
    68. {
    69. int i = 1, j = 1;
    70. for (i = 1; i <= S.len; i++)
    71. {
    72. cout << S.data[i].row << " " << S.data[i].col << " " << S.data[i].e << endl;
    73. }
    74. cout << endl;
    75. }
    76. void print2(TSMatrix S)//打印稀疏矩阵
    77. {
    78. int a[30][30];
    79. for (int i = 0; i < S.m; i++)
    80. {
    81. for (int j = 0; j < S.n; j++)
    82. {
    83. a[i][j] = 0;
    84. }
    85. }
    86. for (int i = 1; i <= S.len; i++)
    87. {
    88. int m = S.data[i].row;
    89. int n = S.data[i].col;
    90. a[m - 1][n - 1] = S.data[i].e;
    91. }
    92. for (int i = 0; i < S.m; i++)
    93. {
    94. for (int j = 0; j < S.n; j++)
    95. {
    96. cout << a[i][j] << " ";
    97. }
    98. cout << endl;
    99. }
    100. }
    1. #include"Array.h"
    2. int main()
    3. {
    4. TSMatrix S;
    5. Init(S);
    6. Push(S);
    7. cout << "原始矩阵:" << endl;
    8. print2(S);
    9. cout << "原始矩阵的三元组:" << endl;
    10. print(S);
    11. cout << endl;
    12. TSMatrix B;
    13. TastTransposeTSMatrix(S, B);
    14. cout << "转置后的矩阵:" << endl;
    15. print2(B);
    16. cout << "转置后的矩阵的三元组:" << endl;
    17. print(B);
    18. return 0;
    19. }

    • 注意:为什么pos[col]++ 

    三、稀疏矩阵的链式存储结构:十字链表

    • 用两个一维的指针数组分别存放各行链表的头指针和各列链表的头指针,从而得到了矩阵的十字链表存储结构 

    (一)在进行行链表插入的时候3中情况

    情况1

    情况2

    情况3 

    (二)在进行列链表插入的时候3种情况

    情况1

    情况2 

    情况3 

    1. #pragma once
    2. #include
    3. #include
    4. #include
    5. #include
    6. using namespace std;
    7. typedef int ElemType;
    8. typedef struct OLNode
    9. {
    10. int row, col;//非零元素的行下标,列下标
    11. ElemType value;
    12. struct OLNode* right, * down;//非零元素所在行表,列表的后继链域
    13. }OLNode,*OLink;
    14. typedef struct
    15. {
    16. OLink* row_head, * col_head;//行、列链表的头指针向量
    17. int m, n, len;//稀疏矩阵的行数,列数,非零元素个数
    18. }CrossList;
    19. bool CreatCrossList(CrossList* M);
    20. void Print(CrossList* M);//打印
    1. #include"Array.h"
    2. bool CreatCrossList(CrossList* M)
    3. {
    4. int m, n, t;
    5. cout << "请依次输入矩阵的行数,列数和非零元素个数:" << endl;
    6. cin >> m >> n >> t;//输入M的行数,列数和非零元素个数
    7. M->m = m;
    8. M->n = n;
    9. M->len = t;
    10. M->row_head = (OLink*)malloc((m + 1) * sizeof(OLink));
    11. if (M->row_head == NULL)//开辟空间失败直接退出
    12. {
    13. exit(-1);
    14. }
    15. M->col_head = (OLink*)malloc((n + 1) * sizeof(OLink));
    16. if (M->col_head == NULL)
    17. {
    18. exit(-1);
    19. }
    20. for (int i = 1; i <= M->m; i++) //初始化行头指针,为空链表
    21. {
    22. M->row_head[i] = NULL;
    23. }
    24. for (int j = 1; j <= M->n; j++) //初始化行头指针,为空链表
    25. {
    26. M->col_head[j] = NULL;
    27. }
    28. cout << "请按次序输入非零元的行,列,元素值:" << endl;
    29. for (int k = 0; k < t; k++)//非零元素的行,列,值
    30. {
    31. int i, j;
    32. ElemType e;
    33. cin >> i >> j >> e;
    34. OLNode* p = (OLNode*)malloc(sizeof(OLNode));
    35. if (p == NULL)//开辟空间失败直接退出
    36. {
    37. exit(-1);
    38. }
    39. p->row = i;
    40. p->col = j;
    41. p->value = e;
    42. //先进行行链表插入
    43. if (M->row_head[i] == NULL || p->col < M->row_head[i]->col)
    44. //第i行头结点为空,或者新建结点的列号小于第i行第1个结点的列号
    45. {
    46. p->right = M->row_head[i];
    47. M->row_head[i] = p;
    48. }
    49. //第2种将上面的if分开写
    50. //if (M->row_head[i] == NULL )
    51. // //第i行头结点为空
    52. //{
    53. // M->row_head[i] = p;
    54. // p->right = NULL;
    55. //}
    56. //else if (p->col < M->row_head[i]->col)//新建结点的列号小于第i行第1个结点的列号
    57. //{
    58. // p->right = M->row_head[i];
    59. // M->row_head[i] = p;
    60. //}
    61. else
    62. {
    63. OLNode* q = M->row_head[i];
    64. for (q; q->right != NULL && q->right->col < j; q = q->right)
    65. {
    66. p->right = q->right;
    67. q->right = p;
    68. }
    69. }
    70. //再进行列链表插入
    71. if (M->col_head[j] == NULL || p->row < M->col_head[j] -> row)
    72. //第j列头结点为空,或者新建结点的行号小于第j列第1个结点的行号
    73. {
    74. p->down = M->col_head[j];
    75. M->col_head[j] = p;
    76. }
    77. //第2种将上面的if分开写
    78. //if (M->col_head[j] == NULL )
    79. // //第j列头结点为空
    80. //{
    81. // M->col_head[j] = p;
    82. // p->down = NULL;
    83. //}
    84. //else if (p->row < M->col_head[j]->row)//新建结点的行号小于第j列第1个结点的行号
    85. //{
    86. // p->down = M->col_head[j];
    87. // M->col_head[j] = p;
    88. //}
    89. else
    90. {
    91. OLNode* q2 = M->col_head[j];
    92. for (q2; q2->down != NULL && q2->down->row < i; q2 = q2->down)
    93. {
    94. p->down = q2->down;
    95. q2->down = p;
    96. }
    97. }
    98. }
    99. return true;
    100. }
    101. void Print(CrossList* M)//打印
    102. {
    103. OLNode* p;
    104. for (int i = 1; i <= M->m; i++)//从矩阵的第一行开始扫描到最后一行
    105. {
    106. p = M->row_head[i];
    107. for (int j = 1; j <= M->n; j++)//从矩阵第一列开始扫描到最后一列
    108. {
    109. if (p == NULL || p->col != j)//已经扫描到该行表尾或当前结点的列值不等于当前列值
    110. {
    111. cout << "0" << " ";
    112. }
    113. else
    114. {
    115. cout << p->value << " ";
    116. p = p->right;
    117. }
    118. }
    119. cout << endl;
    120. }
    121. }
    1. #include"Array.h"
    2. int main()
    3. {
    4. CrossList S;
    5. CreatCrossList(&S);
    6. cout << "以矩阵形式输出:" << endl;
    7. Print(&S);
    8. return 0;
    9. }

  • 相关阅读:
    C++数据类型总结,看这一篇就够了
    Crypto/加密货币 应用
    Day49——盒子类型,浮动属性,定位属性,JavaScript基础语法
    如何设计存储架构
    深度学习 RNN架构解析
    Redis基础学习
    (done) 什么是词嵌入技术?word embedding ?(这里没有介绍词嵌入算法)(没有提到嵌入矩阵如何得到)
    已解决java.lang.ClassCircularityError: 类循环依赖错误的正确解决方法,亲测有效!!!
    如何给视频加水印标记?分享三个好用方法给你
    分布式缓存寻址算法
  • 原文地址:https://blog.csdn.net/m0_63783532/article/details/127711019