1. 程式人生 > >稀疏矩陣的儲存方法之二

稀疏矩陣的儲存方法之二

轉自:http://study.hhit.edu.cn/subject/CourseWare_Detail.aspx?TeachCourseWareID=257

1.基本概念

稀疏矩陣(
SparseMatrix):是矩陣中的一種特殊情況,其非零元素的個數遠小於零元素的個數。
m行n列的矩陣含t個非零元素,則稱

以二維陣列表示高階的稀疏矩陣時,會產生零值元素佔的空間很大且進行了很多和零值的運算的問題。 
特殊矩陣:值相同的元素或0元素在矩陣中的分佈有一定的規律。如下三角陣、三對角陣、稀疏矩陣。
壓縮儲存:為多個值相同的元素只分配一個儲存空間;對0元素不分配空間。目的是節省大量儲存空間。
n x n的矩陣一般需要n2個儲存單元,當為對稱矩陣時需要n(1+n)/2個單元。



2.三元組順序表——壓縮儲存稀疏矩陣方法之一(順序儲存結構)


三元組順序表又稱有序的雙下標法,對矩陣中的每個非零元素用三個域分別表示其所在的行號列號元素值。它的特點是,非零元在表中按行序有序儲存,因此便於進行依行順序處理的矩陣運算。當矩陣中的非0元素少於1/3時即可節省儲存空間。
(1) 稀疏矩陣的三元組順序表儲存表示方法
#define MAXSIZE 12500 // 假設非零元個數的最大值為12500
typedef struct {
int i, j; // 該非零元的行下標和列下標
ElemType e; //非零元素的值
} Triple; // 三元組型別
typedef union { //共用體
Triple data[MAXSIZE + 1]; // 
非零元三元組表,data[0]未用
int mu, nu, tu; // 矩陣的行數、列數和非零元個數
} TSMatrix; // 稀疏矩陣型別
(2) 求轉置矩陣的操作
用常規的二維陣列表示時的演算法
for (col=1; col<=nu; ++col)
for (row=1; row<=mu; ++row)
T[col][row] = M[row][col];
其時間複雜度為: O(mu×nu)
 用三元組順序表表示時的快速轉置演算法
