稀疏矩陣的定義
對於那些零元素數目遠遠多於非零元素數目,並且非零元素的分布沒有規律的矩陣稱為稀疏矩陣(sparse)。
人們無法給出稀疏矩陣的確切定義,一般都只是憑個人的直覺來理解這個概念,即矩陣中非零元素的個數遠遠小於矩陣元素的總數,並且非零元素沒有分布規律。
稀疏矩陣的壓縮存儲
由於稀疏矩陣中非零元素較少,零元素較多,因此可以采用只存儲非零元素的方法來進行壓縮存儲。
由於非零元素分布沒有任何規律,所以在進行壓縮存儲的時侯需要存儲非零元素值的同時還要存儲非零元素在矩陣中的位置,即非零元素所在的行號和列號,也就是在存儲某個元素比如aij的值的同時,還需要存儲該元素所在的行號i和它的列號j,這樣就構成了一個三元組(i,j,a[i][j])的線性表。
三元組可以采用順序表示方法,也可以采用鏈式表示方法,這樣就產生了對稀疏矩陣的不同壓縮存儲方式。
稀疏矩陣的存儲方式
1.稀疏矩陣的三元組表示
若把稀疏矩陣的三元組線性表按順序存儲結構存儲,則稱為稀疏矩陣的三元組順序表。
順序表中除了存儲三元組外,還應該存儲矩陣行數、列數和總的非零元素數目,這樣才能唯一的確定一個矩陣。
typedef struct Triple { int row,col,e; }Triple; typedef struct TSMarix { Triple data[max+1];//data是非零元素三元組表,data[0]未用 int m,n,len;//m:矩陣的行數,n:矩陣的列數,len:非零元素的個數 }TSMarix;
稀疏矩陣的轉置
行列遞增轉置法---算法思想:
采用按照被轉置矩陣三元組表A的序列(即轉置后三元組表B中的行序)遞增的順序進行轉置,並以此送入轉置后矩陣的三元組表B中,這樣一來,轉置矩陣的三元組表B恰好是以“行序為主序”的
這種方法中,轉置后的三元組表B仍按行序遞增存放,必須多次掃描被轉置矩陣的三元組表A,以保證被轉置矩陣遞增形式進行轉置,因此要通過雙重循環來完成
#include <stdio.h> #include <stdlib.h> #define max 3 typedef struct Triple { int row,col,e; }Triple; typedef struct TSMarix { Triple data[max+1];//data是非零元素三元組表,data[0]未用 int m,n,len;//m:矩陣的行數,n:矩陣的列數,len:非零元素的個數 }TSMarix; void CreateTSMarix(TSMarix *T) { int i; printf("請輸入矩陣的行數,列數,非零元素個數:\n") ; scanf("%d%d%d",&T->m,&T->n,&T->len); if(T->m<1||T->n<1||T->len<1||T->len>=max) { printf("輸入的數據有誤!\n"); exit(1); } printf("請輸入非零元素的行下標,列下標,值(空格分開輸入):\n"); for(i=1;i<T->len+1;i++) scanf("%d%d%d",&T->data[i].row,&T->data[i].col,&T->data[i].e); } void TransposeTSMarix(TSMarix T,TSMarix *B)//B保存轉置后的矩陣 { B->n=T.m;//轉置后B的列存儲的T的行 B->m=T.n;//轉置后B的行存儲的T的列 B->len=T.len;//B的元素的個數和T的元素的個數一樣不變 int i,j,k; if(B->len>0) { j=1;//轉置后B元素中的下標 for(k=1;k<=T.m;k++)//采用被轉置矩陣的列序即轉置后矩陣的行序增序排列 { for(i=1;i<=T.len;i++) { if(T.data[i].col==k)//從頭到尾掃描表A,尋找col值為k的三元組進行轉置 { B->data[j].row=T.data[i].col; B->data[j].col=T.data[i].row; B->data[j].e=T.data[i].e; j++; } } } } } void Print(TSMarix T) { int i; printf("元素的行下標,列下標,值為:\n"); for(i=1;i<=T.len;i++) { printf("\t%d\t%d\t%d\n",T.data[i].row,T.data[i].col,T.data[i].e); } } int main() { TSMarix T,B; printf("創建三元組表:\n"); CreateTSMarix(&T); printf("輸出三元組表:\n"); Print(T); TransposeTSMarix(T,&B); printf("轉置后的矩陣B為:\n"); Print(B); return 0; }
一次快速定位轉置法---算法思想
1.待轉置矩陣三元組表A每一列非零元素的總個數,即轉置后矩陣三元組表B的每一行中非零元素的總個數
2.待轉置矩陣每一列中第一個非零元素在三元組表B中的正確位置,即轉置后矩陣每一行中的第一個非零元素在三元組表B中的正確位置,需要增設兩個數組,num[col]用來存放三元組表A第col列非零元素總個數(三元組表第col行非零元素總個數),position[col]用來存放轉置前三元組表A中第col列(轉置后三元組表B中第col行)中第一個非零元素在三元組表B中的存儲位置(下標值)。num[col]:將三元組表A掃描一遍,對於其列號為col的元素,給相應的num數組下標為col的元素加一(下標計數法);position[col]:position[1]=1,表示三元組表A中,列值為1的第一個非零元素在三元組表B中的下標值;position[col]=position[col-1]+num[col-1],其中2<=col<=A.n
通過position[col]的初始值為三元組表A中第col列(三元組表B第col行)中第一個非零元素正確的位置,當三元組表A第col列有一個元素加入到三元組表B時,則position[col]=position[col]+1,令position[col]始終指向三元組表A第col列中下一個非零元素在三元組表B中的正確存放位置。
#include <stdio.h> #include <stdlib.h> #define max 10 typedef struct { int row,col; int e; }Triple; typedef struct { Triple data[max+1]; int m,n,len; }TSMatrix; void Create(TSMatrix *T) { printf("請輸入矩陣行數,列數,非零元素個數:\n"); scanf("%d%d%d",&T->m,&T->n,&T->len); if(T->m<1||T->n<1||T->len<1||T->len>max) { printf("輸入的數據有誤!\n"); exit(1); } printf("請輸入元素的行下標,列下標,值:\n"); for(int i=1;i<T->len+1;i++) scanf("%d%d%d",&T->data[i].row,&T->data[i].col,&T->data[i].e); } void FastTranspose(TSMatrix T,TSMatrix *B) { int col,t,p,q; int num[max],position[max]; B->len=T.len; B->m=T.n; B->n=T.m; if(B->len>0) { for(col=1;col<=T.n;col++) num[col]=0; for(t=1;t<=T.len;t++)//采用數組下標計數法計算每一列非零元素的個數 num[T.data[t].col]++; position[1]=1; for(col=2;col<=T.n;col++)//求col列中第一個非零元素在B.data[]中的正確位置 position[col]=position[col-1]+num[col-1]; for(p=1;p<=T.len;p++) { col=T.data[p].col; q=position[col]; B->data[q].row=T.data[p].col; B->data[q].col=T.data[p].row; B->data[q].e=T.data[p].e; position[col]++; } } } void Print(TSMatrix T) { printf("\t行下標\t列下標\t值:\n"); for(int i=1;i<T.len+1;i++) printf("\t%d\t%d\t%d\n",T.data[i].row,T.data[i].col,T.data[i].e); } int main() { TSMatrix T,B; printf("創建三元組:\n"); Create(&T); printf("輸入的三元組為:\n"); Print(T); printf("快速排序后的三元組為:\n"); FastTranspose(T,&B); Print(B); return 0; }
十字鏈表
十字鏈表結點分為三類 :
表結點,它由五個域組成,其中i和j存儲的是結點所在的行和列,right和down存儲的是指向十字鏈表中該結點所有行和列的下一個結點的指針,v用於存放元素值。
行頭和列頭結點,這類結點也有域組成,其中行和列的值均為零,沒有實際意義,right和down的域用於在行方向和列方向上指向表結點,next用於指向下一個行或列的表頭結點。
總表頭結點,這類結點與表頭結點的結構和形式一樣,只是它的i和j存放的是矩陣的行和列數。
十字鏈表可以看作是由各個行鏈表和列鏈表共同搭建起來的一個綜合鏈表,每個結點aij既是處在第i行鏈表的一個結點,同時也是處在第j列鏈表上的一個結點,就你是處在十字交叉路口上的一個結點一樣,這就是十字鏈表的由來。
十字鏈表中的每一行和每一列鏈表都是一個循環鏈表,都有一個表頭結點。