本來准備昨天下午寫的,但是因為去參加360眾測靶場的考核耽擱了,靶場的題目還是挺基礎的。
繼續學習吧。
使用黑色墨水在白紙上簽名就像由像素點構成的稀疏矩陣。如圖4所示。
圖4 手寫體簽名
【問題】請將以下稀疏點陣信息用三元組表進行存儲,並:
|
* |
* |
* |
* |
* |
* |
* |
|
|
* |
|
|
|
|
|
|
|
* |
|
* |
|
|
|
|
|
|
|
* |
|
|
|
|
|
|
|
|
* |
|
|
|
|
|
|
|
|
* |
|
|
|
|
|
|
|
|
* |
|
|
|
|
|
|
|
|
* |
|
|
|
|
|
|
|
|
* |
|
|
|
|
|
|
|
|
* |
|
|
|
|
|
|
|
|
* |
|
|
|
|
|
|
|
|
* |
* |
* |
* |
* |
* |
* |
* |
* |
* |
(1)用稀疏矩陣快速轉置法對該矩陣進行轉置。轉置前后的三元組表均以行序為主序。
(2) 以陣列形式輸出轉置前后的稀疏矩陣,如圖5所示。
圖5 (a)轉置前 (b)轉置后
先普及一下稀疏矩陣的概念:
簡單理解稀疏矩陣就是元素大部分為零的矩陣,在實際生活中我們遇到的大型稀疏矩陣,如果按照常規的儲存方法,就會造成大量空間的浪費,而且在訪問和操作的時候也會造成大量時間上的浪費。三元組表就是為了解決這一問題而產生的解決方案之一。
稀疏矩陣由於其自身的稀疏特性,通過壓縮可以大大節省稀疏矩陣的內存代價。具體操作是:將非零元素所在的行、列以及它的值構成一個三元組(i,j,v),然后再按某種規律存儲這些三元組,這種方法可以節約存儲空間,而這些三元組的集合,就是三元組表。
我們一步步來,將問題分解為一個個小模塊,先將稀疏矩陣存儲在三元組表中
因為C語言中沒有三元組這種數據類型,所以我們先使用typedef定義三元組表:
typedef struct{ int i,j,val; }NODE;
i,j,val分別表示三元組表的行,列以及非零元素的值。
這里的需要儲存的稀疏矩陣也一起定義了
int nums[11][10]={ {0,1,1,1,1,1,1,1,0,0}, {1,0,0,0,0,0,0,0,1,0}, {1,0,0,0,0,0,0,0,1,0}, {0,0,0,0,0,0,0,1,0,0}, {0,0,0,0,0,0,1,0,0,0}, {0,0,0,0,0,1,0,0,0,0}, {0,0,0,0,1,0,0,0,0,0}, {0,0,0,1,0,0,0,0,0,0}, {0,0,1,0,0,0,0,0,0,0}, {0,1,0,0,0,0,0,0,0,0}, {1,1,1,1,1,1,1,1,1,1} };
這里有一個問題是:
將稀疏矩陣存儲到三元組表中時,需要記錄稀疏矩陣的行列值嗎?
答案是肯定的,如果不存儲稀疏矩陣的行列值,當遇到稀疏矩陣最后一行全部是0的情況,由稀疏矩陣得到的三元組表,是無法還原成原來的稀疏矩陣的。
在上面的稀疏矩陣中,一共有28個非零元素,行值為11,列值為10,所以我們需要申請29個三元組的儲存空間,多余的那一個儲存空間用來存儲稀疏矩陣的行,列值,以及稀疏矩陣中非零元素的個數
#define T 28 NODE matrix[T+1]; matrix[0].i=11; matrix[0].j=10; matrix[0].val=0;
用三元組表中第0個元素存儲稀疏矩陣的基礎信息
然后就到了將稀疏矩陣nums存儲進三元組表的操作,遍歷稀疏矩陣,當有元素為1的時候,將三元組表非零元素數量matrix[0].val++;然后依次將稀疏矩陣非零元素的信息存儲進三元組表中
void init_matrix(NODE* matrix){ int i,j; for(i=0;i<matrix[0].i;i++){ for(j=0;j<matrix[0].j;j++){ if(nums[i][j]==1){ matrix[0].val++; matrix[matrix[0].val].i=i; matrix[matrix[0].val].j=j; matrix[matrix[0].val].val=nums[i][j]; } } } }
存儲了之后我們將三元組表輸出還原成稀疏矩陣,看是否能正確還原
void put_matrix(NODE* matrix){ int i,j; int count=1; for(i=0;i<matrix[0].i;i++){ for(j=0;j<matrix[0].j;j++){ if(i==matrix[count].i&&j==matrix[count].j){ printf("%d",matrix[count].val); count++; }else{ printf("0"); } } printf("\n"); } }
程序運行的結果是
可以看到顯示與原稀疏矩陣相同,說明存儲入三元組表是正確的
接下來我們進行三元組表的快速轉置,先貼一張上課時候的PPT
當然如果直接看PPT的話很有可能還是一頭霧水,所以舉一個形象的例子:
一個年級有四個班的同學,一班有20人,二班有30人,三班有40人,四班有50人,全年級的同學一起到電影院看電影,進了電影院之后,告訴我們要按照班級的順序坐座位,座位只有一排,大家進電影院的時候都是跟自己熟悉的朋友一起,自然是亂序的,那我們應該怎么才能快速依次按照班級的順序坐座位呢?
有一個辦法是,大家隨便坐座位,到了位置上之后,再進行班級順序的比較,讓一班的同學坐一班的位置,二班的同學坐二班的位置等等等,但是這樣兩兩進行比較,效率未免過於低下。
另外的一個辦法是,坐座位的時候,先把人群里面的一班同學篩選出來,依次放在一班同學的位置,然后再將人群里面的二班同學篩選出來,放在一班同學的后面位置,然后。。。
思考了之后,好像沒有能夠只篩選(遍歷)一次人群就坐好座位的辦法。
當然是!有的
所以就引入快速轉置三元組表的辦法,即如果我們提前知道每個班有多少人,在遍歷人群的時候,只需要將其放在每個班開始的位置就行了。
如人群的開始班級排列是這樣的 2,3,1,4,2,2,3.。。。。第一個同學是2班的,我們將其安排在第21號座位上(假設座位號是從1開始),因為我們知道前面會有20個一班的同學要坐座位,接着二號同學是三班的,安排在51號座位,三號同學是1班的,安排在1號座位,四號同學是四班的,安排在91號座位,五號同學!又是二班的,但是原來二班的同學已經坐了21號,所以我們理所當然地坐在22號座位,后面的也是這樣,只需要遍歷一次原人群,就能將同學們按照順序坐在相應的位置上了。
但是在上面的描述中,我們需要知道每個班有多少人,以及每個班的第一個位置是在哪里(比如最開始二班同學的第一個位置是21號座位,有同學坐了21號座位之后,第一個位置自然就后移成22號座位了,等待下一個同學來坐,坐了之后再繼續后移),所以需要兩個輔助數組來存儲這兩個信息
num數組用來存儲每個班中的人數,cpot數組用來存儲每個班的第一個位置。
在三元組表快速轉置中,num[i]表示原三元組表中第i列中非零元的個數,cpot[i]表示原三元組表中第i列中第一個非零元素的在新的三元組表中的位置(cpot這一段可能有點繞,再解釋一下,因為在題目中,我們需要轉置之后的三元組表按照行主序排列,由於是轉置之后的,說明我們的順序在轉置之前,是按照列序在新的三元組表中放置,即如果新的三元組表中有(2,1),(1,2),那么(1,2)會在(2,1)的下面,因為2>1,找到其在三元組表中對應的位置后,再進行行列轉置)
接下來將新三元組表中第0元素設置好
new_matrix[0].i=matrix[0].j; new_matrix[0].j=matrix[0].i; new_matrix[0].val=matrix[0].val;
然后初始化num數組和cpot數組,這里需要提到的是兩個PPT里面也顯示了的關系,再貼一次:
cpot[i]表示原三元組表中第i列中第一個非零元素在新的三元組表中的位置,可以理解為同學們去看電影的時候,每個班在電影院座位上的最開始那個座位,比如二班的同學最開始是21,三班的同學是51,但是有一個顯而易見的事實是(別告訴我你沒看出來:-)當然沒看出來也沒事),一班的同學在座位中最開始的座位是1,這個是不需要遍歷人群,也不需要由班級人數可以確定的,而其他每個班開始的位置,是上一個班的開始位置+上一個班的人數,如21=1+20,1是一班開始的座位,20是1班的人數,三班的同學由二班的位置又可以得到51=21+30,依次類推初始化cpot
for(i=0;i<matrix[0].j;i++){ num[i]=0; } for(i=1;i<=matrix[0].val;i++){ num[matrix[i].j]++; } cpot[0]=1; for(i=1;i<matrix[0].j;i++){ cpot[i]=cpot[i-1]+num[i-1]; }
最后就是最關鍵的轉置的時候了,當然經過前面這么多的鋪墊,最后這一步已經顯得很簡單了,和大家去看電影這個例子一樣,按照步驟放位置就行了。
使用tmp變量存儲該元素屬於哪一列(哪一個班級),first變量存儲其列在新的三元組表中存儲的位置(該班級同學坐的第一個位置),然后將原三元組表中的數據轉置之后放進去就行了,最后讓這個列在新的三元組表中存儲位置后移一位(后來的該班同學只好坐在這個同學后面啦),代碼已經不是很重要了
for(i=1;i<=matrix[0].val;i++){ tmp=matrix[i].j; first=cpot[tmp]; new_matrix[first].i=matrix[i].j; new_matrix[first].j=matrix[i].i; new_matrix[first].val=matrix[i].val; cpot[tmp]++; }
分析完了之后是不是感覺也沒那么難了呢,其實關鍵的代碼只有那么幾處,慢慢分析是可以縷清思路的。
轉置后輸出結果為:
完整代碼如下:
#include<stdio.h> #include<stdlib.h> #define MAX 111 int nums[11][10]={ {0,1,1,1,1,1,1,1,0,0}, {1,0,0,0,0,0,0,0,1,0}, {1,0,0,0,0,0,0,0,1,0}, {0,0,0,0,0,0,0,1,0,0}, {0,0,0,0,0,0,1,0,0,0}, {0,0,0,0,0,1,0,0,0,0}, {0,0,0,0,1,0,0,0,0,0}, {0,0,0,1,0,0,0,0,0,0}, {0,0,1,0,0,0,0,0,0,0}, {0,1,0,0,0,0,0,0,0,0}, {1,1,1,1,1,1,1,1,1,1} }; typedef struct{ int i,j,val; }NODE; #define T 28 void put_matrix(NODE* matrix){ int i,j; int count=1; for(i=0;i<matrix[0].i;i++){ for(j=0;j<matrix[0].j;j++){ if(i==matrix[count].i&&j==matrix[count].j){ printf("%d",matrix[count].val); count++; }else{ printf("0"); } } printf("\n"); } } void init_matrix(NODE* matrix){ int i,j; for(i=0;i<matrix[0].i;i++){ for(j=0;j<matrix[0].j;j++){ if(nums[i][j]==1){ matrix[0].val++; matrix[matrix[0].val].i=i; matrix[matrix[0].val].j=j; matrix[matrix[0].val].val=nums[i][j]; } } } } void put_nums(){ int i,j; for(i=0;i<11;i++){ for(j=0;j<10;j++){ printf("%d",nums[i][j]); } printf("\n"); } } void reverse_matrix(NODE* matrix,NODE* new_matrix){ int num[matrix[0].j],cpot[matrix[0].j]; int tmp,first,i; new_matrix[0].i=matrix[0].j; new_matrix[0].j=matrix[0].i; new_matrix[0].val=matrix[0].val; for(i=0;i<matrix[0].j;i++){ num[i]=0; } for(i=1;i<=matrix[0].val;i++){ num[matrix[i].j]++; } cpot[0]=1; for(i=1;i<matrix[0].j;i++){ cpot[i]=cpot[i-1]+num[i-1]; } //轉置存儲 for(i=1;i<=matrix[0].val;i++){ tmp=matrix[i].j; first=cpot[tmp]; new_matrix[first].i=matrix[i].j; new_matrix[first].j=matrix[i].i; new_matrix[first].val=matrix[i].val; cpot[tmp]++; } } int main(){ int i,j; NODE matrix[T+1],new_matrix[T+1]; matrix[0].i=11; matrix[0].j=10; matrix[0].val=0; printf("儲存入三元組表前:\n"); put_nums(); printf("初始化三元組表..."); init_matrix(matrix); printf("\n使用三元組表按行主序進行儲存后:\n"); put_matrix(matrix); printf("正在轉置..."); reverse_matrix(matrix,new_matrix); printf("\n轉置后的結果為:\n"); put_matrix(new_matrix); return 0; }
以上
matrix[