剛才終於完完全全、徹徹底底的搞明白了疏鬆陣列十字鏈表的儲存方式的實現與該演算法的思想。我覺得有必要把自己的思路記下來,一呢等自己將來忘記了可以回過頭來看,二呢希望與我一樣對該儲存方式迷惑的朋友可以通過我的文章得到一點點的啟示。現在進入正題。
我們知道疏鬆陣列的三元組儲存方式的實現很簡單,每個元素有三個域分別是i, j, e。代表了該非零元的行號、列號以及值。那麼在十字鏈表的儲存方式下,首先這三個域是肯定少不了的,不然在進行很多操作的時候都要自己使用計數器,很麻煩。而十字鏈表的形式大家可以理解成每一行是一個鏈表,而每一列又是一個鏈表。:
通過上面的圖我們可以知道,每個結點不止要存放i, j, e。還要存放它橫向的下一個結點的地址以及縱向的下一個結點的地址。形成一個類似十字形的鏈表的結構。那麼每個結點的結構體定義也就呼之欲出了
typedef struct OLNode { int i, j; //行號與列號 ElemType e; //值 struct OLNode *right, *down; //指標域 }OLNode, *OList;
這樣我們對結點的插入與刪除就要修改兩個指標域。為了方便我們對結點的操作,我們要建立頭指標或者頭結點。至於到底是選擇頭指標呢還是頭結點,請繼續看下去..
我們想下要怎麼建立頭指標或者頭結點,我們可以建立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->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);
這裡m+1與n+1是為了後面操作的方便使得它們的下標從1開始。注意我們建立時候的強制轉換的類型。OLink * , 首先它是指標類型。我們連續建立了m+1個,每一個都指向OLink類型的變數,所以它裡面存放的就應該是一個指向OLNode類型的指標的地址。
建立完以後必須初始化,因為我們後面的插入就其中一個判斷條件就是它們的值為NULL,也就是該行或者該列中沒有結點
for(k=1;k<=m;k++) // 初始化行頭指標向量;各行鏈表為空白鏈表 M->Rhead[k]=NULL; for(k=1;k<=n;k++) // 初始化列頭指標向量;各列鏈表為空白鏈表 M->Chead[k]=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; }