一,相關概念
㈠特殊矩陣:矩陣中存在大多數值相同的元,或非0元,且在矩陣中的分布有一定規律。
⒈對稱矩陣:矩陣中的元素滿足
aij=aji 1≤i,j≤n
⒉三角矩陣:上(下)三角矩陣指矩陣的下(上)三角(不包括對角線)中的元素均為常數c或0的n階矩陣。
⒊對角矩陣(帶狀矩陣):矩陣中所有非0元素集中在主對角線為中心的區域中。
㈡稀疏矩陣:非0元素很少(≤ 5%)且分布無規律。
二,存儲結構
1、對稱矩陣
存儲分配策略: 每一對對稱元只分配一個存儲單元,即只存儲下三角(包括對角線)的元, 所需空間數為: n(n+1)/2。
存儲分配方法: 用一維數組sa[n(n+1)/2]作為存儲結構。
2、三角矩陣
也是一個n階方陣,有上三角和下三角矩陣。下(上)三角矩陣是主對角線以上(下)元素均為零的n階矩陣。設以一維數組sb[0..n(n+1)/2]作為n階三角矩陣B的存儲結構,仍采用按行存儲方案,則B中任一元素bi,j和sb[k]之間仍然有如上的對應關系,只是還需要再加一個存儲常數c的存儲空間即可。如在下三角矩陣中,用n(n+1)/2的位置來存儲常數
對特殊矩陣的壓縮存儲實質上就是將二維矩陣中的部分元素按照某種方案排列到一維數組中,不同的排列方案也就對應不同的存儲方案
3、稀疏矩陣
常見的有三元組表示法、帶輔助行向量的二元組表示法(也即行邏輯鏈表的順序表),十字鏈表表示法等。
三、存儲結構及C語言描述
1、三元組表示法
快速轉置:按照a.data中三元組的次序進行轉置,並將轉置后的三元組放到b.data中的恰當位置。
恰當位置的確定:首先計算M矩陣的每一列(即T的每一行)中非0元的個數,然后求得M矩陣每一列第一個非0元在b.data中的位置。
三元組表示代碼:
#include "stdio.h" #include "stdlib.h" #define MAXSIZE 12500 #define OK 1 typedef int ElemType; typedef struct { int i,j; ElemType e; }Triple; typedef struct { Triple data[MAXSIZE+1]; int mu,nu,tu; //矩陣行數,列數和非0元個數 }TSMatrix; int cpot[MAXSIZE+1],num[MAXSIZE+1]; int TransposeSMatrix(TSMatrix M,TSMatrix &T) { T.mu=M.nu; T.nu=M.mu; T.tu=M.tu; if(T.tu) { int q=1; for(int col=1;col<=M.nu;++col) for(int p=1;p<=M.tu;++p) 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; }//if }//if return OK; }//TransposeSMatrix int InPutM(TSMatrix &M) { printf("input nu mu tu(With a space interval)of a Matrix:\n"); scanf("%d %d %d",&M.nu,&M.mu,&M.tu); //row,colume,and tu printf("Please input the data of Matrix:\n"); for(int c=1;c<=M.tu;c++) { scanf("%d",&M.data[c].i); scanf("%d",&M.data[c].j); scanf("%d",&M.data[c].e); }//for return 1; }//InPut int PrintM(TSMatrix T) { printf("Matrix after transpose is:\n"); for(int c=1;c<=T.tu;c++) { printf("%d %d %d\n",T.data[c].i,T.data[c].j,T.data[c].e); }//for return 1; }//InPut int FastTransposeSMatrix(TSMatrix M,TSMatrix &T) { T.mu=M.nu; T.nu=M.mu; T.tu=M.tu; if(T.tu) { for(int col=1;col<=M.mu;++col) num[col]=0; for(int t=1;t<=M.tu;++t) ++num[M.data[t].j]; //記述M.data[t].j列 //非0元的個數 cpot[1]=1; //求第col列中第一個非零元在b.data(T)中的序號 for(int col=2;col<=M.mu;++col) cpot[col]=cpot[col-1]+num[col-1]; for(int p=1;p<=M.tu;++p) { int col=M.data[p].j; int 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 int main() { TSMatrix M,T; InPutM(M); //TransposeSMatrix(M,T); FastTransposeSMatrix(M,T); PrintM(T); return OK; }
2,行邏輯連接的順序表
上面我們討論的是稀疏矩陣的轉置,現在我們討論稀疏矩陣的相乘,相加,相減。如果預先知道不是稀疏矩陣,則用二維數組相乘法就可以了。如果是,則我們用來存儲矩陣的結構稱之為行邏輯連接的順序表,就是加入一個行表來記錄稀疏矩陣中每行的非零元素在三元組表中的起始位置。
帶行邏輯鏈接的順序表實現代碼:
#include "stdio.h" #include "stdlib.h" #define MAXSIZE 12500 #define MAXRC 100 #define OK 1 #define ERROR -1 typedef int ElemType; typedef int Status; typedef struct { int i,j; ElemType e; }Triple; typedef struct { Triple data[MAXSIZE+1]; int rpos[MAXRC+1];//各行第一個非零元的位置表 int mu,nu,tu; //矩陣行數,列數和非0元個數 }RLSMatrix; int cpot[MAXSIZE+1],num[MAXSIZE+1]; Status CreateSMatrix(RLSMatrix &M) { // 創建稀疏矩陣M int i; Triple T; Status k; printf("請輸入矩陣的行數,列數,非零元素數:(Separated by commas)\n"); scanf("%d,%d,%d",&M.mu,&M.nu,&M.tu); M.data[0].i=0; // 為以下比較做准備 for(i=1;i<=M.tu;i++) { do { printf("請按行序順序輸入第%d個非零元素所在的行(1~%d),列(1~%d),元素值:",i,M.mu,M.nu); printf("the 3 num Separated by commas\n"); scanf("%d,%d,%d",&T.i,&T.j,&T.e); k=0; if(T.i<1||T.i>M.mu||T.j<1||T.j>M.nu) // 行、列超出范圍 k=1; if(T.i<M.data[i-1].i||T.i==M.data[i-1].i&&T.j<=M.data[i-1].j) // 沒有按順序輸入非零元素 k=1; //&&優先級比||高 }while(k); // 當輸入有誤,重新輸入 M.data[i]=T; } for(i=1;i<=M.tu;i++) // 計算rpos[] if(M.data[i].i>M.data[i-1].i) for(T.i=0;T.i<M.data[i].i-M.data[i-1].i;T.i++) M.rpos[M.data[i].i-T.i]=i; for(i=M.data[M.tu].i+1;i<=M.mu;i++) // 給最后沒有非零元素的幾行賦值 M.rpos[i]=M.tu+1; return OK; }//CreateSMatrix void DestroySMatrix(RLSMatrix &M) { // 銷毀稀疏矩陣M(使M為0行0列0個非零元素的矩陣) M.mu=0; M.nu=0; M.tu=0; }//DestroySMatrix void PrintSMatrix(RLSMatrix M) { // 輸出稀疏矩陣M int i; printf("%d行%d列%d個非零元素。\n",M.mu,M.nu,M.tu); printf("行 列 元素值\n"); for(i=1;i<=M.tu;i++) printf("%2d%4d%8d\n",M.data[i].i,M.data[i].j,M.data[i].e); for(i=1;i<=M.mu;i++) printf("第%d行的第一個非零元素是本矩陣第%d個元素\n",i,M.rpos[i]); }//PrintSMatrix Status MultSMatrix(RLSMatrix M,RLSMatrix N,RLSMatrix &Q) { // 求稀疏矩陣乘積Q=M*N。 int arow,brow,p,q,ccol,ctemp[MAXRC+1]; if(M.nu!=N.mu) // 矩陣M的列數應和矩陣N的行數相等 return ERROR; Q.mu=M.mu; // Q初始化 Q.nu=N.nu; Q.tu=0; M.rpos[M.mu+1]=M.tu+1; // 為方便后面的while循環臨時設置 N.rpos[N.mu+1]=N.tu+1; if(M.tu*N.tu!=0) // M和N都是非零矩陣 { for(arow=1;arow<=M.mu;++arow) { //從M的第一行開始,到最后一行,arow是M的當前行 for(ccol=1;ccol<=Q.nu;++ccol) ctemp[ccol]=0; //Q的當前行的各列元素累加器清零 Q.rpos[arow]=Q.tu+1; //Q當前行的第1個元素位於上1行最后1個元素之后 for(p=M.rpos[arow];p<M.rpos[arow+1];++p) { // 對M當前行中每一個非零元 brow=M.data[p].j; //找到對應元在N中的行號(M當前元的列號) for(q=N.rpos[brow];q<N.rpos[brow+1];++q) { ccol=N.data[q].j; //乘積元素在Q中列號 ctemp[ccol]+=M.data[p].e*N.data[q].e; }//for } //求得Q中第arow行的非零元 for(ccol=1;ccol<=Q.nu;++ccol) //壓縮存儲該行非零元 if(ctemp[ccol]) { if(++Q.tu>MAXSIZE) return ERROR; Q.data[Q.tu].i=arow; Q.data[Q.tu].j=ccol; Q.data[Q.tu].e=ctemp[ccol]; }//if }//for }//if return OK; }//MultSMatrix int main() { RLSMatrix M,N,Q; CreateSMatrix(M); CreateSMatrix(N); MultSMatrix(M,N,Q); PrintSMatrix(Q); DestroySMatrix(M); DestroySMatrix(N); DestroySMatrix(Q); return OK; }
3,十字鏈表表示法
我們知道稀疏矩陣的三元組存儲方式的實現很簡單,每個元素有三個域分別是i, j, e。代表了該非零元的行號、列號以及值。那么在十字鏈表的存儲方式下,首先這三個域是肯定少不了的,不然在進行很多操作的時候都要自己使用計數器,很麻 煩。而十字鏈表的形式大家可以理解成每一行是一個鏈表,而每一列又是一個鏈表。如圖所示:
通過上面的圖我們可以知道,每個結點不止要存放i, j, e。還要存放它橫向的下一個結點的地址以及縱向的下一個結點的地址。形成一個類似十字形的鏈表的結構。那么每個結點的結構體定義也就呼之欲出了
這樣我們對結點的插入與刪除就要修改兩個指針域。為了方便我們對結點的操作,我們要創建頭指針或者頭結點。至於到底是選擇頭指針呢還是頭結點,請繼續看下去..
我們想下要怎么創建頭指針或者頭結點,我們可以創建OLNode結構的結點形成一段連續的地址空間來指向某一行或者某一列中的結點(這是我最初的想法)
或者我們創建指針數組,數組元素中存放的地址就是某一行或者某一列的第一個結點的地址。來分析下兩種方法
第一種方法會浪費大量的空間,而因為指針變量的空間都是4個字節,所以相對來說第二種節省空間。
毫無疑問我們選擇第二種,也就是創建頭指針。那么第二種我們用什么來實現?是數組還是動態內存分配?如果用數組我們要預先定義行和列的最大值,顯然 這不是一個好主意,而動態內存分配的方法我們可以在用戶輸入了行數與列數之后分配相應的一段地址連續的空間。更為靈活,所以我們選擇動態內存分配。
typedef struct { OLink *Rhead, *Chead; int mu, nu, tu; // 稀疏矩陣的行數、列數和非零元個數 }CrossList;
注意Rhead與Chead的類型,它們是指向指針的指針,也就是說,它們是指向我們定義的OLNode結構的結點的指針的指針。這話有點繞,不過相信C學的不錯的朋友都應該清楚。如果不清楚的請看這個帖子 http://topic.csdn.net/u/20110829/01/506b33e3-ebc9-4905-bf8d-d0c877f85c08.html
現在結構體已經定義好了,我們來想想下面應該干什么。首先需要用戶輸入稀疏矩陣的行數與列數以及非零元的個數。那么就需要定義一個CrossList的結構體變量來存儲這些值。
int main(void) { CrossList M; CreateSMatrix(&M); }
CreatSMatrix函數是我們今天要創建的函數,它用來建立稀疏矩陣並使用十字鏈表的方式存儲矩陣。該函數的原型為int CreateSMatrix(CrossList *M);
當我們創建好了M就需要用戶輸入了,那么就要對用戶的輸入進行檢查,看是否符合要求,首先mu, nu, tu都不能小於0,並且mu, nu不能等於0(我們這里假設行號與列號都是從1開始的,所以不能等於0),tu的值必須在0與mu * nu之間。
int CreateSMatrix(CrossList *M) { int i, j, m, n, t; int k, flag; ElemType e; OLNode *p, *q; if (M->Rhead) DestroySMatrix(M); do { flag = 1; printf("輸入需要創建的矩陣的行數、列數以及非零元的個數"); scanf("%d%d%d", &m, &n, &t); if (m<0 || n<0 || t<0 || t>m*n) flag = 0; }while (!flag); M->mu = m; M->nu = n; M->tu = t; ................................... return 1; }
當用戶輸入了正確的值以后,我們要創建頭指針的數組
這里m+1與n+1是為了后面操作的方便使得它們的下標從1開始。注意我們創建時候的強制轉換的類型。OLink * , 首先它是指針類型。我們連續創建了m+1個,每一個都指向OLink類型的變量,所以它里面存放的就應該是一個指向OLNode類型的指針的地址。
創建完以后必須初始化,因為我們后面的插入就其中一個判斷條件就是它們的值為NULL,也就是該行或者該列中沒有結點
現在我們就可以進行結點的輸入了,顯而易見的要輸入的結點個數剛才已經存放到了t變量中,那么我們要創建t個結點。這就是一個大的循環
而每創建一個結點我們都要修改它的兩個指針域以及鏈表頭數組。那么我們可以分開兩次來修改,第一次修改行的指針域,第二次修改列的指針域。
do { flag = 1; printf("輸入第%d個結點行號、列號以及值", k); scanf("%d%d%d", &i, &j, &e); if (i<=0 || j<=0) flag = 0; }while (!flag); p = (OLink) malloc (sizeof(OLNode)); if (NULL == p) exit(-1); p->i = i; p->j = j; p->e = e;
當用戶輸入一系列正確的值,並且我們也創建了一個OLNode類型的結點之后我們要講它插入到某一行中。首先要確定插入在哪一行?我們輸入的時候已經輸入了行號i,那么我們自然要插入到i行中,那么應該怎樣去插入?分兩種情況
1、當這一行中沒有結點的時候,那么我們直接插入
2、當這一行中有結點的時候我們插入到正確的位置
逐個來分析:
怎么判定一行中有沒有結點? 記得我們前面對Rhead的初始化嗎? 所有的元素的值都為NULL,所以我們的判斷條件就是 NULL==M->Rhead[i].
現在我們來解決第二個問題。
怎么去找到要插入的正確位置。當行中有結點的時候我們無非就是插入到某個結點之前或者之后。那么我們再回到前面,在我們定義Rhead的時候就說 過,某一行的表頭指針指向的就是該行中第一個結點的地址。我們假設該行中已經有了一個結點我們稱它為A結點,如果要插在A結點之前那么A結點的列號必定是 大於我們輸入的結點(我們稱它為P結點)的列號的。我們的插入操作就要修改頭指針與p結點的right域。就像鏈表中的插入。那么當該行中沒有結點的時候 我們怎么去插入?同樣是修改頭指針讓它指向我們的P結點,同樣要修改P結點的right域。看,我們可以利用if語句來實現這兩種條件的判斷。那么就有了 下面的代碼!
if(NULL==M->Rhead[i] || M->Rhead[i]->j>j) { // p插在該行的第一個結點處 // M->Rhead[i]始終指向該行的第一個結點 p->right = M->Rhead[i]; M->Rhead[i] = p; }
現在我們再想一下怎么去插入到某一個結點的后面? 我們新創建的P結點要插入到現有的A結點的后面,那么P的列號必定是大 於A的列號,那么我們只要找到第一個大於比P的列號大的結點B,然后插入到B結點之前!如果現有的結點沒有一個結點列號是大於P結點的列號的,那么我們就 應該插入到最后一個結點之后!所以我們首先要尋找符合條件的位置進行插入
for(q=M->Rhead[i]; q->right && q->right->j < j; q=q->right) ; p->right=q->right; // 完成行插入 q->right=p;
這樣就插入完成了,至於列的指針域的修改和這個類似!現在貼出所有代碼
int CreateSMatrix(CrossList *M) { int i, j, m, n, t; int k, flag; ElemType e; OLNode *p, *q; if (M->Rhead) DestroySMatrix(M); do { flag = 1; printf("輸入需要創建的矩陣的行數、列數以及非零元的個數"); scanf("%d%d%d", &m, &n, &t); if (m<0 || n<0 || t<0 || t>m*n) flag = 0; }while (!flag); M->mu = m; M->nu = n; M->tu = t; //創建行鏈表頭數組 M->Rhead = (OLink *)malloc((m+1) * sizeof(OLink)); if(!M->Rhead) exit(-1); //創建列鏈表頭數組 M->Chead = (OLink *)malloc((n+1) * sizeof(OLink)); if(!(M->Chead)) exit(-1); for(k=1;k<=m;k++) // 初始化行頭指針向量;各行鏈表為空鏈表 M->Rhead[k]=NULL; for(k=1;k<=n;k++) // 初始化列頭指針向量;各列鏈表為空鏈表 M->Chead[k]=NULL; //輸入各個結點 for (k=1; k<=t; ++k) { do { flag = 1; printf("輸入第%d個結點行號、列號以及值", k); scanf("%d%d%d", &i, &j, &e); if (i<=0 || j<=0) flag = 0; }while (!flag); p = (OLink) malloc (sizeof(OLNode)); if (NULL == p) exit(-1); p->i = i; p->j = j; p->e = e; if(NULL==M->Rhead[i] || M->Rhead[i]->j>j) { // p插在該行的第一個結點處 // M->Rhead[i]始終指向它的下一個結點 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 1; }