1. 程式人生 > >矩陣加法(基於十字連結串列)及C語言程式碼實現

矩陣加法(基於十字連結串列)及C語言程式碼實現

矩陣之間能夠進行加法運算的前提條件是:各矩陣的行數和列數必須相等

在行數和列數都相等的情況下,矩陣相加的結果就是矩陣中對應位置的值相加所組成的矩陣,例如:

圖1 矩陣相加

十字連結串列法

之前所介紹的都是採用順序儲存結構儲存三元組,在類似於矩陣的加法運算中,矩陣中的資料元素變化較大(這裡的變化主要為:非0元素變為0,0變為非0元素),就需要考慮採用另一種結構——鏈式儲存結構來儲存三元組。

採用鏈式儲存結構儲存稀疏矩陣三元組的方法,稱為“十字連結串列法”

十字連結串列法表示矩陣

例如,用十字連結串列法表示矩陣 A ,為:   圖2 矩陣用十字連結串列法表示
  由此可見,採用十字連結串列表示矩陣時,矩陣的每一行和每一個列都可以看作是一個單獨的連結串列,而之所以能夠表示矩陣,是因為行連結串列和列連結串列都分別儲存在各自的陣列中 圖 2 中:儲存行連結串列的陣列稱為 rhead 陣列
;儲存列連結串列的陣列稱為 chead 陣列

十字連結串列中的結點

從圖2中的十字連結串列表示矩陣的例子可以看到,十字連結串列中的結點由 5 部分組成: 圖3 十字連結串列中的結點 指標域A儲存的是矩陣中結點所在列的下一個結點的地址(稱為 “down域”);
指標域B儲存的是矩陣中該結點所在行的下一個結點的地址(稱為 “right域”);
用結構體自定義表示為:
typedef struct OLNode
{
    int i,j,e;                      //矩陣三元組 i 代表行 j 代表列 e 代表當前位置的資料
    struct OLNode *right,*down;     //指標域 右指標 下指標
}OLNode,*OLink;

十字連結串列的結構

使用十字連結串列表示一個完整的矩陣,在瞭解矩陣中各結點的結構外,還需要儲存矩陣的行數、列數以及非 0 元素的個數,另外,還需要將各結點連結成的連結串列儲存在陣列中。

所以,採用結構體自定義十字連結串列的結構,為:
typedef struct
{
    OLink *rhead,*chead;            //存放各行和列連結串列頭指標的陣列
    int mu,nu,tu;                   //矩陣的行數,列數和非零元的個數
}CrossList;

十字連結串列儲存矩陣三元組

由於三元組儲存的是該資料元素的行標、列標和數值,所以,通過行標和列標,就能在十字連結串列中唯一確定一個位置。

判斷方法為:在同一行中通過列標來判斷位置;在同一列中通過行標來判斷位置。

首先判斷該資料元素 A(例如三元組為:(i,j,k))所在行的具體位置:
  • 如果 A 的列標 j 值比該行第一個非 0 元素 B 的 j 值小,說明該資料元素在元素 B 的左側,這時 A 就成為了該行第一個非0元素(也適用於當該行沒有非 0 元素的情況,可以一併討論)
  • 如果 A 的列標 j 比該行第一個非 0 元素 B 的 j 值大,說明 A 在 B 的右側,這時,就需要遍歷該行連結串列,找到插入位置的前一個結點,進行插入。

對應行連結串列的位置確定之後,判斷資料元素 A 在對應列的位置:
  • 如果 A 的行標比該列第一個非 0 元素 B 的行標 i 值還小,說明 A 在 B 的上邊,這時 A 就成了該列第一個非 0 元素。(也適用於該列沒有非 0 元素的情況)
  • 反之,說明 A 在 B 的下邊,這時就需要遍歷該列連結串列,找到要插入位置的上一個資料元素,進行插入。

