數據結構(13)矩陣及其壓縮存儲

前言

矩陣我們平時接觸得比較少,但是在計算機中如何存儲表示它也值得思考。通常在高級語言裏,用二維數組來存儲矩陣元素。但是在矩陣中有許多值相同的元素或者零元素,使用二維數組來保存可能會浪費空間,因此牽扯到矩陣的壓縮存儲問題。

所謂的壓縮存儲是指,爲多個值相同的元只分配一個存儲空間,對零元不分配存儲空間。

特殊矩陣與稀疏矩陣

矩陣有特殊矩陣和稀疏矩陣的區別。

特殊矩陣是指:值相同的元素或者零元素在矩陣的分佈中有一定規律

而稀疏矩陣是指:零元素的數量遠多於非零元素的數量,並且非零元素的分佈沒有規律可言

特殊矩陣的壓縮存儲

特殊矩陣中,非零元素的分佈都有一定的規律,我們可以根據規律將其壓縮到一維數組中,並且找到每個非零元素在一維數組中的對應關係。以對稱矩陣爲例,由於對稱矩陣沿對角線兩側的數據相等,因此我們可以只存儲矩陣中對角線一側(包含對角線)的數據即可。

img_1

按行元素存儲到一維數組中,可以得到如下的一維數組

img_2
矩陣中的元素和一維數組中的元素是存在着一一對應的關係的,設某元素所在的位置爲( i , j ),它的值與數組中對應值skr[k]的關係如下:

img_3

注:矩陣元素中的行和列從1開始

例如,現在要求位於(3,1)處的元素值,代入公式:

img_4
得k值爲3,代入一維數組skr[3] = 3,與矩陣相符。

以上參考:矩陣(稀疏矩陣)壓縮存儲(3種方式)

除了對稱矩陣外,還有諸如三角矩陣、對角矩陣等都可以找到規律來壓縮存儲,這裏不詳談了,下面主要講稀疏矩陣的壓縮存儲。

稀疏矩陣的壓縮存儲

用三元組來保存非零元素

按照壓縮存儲的概念,我們只存儲其非零元素的值,而稀疏矩陣的問題在於其非零元素的分佈沒有規律。因此除了值本身之外,我們還需要存儲它所在的行和列的位置。這實際上需要一個結構體來表示,一般稱爲三元組。

//三元組
typedef struct Triple{
    //行
    int i;
    //列
    int j;
    //元素值
    ElemType e;
}Triple;

例如,如圖所示的稀疏矩陣

img_5

可以由如下的三元組表來記錄

img_6

當然,一般還需要記錄下稀疏矩陣的行數、列數。

通過三元組表可以來表示稀疏矩陣,而如何來存儲表示三元組表,則可以引申出稀疏矩陣不同的壓縮存儲方法。

以上三種方法除了都存儲行、列和元素值外,在結點或者整個表結構的設計上會有所區別,例如十字鏈表法的結點中還會額外設置兩個指針域。而三元組順序表,一般會記錄整個稀疏矩陣的行數、列數和非零元的個數。

//稀疏矩陣
typedef struct SMatrix{
    //一個數組用於保存三元組
    Triple data[MAXSIZE];
    //行數
    int mu;
    //列數
    int nu;
    //非零元素的個數
    int tu;
}SMatrix;

三元組順序表矩陣的創建

這裏所說的創建矩陣,實際上就是將一個稀疏矩陣轉化爲三元組順序表的過程。我們默認從文件中讀取一個矩陣,第一行爲矩陣的行數和列數,文件內容如下(矩陣爲上圖展示的矩陣M):

img_7

首先讀取出矩陣的行和列

//讀取出矩陣的行和列
fscanf(fp, "%d %d",&m->mu,&m->nu);

根據行列遍歷矩陣,讀取到每一個值後,判斷是否爲零,若不是則記錄下行數、列數和該值即可。

