スパース行列の実装
スパース行列とは何ですか?また、スパース行列を定義する方法は何ですか? mxn 行列に 0 ではない要素が t 個あると仮定します。z=t/(mxn) とすると、z<=0.05 の場合、この行列はスパース行列と呼ばれます。スパース行列は非ゼロ要素が少ないため、大容量のストレージを使用すると必然的にメモリの無駄が発生します。したがって、非ゼロ要素の値のみが必要です。スパース行列を格納するために一般的に使用される 3 つの方法を以下に示します。
トリプルシーケンステーブル
トリプルシーケンステーブルの実装は、シーケンシャルストレージ構造によって実装されます。トリプレット (データの 3 つの部分から構成されるセット) を格納する配列構造を定義します。グループ内のデータは個別に表現されます (行ラベル、列ラベル、要素値)。構造体の実装は次のとおりです。
//三元组结构体
typedef struct {
int i,j;//行标i,列标j
int data;//元素值
}triple;
//矩阵的结构表示
typedef struct {
triple data[number];//存储该矩阵中所有非0元素的三元组
int n,m,num;//n和m分别记录矩阵的行数和列数,num记录矩阵中所有的非0元素的个数
}TSMatrix;
上図に示すように、配列に順番に挿入される 3 値テーブルです。具体的な実装プロセスは次のとおりです。
#include <stdio.h>
#define MAXSIZE 100
#define OK 1
#define ERROR 0
#define ELEMTYPE int
typedef struct {
int i; //行
int j; //列
ELEMTYPE e; //元素
}Triple;
typedef struct {
Triple data[MAXSIZE+1];
int mu, nu, tu;
}TSmatrix;
void display(TSmatrix *s);
int main(void)
{
TSmatrix s;
s.mu = 3;
s.nu = 3;
s.tu = 3;
s.data[0].i = 3;
s.data[0].j = 3;
s.data[0].e = 6;
s.data[1].i = 2;
s.data[1].j = 3;
s.data[1].e = 8;
s.data[2].i = 2;
s.data[2].j = 1;
s.data[2].e = 4;
display(&s);
getchar();
return 0;
}
void display(TSmatrix *s)
{
for (int i = 1; i <= s->mu; i++)
{
for (int j = 1; j <= s->nu; j++)
{
int value = 0; //用来判断是否到了指定的(i,j)位置
for (int k = 0; k < s->tu; k++)//遍历数组中的三元表值
{
if (s->data[k].i == i && s->data[k].j == j) //若遍历至指定(i,j)就打印元素值
{
value = 1;
printf("%d ", s->data[k].e);
break;
}
}
if(value==0) //若不为三元表中存储的值就打印0
printf("%d ", 0);
}
printf("\n");
}
}
結果は上図のようになりますが、マトリックスを確認すると印刷位置に問題がないことがわかります。
上記の方法はスパース行列の保存を実現できますが、データ抽出が比較的非効率です。現在の 3x3 行列では見えないかもしれませんが、m と n が両方とも数千万の場合、非ゼロ値はスパース行列の場合 要素数が約 n の場合、時間計算量は O(mxn xn) に達し、膨大な時間の無駄になります。そこで、この方法を改良したのが以下の方法です。
行論理リンク順序表
行論理リンク シーケンスは、上記の方法に基づく改良を表しています。上記に基づいて、トリプル テーブルを格納するために使用される配列内の各行の最初の 0 以外の要素の位置を格納する配列が追加されます。 3 値テーブル配列全体を走査する必要がないため、対応する行のデータを走査するだけで済み、効率が大幅に向上します。
配列 rpos を使用して、1 次元配列の行列の各行の最初の 0 以外の要素の格納場所を記録します。走査の具体的なプロセスは次のとおりです。
rpos 配列から、最初の行の最初の 0 以外の要素が data[1] に配置されていることがわかります。そのため、この行を走査するときは、次の位置から直接開始できます。 data[1] と次の行への移動 行内の最初の 0 以外の要素の位置 (data[3] の前。2 番目の
行を移動するとき、rpos 配列から、最初の 0 以外の要素がこの行は data[3] にあるため、data[3] から直接取得できます。開始して、次の行の最初の 0 以外の要素が見つかるまで (data[4]) 走査します。3
行目を走査する場合は、rpos 配列から、この行の最初の 0 以外の要素が data[4] にあることがわかります。これは行列の最後の行であるため、rpos 配列の終わりまで走査できます (つまり、data[tu]、tu は、行列内の 0 以外の要素の総数を指します。
#include <stdio.h>
#define MAXSIZE 100
#define MAXSD 100
#define ELEMTYPE int
typedef struct {
int i;
int j;
ELEMTYPE e;
}Triple;
typedef struct {
Triple data[MAXSIZE];
int rpos[MAXSD]; //定义rpos数组用于存储每一行第一个非0元素
int mu, nu, tu;
}RLSmatrix;
void display(RLSmatrix *s);
int main(void)
{
RLSmatrix s;
s.mu = 4;
s.nu = 3;
s.tu = 4;
s.data[1].i = 1;
s.data[1].j = 1;
s.data[1].e = 3;
s.data[2].i = 2;
s.data[2].j = 2;
s.data[2].e = 4;
s.data[3].i = 3;
s.data[3].j = 3;
s.data[3].e = 6;
s.data[4].i = 4;
s.data[4].j = 3;
s.data[4].e = 3;
s.rpos[1] = 1;
s.rpos[2] = 2;
s.rpos[3] = 3;
s.rpos[4] = 4;
display(&s);
getchar();
return 0;
}
void display(RLSmatrix *s)
{
for (int i = 1; i <= s->mu; i++)
{
for (int j = 1; j <= s->nu; j++)
{
int value = 0;
if(i+1<=s->mu) //假设一共i行则前面的i-1行遍历对应行的元素
{
for (int k = s->rpos[i]; k < s->rpos[i + 1]; k++) //遍历数组中对应行的非0元素即可
{
if (s->data[k].i == i && s->data[k].j == j)
{
value = 1;
printf("%d ", s->data[k].e);
break;
}
if (value == 0)
printf("0 ");
}
}
else //最后一行只需遍历到最后一个非0元素即可
{
for (int k = s->rpos[i]; k <=s->tu; k++)
{
if (s->data[k].i == i && s->data[k].j == j)
{
value = 1;
printf("%d ", s->data[k].e);
break;
}
if (value == 0)
printf("0 ");
}
}
}
printf("\n");
}
}
スパース行列を格納するクロスリンクリスト方式
上記 2 つの保存方法は、固定保存に使用する場合にはうまく機能しますが、ゼロ以外の要素の挿入または削除が含まれる場合、2 つの行列を加算する操作など、要素の値の移動が発生します。チェーンを使用します。トリプルを表すにはトリプル ストレージを使用する方が適切です。この保存方法では、「リンク リスト + 配列」構造が使用されます。
上の図からわかるように、非ゼロ要素は 5 つのフィールドを含むノードで表され、2 つのポインターを使用してそれぞれ同じ列と同じ行の要素を指します。次に、2 つの 1 次元配列を使用して行リンク リストを保存し、列リンク リストを使用してこれらのリンク リストを保存します。ゼロ以外の各要素は、行リンク リストのノードと列リンク リストのノードの両方です。マトリックス全体が十字にリンクされたリストを形成します。
#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;
}
}
}
}