實現程式碼:
//建立係數矩陣M,採用十字連結串列儲存表示
CrossList CreateMatrix_OL(CrossList M)
{
    int m,n,t;
    int i,j,e;
    OLNode *p,*q;//定義輔助變數
    scanf("%d%d%d",&m,&n,&t);  //輸入矩陣的行列及非零元的數量
    //初始化矩陣的行列及非零元的數量
    M.mu=m;
    M.nu=n;
    M.tu=t;
    if(!(M.rhead=(OLink*)malloc((m+1)*sizeof(OLink)))||!(M.chead=(OLink*)malloc((n+1)*sizeof(OLink))))
    {
        printf("初始化矩陣失敗");
        exit(0);      //初始化矩陣的行列連結串列
    }
    for(i=1;i<=m;i++)
    {
        M.rhead[i]=NULL;   //初始化行
    }
    for(j=1;j<=n;j++)
    {
        M.chead[j]=NULL;   //初始化列
    }
    for(scanf("%d%d%d",&i,&j,&e);0!=i;scanf("%d%d%d",&i,&j,&e)) //輸入三元組 直到行為0結束
    {
        if(!(p=(OLNode*)malloc(sizeof(OLNode))))
        {
            printf("初始化三元組失敗");
            exit(0);                 //動態生成p
        }
        p->i=i;
        p->j=j;
        p->e=e;                             //初始化p
        if(NULL==M.rhead[i]||M.rhead[i]->j>j)
        {
            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 M;
}

十字連結串列解決矩陣相加問題

在解決 “將矩陣 B 加到矩陣 A ” 的問題時,由於採用的是十字連結串列法儲存矩陣的三元組,所以在相加的過程中,針對矩陣 B 中每一個非 0 元素,需要判斷在矩陣 A 中相對應的位置,有三種情況:

  1. 提取到的 B 中的三元組在 A 相應位置上沒有非 0 元素,此時直接加到矩陣 A 該行連結串列的對應位置上;
  2. 提取到的 B 中三元組在 A 相應位置上有非 0 元素,且相加不為 0 ,此時只需要更改 A 中對應位置上的三元組的值即可;
  3. 提取到的 B 中三元組在 A 響應位置上有非 0 元素,但相加為 0 ,此時需要刪除矩陣 A 中對應結點。

提示:演算法中,只需要逐個提取矩陣 B 中的非 0 元素,然後判斷矩陣 A 中對應位置上是否有非 0 元素,根據不同的情況,相應作出處理。
設指標 pa 和 pb 分別表示矩陣 A 和矩陣 B 中同一行中的結點( pb 和 pa 都是從兩矩陣的第一行的第一個非0元素開始遍歷),針對上面的三種情況,細分為 4 種處理過程(第一種情況下有兩種不同情況):

  1. 當 pa 結點的列值 j > pb 結點的列值 j 或者 pa == NULL (說明矩陣 A 該行沒有非 0 元素),兩種情況下是一個結果,就是將 pb 結點插入到矩陣 A 中。
  2. 當 pa 結點的列值 j < pb 結點的列值 j ,說明此時 pb 指向的結點位置比較靠後,此時需要移動 pa 的位置,找到離 pb 位置最近的非 0 元素,然後在新的 pa 結點的位置後邊插入;
  3. 當 pa 的列值 j == pb 的列值 j, 且兩結點的值相加結果不為 0 ,只需要更改 pa 指向的結點的值即可;
  4. 當 pa 的列值 j == pb 的列值 j ,但是兩結點的值相加結果為 0 ,就需要從矩陣 A 的十字連結串列中刪除 pa 指向的結點。

實現程式碼:
CrossList AddSMatrix(CrossList M,CrossList N){
    OLNode * pa,*pb;//新增的兩個用於遍歷兩個矩陣的結點
    OLink * hl=(OLink*)malloc(M.nu*sizeof(OLink));//用於儲存當前遍歷的行為止以上的區域每一個列的最後一個非0元素的位置。
    OLNode * pre=NULL;//用於指向pa指標所在位置的此行的前一個結點
    //遍歷初期,首先要對hl陣列進行初始化,指向每一列的第一個非0元素
    for (int j=1; j<=M.nu; j++) {
        hl[j]=M.chead[j];
    }
    //按照行進行遍歷
    for (int i=1; i<=M.mu; i++) {
        //遍歷每一行以前,都要pa指向矩陣M當前行的第一個非0元素;指標pb也是如此,只不過遍歷物件為矩陣N
        pa=M.rhead[i];
        pb=N.rhead[i];
        //當pb為NULL時,說明矩陣N的當前行的非0元素已經遍歷完。
        while (pb!=NULL) {
            //建立一個新的結點,每次都要複製一個pb結點,但是兩個指標域除外。(複製的目的就是排除指標域的干擾)
            OLNode * p=(OLNode*)malloc(sizeof(OLNode));
            p->i=pb->i;
            p->j=pb->j;
            p->e=pb->e;
            p->down=NULL;
            p->right=NULL;
           
            //第一種情況
            if (pa==NULL||pa->j>pb->j) {
                //如果pre為NULL,說明矩陣M此行沒有非0元素
                if (pre==NULL) {
                    M.rhead[p->i]=p;
                }else{//由於程式開始時pre肯定為NULL,所以,pre指向的是第一個p的位置,在後面的遍歷過程中,p指向的位置是逐漸向後移動的,所有,pre肯定會在p的前邊
                    pre->right=p;
                }
                p->right=pa;
                pre=p;
                //在連結好行連結串列之後,連結到對應列的列連結串列中的相應位置
                if (!M.chead[p->j]||M.chead[p->j]->i>p->i) {
                    p->down=M.chead[p->j];
                    M.chead[p->j]=p;
                }else{
                    p->down=hl[p->j]->down;
                    hl[p->j]->down=p;
                }
                //更新hl中的資料
                hl[p->j]=p;
            }else{
                //第二種情況,只需要移動pa的位置,繼續判斷pa和pb的位置,一定要有continue
                if (pa->j<pb->j) {
                    pre=pa;
                    pa=pa->right;
                    continue;
                }
                //第三、四種情況,當行標和列標都想等的情況下,需要討論兩者相加的值的問題
                if (pa->j==pb->j) {
                pa->e+=pb->e;
                //如果為0,摘除當前結點,並釋放所佔的空間
                if (pa->e==0) {
                    if (pre==NULL) {
                        M.rhead[pa->i]=pa->right;
                    }else{
                        pre->right=pa->right;
                    }
                    p=pa;
                    pa=pa->right;
                    if (M.chead[p->j]==p) {
                        M.chead[p->j]=hl[p->j]=p->down;
                    }else{
                        hl[p->j]->down=p->down;
                    }
                    free(p);
                    }
                }
            }
            pb=pb->right;
        }
    }
    //用於輸出矩陣三元組的功能函式
    display(M);
    return M;
}

完整程式碼演示

#include<stdio.h>
#include<stdlib.h>

typedef struct OLNode
{
    int i,j,e;                      //矩陣三元組i代表行 j代表列 e代表當前位置的資料
    struct OLNode *right,*down;     //指標域 右指標 下指標
}OLNode,*OLink;

typedef struct
{
    OLink *rhead,*chead;            //行和列連結串列頭指標
    int mu,nu,tu;                   //矩陣的行數,列數和非零元的個數
}CrossList;

CrossList CreateMatrix_OL(CrossList M);
CrossList AddSMatrix(CrossList M,CrossList N);

void display(CrossList M);

void main()
{
    CrossList M,N;
    printf("輸入測試矩陣M:\n");
    M=CreateMatrix_OL(M);
    printf("輸入測試矩陣N:\n");
    N=CreateMatrix_OL(N);
    M=AddSMatrix(M,N);
    printf("矩陣相加的結果為:\n");
    display(M);
}

CrossList CreateMatrix_OL(CrossList M)
{
    int m,n,t;
    int i,j,e;
    OLNode *p,*q;
    scanf("%d%d%d",&m,&n,&t);
    M.mu=m;
    M.nu=n;
    M.tu=t;
    if(!(M.rhead=(OLink*)malloc((m+1)*sizeof(OLink)))||!(M.chead=(OLink*)malloc((n+1)*sizeof(OLink))))
    {
        printf("初始化矩陣失敗");
        exit(0);
    }
    for(i=1;i<=m;i++)
    {
        M.rhead[i]=NULL;
    }
    for(j=1;j<=n;j++)
    {
        M.chead[j]=NULL;
    }
    for(scanf("%d%d%d",&i,&j,&e);0!=i;scanf("%d%d%d",&i,&j,&e))    {
        if(!(p=(OLNode*)malloc(sizeof(OLNode))))
        {
            printf("初始化三元組失敗");
            exit(0);
        }
        p->i=i;
        p->j=j;
        p->e=e;
        if(NULL==M.rhead[i]||M.rhead[i]->j>j)
        {
            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 M;
}

CrossList AddSMatrix(CrossList M,CrossList N){
    OLNode * pa,*pb;
    OLink * hl=(OLink*)malloc(M.nu*sizeof(OLink));
    OLNode * pre=NULL;
    for (int j=1; j<=M.nu; j++) {
        hl[j]=M.chead[j];
    }
    for (int i=1; i<=M.mu; i++) {
        pa=M.rhead[i];
        pb=N.rhead[i];
        while (pb!=NULL) {
            OLNode * p=(OLNode*)malloc(sizeof(OLNode));
            p->i=pb->i;
            p->j=pb->j;
            p->e=pb->e;
            p->down=NULL;
            p->right=NULL;
            if (pa==NULL||pa->j>pb->j) {
                if (pre==NULL) {
                    M.rhead[p->i]=p;
                }else{
                    pre->right=p;
                }
                p->right=pa;
                pre=p;
                if (!M.chead[p->j]||M.chead[p->j]->i>p->i) {
                    p->down=M.chead[p->j];
                    M.chead[p->j]=p;
                }else{
                    p->down=hl[p->j]->down;
                    hl[p->j]->down=p;
                }
                hl[p->j]=p;
            }else{
                if (pa->j<pb->j) {
                    pre=pa;
                    pa=pa->right;
                    continue;
                }
                if (pa->j==pb->j) {
                pa->e+=pb->e;
                if (pa->e==0) {
                    if (pre==NULL) {
                        M.rhead[pa->i]=pa->right;
                    }else{
                        pre->right=pa->right;
                    }
                    p=pa;
                    pa=pa->right;
                    if (M.chead[p->j]==p) {
                        M.chead[p->j]=hl[p->j]=p->down;
                    }else{
                        hl[p->j]->down=p->down;
                    }
                    free(p);
                    }
                }
            }
            pb=pb->right;
        }
    }
    display(M);
    return M;
}

void display(CrossList M){
    printf("輸出測試矩陣:\n");
    printf("M:\n---------------------\ni\tj\te\n---------------------\n");
    for (int i=1;i<=M.nu;i++)
    {
        if (NULL!=M.chead[i])
        {
           OLink p=M.chead[i];
            while (NULL!=p)
            {
                printf("%d\t%d\t%d\n",p->i,p->j,p->e);
                p=p->down;
            }
        }
    }

}
執行結果: 輸入測試矩陣M:
3 3 3
1 2 1
2 1 1
3 3 1
0 0 0
輸入測試矩陣N:
3 3 4
1 2 -1
1 3 1
2 3 1
3 1 1
0 0 0
矩陣相加的結果為:
輸出測試矩陣:
M:
---------------------
i j e
---------------------
2 1 1
3 1 1
1 3 1
2 3 1
3 3 1

總結

使用十字連結串列法解決稀疏矩陣的壓縮儲存的同時,在解決矩陣相加的問題中,對於某個單獨的結點來說,演算法的時間複雜度為一個常數(全部為選擇結構),演算法的整體的時間複雜度取決於兩矩陣中非0元素的個數。