//讀取數據
    int value;
		//行
    for (int i = 0; i < m->mu; i++) {
        //列
        for (int j = 0; j < m->nu; j ++) {
            fscanf(fp, "%d",&value);
            if (value != 0) {
                //保存到三元組中
                m->data[m->tu].e = value;
                m->data[m->tu].i = i;
                m->data[m->tu].j = j;
                //非零元個數
                m->tu ++;
            }
        }
    }

三元組順序表矩陣的轉置

轉置是矩陣的運算之一,設矩陣M爲m行n列,則其轉置的矩陣T爲n行m列,且 T( i , j ) = M( j , i ) 。如圖所示。

img_8

如圖,a.data表示的是矩陣M,b.data表示的是矩陣T,如何將a轉成b呢?

img_9
可以看到對於矩陣M來說,a.data是按行來依次輸出,而b.data是按列來依次輸出,並且由於轉置運算,需要將 i 和 j 對調。

img_10
那麼我們的轉置算法可以這樣設計:

  • 用一個 col 循環來查找每一列,即依次從1 2 3 4…開始找
  • 遍歷整個三元組表,比較與當前循環次數相等的 j 下標
  • 如果相等說明是該列的元素,需要行列對調,然後保存到新表中

img_11

可以發現,這個方法需要的循環次數過多,假如列數和非零元個數很多,就沒有效率,下面介紹矩陣的快速轉置方法。

三元組順序表矩陣的快速轉置

矩陣的快速轉置方法,它的基本思想是:先計算出每一列元素在b.data裏所佔的位置,這樣在每轉置一個元素時,可以直接得其在b.data的位置,存入即可

img_12

爲了實現這個算法,我們需要額外設置兩個數組,一個用於保存每一列中非零元素的個數,進而得到另一個用於保存每一列的第一個非零元素在b.data中位置的數組。

img_13

得到如上數組後,我們只需要遍歷一次a.data,依次把元素放入b.data的相應位置就好了。需要注意的是,在存完一個元素後,對應列的第一個非零元素在b.data的位置應該加一,例如在存完(2,1,12)後,下一個(2,5,18)應該保存到4的位置。這個”每一列第一個非零元素“,應該是”剩下還沒轉置“的第一個。

img_14

全部代碼

需要說明一下:矩陣的行和列從1開始,爲了方便說明包括數組也是從1開始的,但是在代碼中爲了方便,行列值都是從0開始

Matrix.txt

6 7

0 12 9 0 0 0 0

0 0 0 0 0 0 0

-3 0 0 0 0 14 0

0 0 24 0 0 0 0

0 18 0 0 0 0 0

15 0 0 -7 0 0 0

SpareMatrix.h

#ifndef SparseMatrix_h
#define SparseMatrix_h

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

#define ElemType int
#define MAXSIZE 20

//三元組
typedef struct Triple{
    //行
    int i;
    //列
    int j;
    //元素值
    ElemType e;
}Triple;

//稀疏矩陣
typedef struct SMatrix{
    //一個數組用於保存三元組
    Triple data[MAXSIZE];
    //行數
    int mu;
    //列數
    int nu;
    //非零元素的個數
    int tu;
}SMatrix;

//創建矩陣
void CreateMatrix(SMatrix *m);
//打印矩陣
void PrintMatrix(SMatrix *m);
//拷貝矩陣->將m拷貝到t中
void CopyMatrix(SMatrix *m,SMatrix *t);
//矩陣轉置
void TransposeMatrix(SMatrix *m,SMatrix *t);
//矩陣快速轉置
void FastTransposeMatrix(SMatrix *m,SMatrix *t);

#endif /* SparseMatrix_h */

SpareMatrix.c

#include "SparseMatrix.h"
//創建矩陣
void CreateMatrix(SMatrix *m){
    //從文件中讀取數據
    FILE *fp = fopen("/Matrix.txt", "r");
    if (fp == NULL) {
        exit(1);
    }
    //讀取出矩陣的行和列
    fscanf(fp, "%d %d",&m->mu,&m->nu);
    //讀取數據
    int value;
    //行
    for (int i = 0; i < m->mu; i++) {
        //列
        for (int j = 0; j < m->nu; j ++) {
            fscanf(fp, "%d",&value);
            if (value != 0) {
                //保存到三元組中
                m->data[m->tu].e = value;
                m->data[m->tu].i = i;
                m->data[m->tu].j = j;
                //非零元個數
                m->tu ++;
            }
        }
    }
    fclose(fp);
}