Status FastTransposeSMatrix(TSMatrix M, TSMatrix &T) {
// 採用三元組順序表儲存表示,求稀疏矩陣M的轉置矩陣T

T.mu = M.nu; T.nu = M.mu; T.tu = M.tu;
if (T.tu) {
for (col=1; col<=M.nu; ++col) num[col] = 0;
for (t=1; t<=M.tu; ++t) ++num[M.data[t].j];// 求 M 中每一列所含非零元的個數
cpot[1] = 1;
for (col=2; col<=M.nu; ++col) cpot[col] = cpot[col-1] + num[col-1];
// 求 M 中每一列的第一個非零元在 b.data 中的序號
for (p=1; p<=M.tu; ++p) { // 轉置矩陣元素
col = M.data[p].j; q = cpot[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; ++cpot[col]; 
} // for
} // if
return OK;
} // FastTransposeSMatrix
其時間複雜度為: O(mu +nu) 

3.行邏輯聯接的順序表——壓縮儲存稀疏矩陣方法之二(連結儲存結構)


行邏輯聯接的順序表:
稀疏矩陣中為了隨機存取任意一行的非0元素,需要知道每一行的第一個非0元素在三元組表中的位置,因此將上述快速轉置演算法中指示行資訊的輔助陣列cpot固定在稀疏矩陣的儲存結構中,讓每一行對應一個單鏈表,每個單鏈表都有一個表頭指標,這種“帶行連結資訊”的三元組表即稱為行邏輯聯接的順序表。
(1)行邏輯聯接的順序表的表示法
#define MAXMN 500 // 假設矩陣行數和列數的最大值為500
typedef struct {
Triple data[MAXSIZE + 1]; // 非零元三元組表,data[0]未用
int rpos[MAXMN + 1]; // 指示各行第一個非零元的位置
int mu, nu, tu; // 矩陣的行數、列數和非零元個數
} RLSMatrix; // 行邏輯連結順序表型別
(2) 求矩陣乘法的操作
 矩陣乘法的精典演算法:
for (i=1; i<=m1; ++i)
for (j=1; j<=n2; ++j) {
Q[i][j] = 0;
for (k=1; k<=n1; ++k) Q[i][j] += M[i][k] * N[k][j];
}
其時間複雜度為:O(m1 n1 n2)
 用行邏輯聯接的順序表表示時的矩陣乘法
Status MultSMatrix(RLSMatrix M, RLSMatrix N, RLSMatrix &Q) {
//求矩陣乘積Q=M*N,採用行邏輯連結儲存表示。
if (M.nu != N.mu) return ERROR;
Q.mu = M.mu; Q.nu = N.nu; Q.tu = 0; // Q初始化
if (M.tu*N.tu != 0) { // Q是非零矩陣
for (arow=1; arow<=M.mu; ++arow) { // 處理M的每一行
ctemp[] = 0; // 當前行各元素累加器清零
Q.rpos[arow] = Q.tu+1; 
if (arow<M.mu) tp=M.rpos[arow+1];
else {tp=M.tu+1}
for (p=M.rpos[arow]; p<M.rpos[arow+1];++p) { 
//對當前行中每一個非零元找到對應元在N中的行號
brow=M.data[p].j; 
if (brow < N.nu ) t = N.rpos[brow+1];
else { t = N.tu+1 }
for (q=N.rpos[brow]; q< t; ++q) {
ccol = N.data[q].j; // 乘積元素在Q中列號
ctemp[ccol] += M.data[p].e * N.data[q].e;
} // for q
} // 求得Q中第crow( =arow)行的非零元
for (ccol=1; ccol<=Q.nu; ++ccol) // 壓縮儲存該行非零元
if (ctemp[ccol]) {
if (++Q.tu > MAXSIZE) return ERROR;
Q.data[Q.tu] = {arow, ccol, ctemp[ccol]};
} // if
} // for arow
} // if 
return OK;
} // MultSMatrix
上述演算法的時間複雜度分析
 累加器ctemp初始化的時間複雜度為O (M.mu x N.mu)
求Q的所有非零元的時間複雜度為O (M.tu x N.tu/N.mu)
 進行壓縮儲存的時間複雜度為O (M.mu x N.nu)
總的時間複雜度就是O (M.mu x N.nu + M.tu x N.tu/N.mu)。
若M是m行n列的稀疏矩陣,N是n行p列的稀疏矩陣,則M中非零元的個數 M.tu = d M x m x n,N中非零元的個數 N.tu = d N x n x p,相乘演算法的時間複雜度就是 O (m x p x(1+nd Md N)) ,當d M<0.05 和d N<0.05及 n <1000時,相乘演算法的時間複雜度就相當於 O (mxp)
顯然,這是一個相當理想的結果。如果事先能估算出所求乘積矩陣Q不再是稀疏矩陣,則以二維陣列表示Q,相乘的演算法也就更簡單了。 

4. 十字連結串列——壓縮儲存稀疏矩陣方法之三(連結儲存結構)


(1) 基本概念

十字連結串列:是既帶行指標又帶列指標的連結儲存方式,每個三元組結點處於所在行單鏈表與列單鏈表的交點處,當矩陣的非零元個數和位置在操作過程中變化較大時,用這種儲存結構更為恰當。
在十字連結串列中,每個非零元可用一個含五個域的結點表示,其中 i, j 和e 三個域分別表示該非零元所在的行、列和非零元的值,向右域 right 用以連結同一行中下一個非零元,向下域down 用以連結同一列中下一個非零元。同一行的非零元通過 right 域連結成一個線性連結串列,同一列的非零元通過 down 域連結成一個線性連結串列,每個非零元既是某個行連結串列中的一個結點,又是某個列連結串列中的一個結點,整個矩陣構成了一個十字交叉的連結串列,故稱這樣的儲存結構為十字連結串列,可用兩個分別儲存行連結串列的頭指標和列連結串列的頭指標的一維陣列表示之。例如:矩陣M的十字連結串列如下圖所示。

  假設非空指標 pa和 pb分別指向矩陣A和B中行值相同的兩個結點,pa ==NULL 表明矩陣A在該行中沒有非零元,則上述四種情況的處理過程為:
(1) 若pa==NULL或pa->j 〉pb->j,則需要在A矩陣的連結串列中插入一個值為bi,j的結點。此時,需  改變同一行中前一結點的right域值,以及同一列中前一結點的down域值。
(2) 若pa->j〈 pb->j,則只要將pa指標往右推進一步。
(3) 若pa->j == pb->j且pa->e+pb->e !=0,則只要將ai,j+bi,j 的值送到pa所指結點的e域即  可,其它所有域的值都不變。
(4) 若pa->j == pb->j且pa->e+pb->e == 0,則需要在A矩陣的連結串列中刪除pa所指的結點。此時  ,需改變同一行中前一結點的right域值,以及同一列中前一結點的down域值。
  為了便於插入和刪除結點,還需要設立一些輔助指標。其一是,在A的行連結串列上設pre指標,指  示pa所指結點的前驅結點;其二是,在A的每一列的連結串列上設一個指標hl[j],它的初值和列鏈  表的頭指標相同,即hl[j]=chead[j]。
(2) 稀疏矩陣的十字連結串列表示與建立的演算法 (P:104)
(3) 兩個矩陣相加的演算法描述

(1) 初始令pa和pb分別指向A和B的第一行的第一個非零元素的結點,即
  pa=A.rhead[1]; pb=B.rhead[1]; pre = NULL;
  且令hl初始化 for (j=1; j<=A.nu; ++j) hl[j]=A.chead[j];
(2) 重複本步驟,依次處理本行結點,直到B的本行中無非零元素的結點,即pb==NULL為止:
① 若pa==NULL或pa->j〉pb->j(即A的這一行中非零元素已處理完),則需在A中插入一個pb所指  結點的複製結點。假設新結點的地址為p,則A的行表中的指標作如下變化:
if pre == NULL rhead[p->i]=p;
else { pre->right=p; }
p->right=pa; pre = p;

A的列連結串列中的指標也要作相應的改變。首先需從hl[p->j]開始找到新結點在同一列中的前驅結點,並讓hl[p->j]指向它,然後在列連結串列中插入新結點:
if chead[p->j] == NULL 
{ chead[p->j] = p; p->down = NULL; }
else { 
p->down=hl[p->j]->down; hl[p->j]->down=p; }
hl[p->j] = p;
② 若pa->j〈pb->j且pa->j!=0,則令pa指向本行下一個非零元結點,即 pre=pa; pa=pa->right;
③ 若pa->j == pb->j,則將B中當前結點的值加到A中當前結點上,即
pa->e+=pb->e;

此時若pa->e!=0,則指標不變,否則刪除A中該結點,即行表中指標變為:
if pre == NULL rhead[pa->i] = pa->right;
else { pre->right=pa->right; }
p=pa; pa=pa->right; 

同時,為了改變列表中的指標,需要先找到同一列中的前驅結點,且讓hl[pa->j]指向該結點,然後如下修改相應指標:
if chead[p->j] == p
chead[p->j] = hl[p->j] = p->down; 
else { hl[p->j]->down=p->down; }
free (p);
(3) 若本行不是最後一行,則令pa和pb指向下一行的第一個非零元結點,轉(2);否則結束。
此演算法時間複雜度:O(ta+tb)