//打印矩陣(只打印三元組表
void PrintMatrix(SMatrix *m){
    printf("row:%d col:%d\n",m->mu,m->nu);
    for (int i = 0; i < m->tu; i ++) {
        printf("(%d,%d,%d)\n",m->data[i].i,m->data[i].j,m->data[i].e);
    }
}

//拷貝矩陣->將m拷貝到t中
void CopyMatrix(SMatrix *m,SMatrix *t){
    t->mu = m->nu;
    t->nu = m->nu;
    t->tu = m->tu;
    
    for (int i = 0; i < m->tu; i ++) {
        t->data[i].i = m->data[i].i;
        t->data[i].j = m->data[i].j;
        t->data[i].e = m->data[i].e;
    }
}

//矩陣轉置
void TransposeMatrix(SMatrix *m,SMatrix *t){
    //先轉置行數和列數
    t->mu = m->nu;
    t->nu = m->mu;
    t->tu = m->tu;
    
    //轉置數值
    int k = 0;
    if (m->tu != 0) {
        //從每一列開始找
        for (int col = 0; col < m->nu ; col++) {
            //遍歷整個三元組表,尋找j座標和當前列標一致的元素
            for (int i = 0; i < m->tu; i++) {
                if (m->data[i].j == col) {
                    //交換i、j值,保存數據
                    t->data[k].i = m->data[i].j;
                    t->data[k].j = m->data[i].i;
                    t->data[k].e = m->data[i].e;
                    k ++;
                }
            }
        }
    }
}

//矩陣快速轉置
void FastTransposeMatrix(SMatrix *m,SMatrix *t){
    //先轉置行數和列數
    t->mu = m->nu;
    t->nu = m->mu;
    t->tu = m->tu;
    
    //記錄每一列(即轉置後每一行)的非零元素個數
    int *num = (int *)malloc(sizeof(int) * m->tu);
    assert(num != NULL);
    
    //記錄每一列(即轉置後每一行)的第一個非零元素在轉置後的位置
    int *cpot = (int *)malloc(sizeof(int)*m->nu);
    assert(cpot != NULL);
    
    if (t->tu != 0) {
        //先將計數矩陣初始化爲零
        for (int col = 0; col < m->nu; col++) {
            num[col] = 0;
        }
        //統計每一列非零元素的個數
        for (int t = 0; t < m->tu; t++) {
            num[m->data[t].j]++;
        }
        
        //計算每一列第一個元素轉置後的起始位置
        //第0列直接爲0
        cpot[0] = 0;
        for (int col = 1; col < m->nu; col++) {
            //此後的每一列起始位置 = 上一列的起始位置+上一列非零元素的個數
            cpot[col] = cpot[col -1] + num[col -1];
        }
        
        
        int q = 0;
        for (int p = 0; p < m->tu; p ++) {
            //得到列數
            int col = m->data[p].j;
            //得到本列非零元素的起始位置
            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] ++;
        }
    }
    free(num);
    free(cpot);
}

Main.c

#include "SparseMatrix.h"

int main(int argc, const char * argv[]) {
    SMatrix sm,sm1;
    //全部初始化爲0
    memset(&sm, 0, sizeof(sm));
    
    CreateMatrix(&sm);
    PrintMatrix(&sm);
    
    //拷貝
   // CopyMatrix(&sm, &sm1);
   // PrintMatrix(&sm1);
    
    //轉置
    //TransposeMatrix(&sm, &sm1);
    FastTransposeMatrix(&sm, &sm1);
    PrintMatrix(&sm1);
    return 0;
}
發佈了36 篇原創文章 · 獲贊 18 · 訪問量 2萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章